2021-05-13 19:56:38 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System ;
using System.Collections.Generic ;
using System.Linq ;
2021-05-13 20:16:19 +08:00
using JetBrains.Annotations ;
2021-05-13 19:56:38 +08:00
using osu.Framework.Graphics ;
using osu.Game.Rulesets.Objects ;
using osu.Game.Rulesets.Objects.Drawables ;
using osu.Game.Rulesets.UI ;
namespace osu.Game.Screens.Edit.Compose
{
/// <summary>
/// A queue which processes events from the many <see cref="HitObjectContainer"/>s in a nested <see cref="Playfield"/> hierarchy.
/// </summary>
internal class HitObjectContainerEventQueue : Component
{
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes used by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If the ruleset uses pooled objects, this represents the time when the <see cref="HitObject"/>s become alive.
/// </remarks>
2021-05-13 20:16:19 +08:00
public event Action < HitObject > HitObjectUsageBegan ;
2021-05-13 19:56:38 +08:00
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes unused by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If the ruleset uses pooled objects, this represents the time when the <see cref="HitObject"/>s become dead.
/// </remarks>
public event Action < HitObject > HitObjectUsageFinished ;
/// <summary>
/// Invoked when a <see cref="HitObject"/> has been transferred to another <see cref="DrawableHitObject"/>.
/// </summary>
public event Action < HitObject , DrawableHitObject > HitObjectUsageTransferred ;
private readonly Playfield playfield ;
/// <summary>
/// Creates a new <see cref="HitObjectContainerEventQueue"/>.
/// </summary>
/// <param name="playfield">The most top-level <see cref="Playfield"/>.</param>
2021-05-13 20:16:19 +08:00
public HitObjectContainerEventQueue ( [ NotNull ] Playfield playfield )
2021-05-13 19:56:38 +08:00
{
this . playfield = playfield ;
2021-05-13 20:16:19 +08:00
playfield . HitObjectUsageBegan + = onHitObjectUsageBegan ;
playfield . HitObjectUsageFinished + = onHitObjectUsageFinished ;
2021-05-13 19:56:38 +08:00
}
2021-05-13 20:41:18 +08:00
private readonly Dictionary < HitObject , EventType > pendingEvents = new Dictionary < HitObject , EventType > ( ) ;
2021-05-13 19:56:38 +08:00
2021-05-13 20:41:18 +08:00
private void onHitObjectUsageBegan ( HitObject hitObject ) = > updateEvent ( hitObject , EventType . Began ) ;
2021-05-13 19:56:38 +08:00
2021-05-13 20:41:18 +08:00
private void onHitObjectUsageFinished ( HitObject hitObject ) = > updateEvent ( hitObject , EventType . Finished ) ;
private void updateEvent ( HitObject hitObject , EventType newEvent )
{
if ( ! pendingEvents . TryGetValue ( hitObject , out EventType existingEvent ) )
{
pendingEvents [ hitObject ] = newEvent ;
return ;
}
switch ( existingEvent , newEvent )
{
2021-05-18 17:49:11 +08:00
// This exists as a safeguard to ensure that the sequence: { Began -> Finished }, where { ... } indicates a sequence within a single frame, does not trigger any events.
// This is unlikely to occur in practice as it requires the usage to finish immediately after the HitObjectContainer updates hitobject lifetimes,
// however, an Editor action scheduled somewhere between the lifetime update and this event queue's own Update() could cause this.
case ( EventType . Began , EventType . Finished ) :
pendingEvents . Remove ( hitObject ) ;
break ;
// This exists as a safeguard to ensure that the sequence: Began -> { Finished -> Began -> Finished }, where { ... } indicates a sequence within a single frame,
// correctly leads into a final "finished" state rather than remaining in the intermediate "transferred" state.
// As above, this is unlikely to occur in practice.
2021-05-13 20:41:18 +08:00
case ( EventType . Transferred , EventType . Finished ) :
pendingEvents [ hitObject ] = EventType . Finished ;
break ;
case ( EventType . Finished , EventType . Began ) :
pendingEvents [ hitObject ] = EventType . Transferred ;
break ;
default :
throw new ArgumentOutOfRangeException ( $"Unexpected event update ({existingEvent} => {newEvent})." ) ;
}
}
2021-05-13 19:56:38 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2021-05-13 20:41:18 +08:00
foreach ( var ( hitObject , e ) in pendingEvents )
2021-05-13 19:56:38 +08:00
{
2021-05-13 20:41:18 +08:00
switch ( e )
2021-05-13 19:56:38 +08:00
{
2021-05-13 20:41:18 +08:00
case EventType . Began :
HitObjectUsageBegan ? . Invoke ( hitObject ) ;
break ;
2021-05-13 19:56:38 +08:00
2021-05-13 20:41:18 +08:00
case EventType . Transferred :
2021-05-13 19:56:38 +08:00
HitObjectUsageTransferred ? . Invoke ( hitObject , playfield . AllHitObjects . Single ( d = > d . HitObject = = hitObject ) ) ;
2021-05-13 20:41:18 +08:00
break ;
case EventType . Finished :
HitObjectUsageFinished ? . Invoke ( hitObject ) ;
break ;
2021-05-13 19:56:38 +08:00
}
}
2021-05-13 20:41:18 +08:00
pendingEvents . Clear ( ) ;
2021-05-13 19:56:38 +08:00
}
2021-05-13 20:16:19 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
playfield . HitObjectUsageBegan - = onHitObjectUsageBegan ;
playfield . HitObjectUsageFinished - = onHitObjectUsageFinished ;
}
2021-05-13 20:41:18 +08:00
private enum EventType
{
2021-05-18 16:39:45 +08:00
/// <summary>
/// A <see cref="HitObject"/> has started being used by a <see cref="DrawableHitObject"/>.
/// </summary>
2021-05-13 20:41:18 +08:00
Began ,
2021-05-18 16:39:45 +08:00
/// <summary>
/// A <see cref="HitObject"/> has finished being used by a <see cref="DrawableHitObject"/>.
/// </summary>
2021-05-13 20:41:18 +08:00
Finished ,
2021-05-18 16:39:45 +08:00
/// <summary>
/// An internal intermediate state that occurs when a <see cref="HitObject"/> has finished being used by one <see cref="DrawableHitObject"/>
/// and started being used by another <see cref="DrawableHitObject"/> in the same frame. The <see cref="DrawableHitObject"/> may be the same instance in both cases.
/// </summary>
/// <remarks>
/// This usually occurs when a <see cref="HitObject"/> is transferred between <see cref="HitObjectContainer"/>s,
/// but also occurs if the <see cref="HitObject"/> dies and becomes alive again in the same frame within the same <see cref="HitObjectContainer"/>.
/// </remarks>
2021-05-13 20:41:18 +08:00
Transferred
}
2021-05-13 19:56:38 +08:00
}
}