2019-01-24 16:43:03 +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.
2018-04-13 17:19:50 +08:00
2020-11-10 21:49:02 +08:00
using System ;
2018-04-13 17:19:50 +08:00
using System.Collections.Generic ;
2020-11-10 21:49:02 +08:00
using System.Diagnostics ;
2018-04-13 17:19:50 +08:00
using System.Linq ;
2020-11-10 21:49:02 +08:00
using osu.Framework.Allocation ;
2019-12-18 01:56:29 +08:00
using osu.Framework.Bindables ;
2020-11-12 13:54:33 +08:00
using osu.Framework.Extensions.TypeExtensions ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2020-10-29 14:20:10 +08:00
using osu.Framework.Graphics.Performance ;
2020-11-10 21:49:02 +08:00
using osu.Game.Rulesets.Judgements ;
using osu.Game.Rulesets.Objects ;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Objects.Drawables ;
namespace osu.Game.Rulesets.UI
{
2021-04-28 17:27:40 +08:00
public class HitObjectContainer : CompositeDrawable , IHitObjectContainer
2018-04-13 17:19:50 +08:00
{
2021-04-29 13:42:41 +08:00
/// <summary>
/// All entries in this <see cref="HitObjectContainer"/> including dead entries.
/// </summary>
public IEnumerable < HitObjectLifetimeEntry > Entries = > allEntries ;
/// <summary>
/// All alive entries and <see cref="DrawableHitObject"/>s used by the entries.
/// </summary>
2021-05-04 15:44:48 +08:00
public IEnumerable < ( HitObjectLifetimeEntry Entry , DrawableHitObject Drawable ) > AliveEntries = > aliveDrawableMap . Select ( x = > ( x . Key , x . Value ) ) ;
2021-04-29 13:42:41 +08:00
2020-11-11 18:27:07 +08:00
public IEnumerable < DrawableHitObject > Objects = > InternalChildren . Cast < DrawableHitObject > ( ) . OrderBy ( h = > h . HitObject . StartTime ) ;
2020-11-10 21:49:02 +08:00
2020-11-11 18:27:07 +08:00
public IEnumerable < DrawableHitObject > AliveObjects = > AliveInternalChildren . Cast < DrawableHitObject > ( ) . OrderBy ( h = > h . HitObject . StartTime ) ;
2020-11-10 21:49:02 +08:00
2020-11-10 22:32:30 +08:00
/// <summary>
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
/// </summary>
2020-11-10 21:49:02 +08:00
public event Action < DrawableHitObject , JudgementResult > NewResult ;
2020-11-10 22:32:30 +08:00
/// <summary>
/// Invoked when a <see cref="DrawableHitObject"/> judgement is reverted.
/// </summary>
2020-11-10 21:49:02 +08:00
public event Action < DrawableHitObject , JudgementResult > RevertResult ;
2020-11-12 17:32:20 +08:00
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes used by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If this <see cref="HitObjectContainer"/> uses pooled objects, this represents the time when the <see cref="HitObject"/>s become alive.
/// </remarks>
2020-11-26 13:01:46 +08:00
internal event Action < HitObject > HitObjectUsageBegan ;
2020-11-12 17:32:20 +08:00
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes unused by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If this <see cref="HitObjectContainer"/> uses pooled objects, this represents the time when the <see cref="HitObject"/>s become dead.
/// </remarks>
2020-11-26 13:01:46 +08:00
internal event Action < HitObject > HitObjectUsageFinished ;
2020-11-12 17:32:20 +08:00
2020-11-12 17:34:50 +08:00
/// <summary>
/// The amount of time prior to the current time within which <see cref="HitObject"/>s should be considered alive.
/// </summary>
2020-11-13 17:54:49 +08:00
internal double PastLifetimeExtension { get ; set ; }
2020-11-12 17:34:50 +08:00
/// <summary>
/// The amount of time after the current time within which <see cref="HitObject"/>s should be considered alive.
/// </summary>
2020-11-13 17:54:49 +08:00
internal double FutureLifetimeExtension { get ; set ; }
2020-11-12 17:34:50 +08:00
2020-11-10 21:49:02 +08:00
private readonly Dictionary < DrawableHitObject , IBindable > startTimeMap = new Dictionary < DrawableHitObject , IBindable > ( ) ;
2021-04-29 13:42:41 +08:00
2021-05-04 15:44:48 +08:00
private readonly Dictionary < HitObjectLifetimeEntry , DrawableHitObject > aliveDrawableMap = new Dictionary < HitObjectLifetimeEntry , DrawableHitObject > ( ) ;
2021-04-28 17:27:40 +08:00
private readonly Dictionary < HitObjectLifetimeEntry , DrawableHitObject > nonPooledDrawableMap = new Dictionary < HitObjectLifetimeEntry , DrawableHitObject > ( ) ;
2020-11-10 21:49:02 +08:00
2021-04-29 13:42:41 +08:00
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager ( ) ;
private readonly HashSet < HitObjectLifetimeEntry > allEntries = new HashSet < HitObjectLifetimeEntry > ( ) ;
2020-11-10 21:49:02 +08:00
[Resolved(CanBeNull = true)]
2020-11-13 23:54:57 +08:00
private IPooledHitObjectProvider pooledObjectProvider { get ; set ; }
2019-12-18 01:56:29 +08:00
2018-09-21 13:35:50 +08:00
public HitObjectContainer ( )
{
RelativeSizeAxes = Axes . Both ;
2020-11-10 21:49:02 +08:00
lifetimeManager . EntryBecameAlive + = entryBecameAlive ;
lifetimeManager . EntryBecameDead + = entryBecameDead ;
2021-04-28 17:27:40 +08:00
lifetimeManager . EntryCrossedBoundary + = entryCrossedBoundary ;
2018-09-21 13:35:50 +08:00
}
2020-11-12 16:07:20 +08:00
protected override void LoadAsyncComplete ( )
2020-11-12 15:58:40 +08:00
{
2020-11-12 16:07:20 +08:00
base . LoadAsyncComplete ( ) ;
2020-11-12 15:58:40 +08:00
2020-11-12 16:07:20 +08:00
// Application of hitobjects during load() may have changed their start times, so ensure the correct sorting order.
2020-11-12 15:58:40 +08:00
SortInternal ( ) ;
}
2020-11-10 21:49:02 +08:00
#region Pooling support
2021-04-29 13:42:41 +08:00
public void Add ( HitObjectLifetimeEntry entry )
{
allEntries . Add ( entry ) ;
lifetimeManager . AddEntry ( entry ) ;
}
2020-11-10 21:49:02 +08:00
2021-04-28 17:27:40 +08:00
public bool Remove ( HitObjectLifetimeEntry entry )
{
if ( ! lifetimeManager . RemoveEntry ( entry ) ) return false ;
2021-05-04 15:44:48 +08:00
// This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry.
if ( nonPooledDrawableMap . Remove ( entry , out var drawable ) )
removeDrawable ( drawable ) ;
2021-04-29 13:42:41 +08:00
allEntries . Remove ( entry ) ;
2021-04-28 17:27:40 +08:00
return true ;
}
2020-11-10 21:49:02 +08:00
2021-05-04 15:44:48 +08:00
private void entryBecameAlive ( LifetimeEntry lifetimeEntry )
2020-11-10 21:49:02 +08:00
{
2021-05-04 15:44:48 +08:00
var entry = ( HitObjectLifetimeEntry ) lifetimeEntry ;
Debug . Assert ( ! aliveDrawableMap . ContainsKey ( entry ) ) ;
2020-11-10 21:49:02 +08:00
2021-05-04 15:44:48 +08:00
bool isNonPooled = nonPooledDrawableMap . TryGetValue ( entry , out var drawable ) ;
2021-04-28 17:27:40 +08:00
drawable ? ? = pooledObjectProvider ? . GetPooledDrawableRepresentation ( entry . HitObject , null ) ;
2020-11-12 13:54:33 +08:00
if ( drawable = = null )
throw new InvalidOperationException ( $"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}." ) ;
2021-05-04 15:44:48 +08:00
aliveDrawableMap [ entry ] = drawable ;
2020-11-27 12:36:40 +08:00
OnAdd ( drawable ) ;
2020-11-12 17:32:20 +08:00
2021-05-04 15:44:48 +08:00
if ( isNonPooled ) return ;
addDrawable ( drawable ) ;
2020-11-26 13:01:46 +08:00
HitObjectUsageBegan ? . Invoke ( entry . HitObject ) ;
2020-11-10 21:49:02 +08:00
}
2021-05-04 15:44:48 +08:00
private void entryBecameDead ( LifetimeEntry lifetimeEntry )
2019-12-18 01:56:29 +08:00
{
2021-05-04 15:44:48 +08:00
var entry = ( HitObjectLifetimeEntry ) lifetimeEntry ;
Debug . Assert ( aliveDrawableMap . ContainsKey ( entry ) ) ;
2020-11-06 23:57:33 +08:00
2021-05-04 15:44:48 +08:00
var drawable = aliveDrawableMap [ entry ] ;
bool isNonPooled = nonPooledDrawableMap . ContainsKey ( entry ) ;
2021-01-21 06:55:54 +08:00
drawable . OnKilled ( ) ;
2021-05-04 15:44:48 +08:00
aliveDrawableMap . Remove ( entry ) ;
OnRemove ( drawable ) ;
if ( isNonPooled ) return ;
removeDrawable ( drawable ) ;
// The hit object is not freed when the DHO was not pooled.
HitObjectUsageFinished ? . Invoke ( entry . HitObject ) ;
}
private void addDrawable ( DrawableHitObject drawable )
{
drawable . OnNewResult + = onNewResult ;
drawable . OnRevertResult + = onRevertResult ;
bindStartTime ( drawable ) ;
AddInternal ( drawable ) ;
}
private void removeDrawable ( DrawableHitObject drawable )
{
2020-11-10 21:49:02 +08:00
drawable . OnNewResult - = onNewResult ;
drawable . OnRevertResult - = onRevertResult ;
unbindStartTime ( drawable ) ;
2020-11-26 13:01:46 +08:00
2021-05-04 15:44:48 +08:00
RemoveInternal ( drawable ) ;
2020-11-10 21:49:02 +08:00
}
#endregion
#region Non - pooling support
2021-04-28 17:27:40 +08:00
public virtual void Add ( DrawableHitObject drawable )
2020-11-10 21:49:02 +08:00
{
2021-04-28 17:27:40 +08:00
if ( drawable . Entry = = null )
throw new InvalidOperationException ( $"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated" ) ;
2020-11-10 21:49:02 +08:00
2021-04-28 17:27:40 +08:00
nonPooledDrawableMap . Add ( drawable . Entry , drawable ) ;
2021-05-04 15:44:48 +08:00
addDrawable ( drawable ) ;
2021-04-28 17:27:40 +08:00
Add ( drawable . Entry ) ;
2019-12-18 01:56:29 +08:00
}
2021-04-28 17:27:40 +08:00
public virtual bool Remove ( DrawableHitObject drawable )
2019-12-18 01:56:29 +08:00
{
2021-04-28 17:27:40 +08:00
if ( drawable . Entry = = null )
2019-12-18 11:03:15 +08:00
return false ;
2019-12-18 01:56:29 +08:00
2021-04-28 17:27:40 +08:00
return Remove ( drawable . Entry ) ;
2019-12-18 01:56:29 +08:00
}
2020-11-10 21:49:02 +08:00
public int IndexOf ( DrawableHitObject hitObject ) = > IndexOfInternal ( hitObject ) ;
2021-04-28 17:27:40 +08:00
private void entryCrossedBoundary ( LifetimeEntry entry , LifetimeBoundaryKind kind , LifetimeBoundaryCrossingDirection direction )
2020-04-23 10:16:59 +08:00
{
2021-04-28 17:27:40 +08:00
if ( nonPooledDrawableMap . TryGetValue ( ( HitObjectLifetimeEntry ) entry , out var drawable ) )
OnChildLifetimeBoundaryCrossed ( new LifetimeBoundaryCrossedEvent ( drawable , kind , direction ) ) ;
}
2020-11-10 21:49:02 +08:00
2021-04-28 17:27:40 +08:00
protected virtual void OnChildLifetimeBoundaryCrossed ( LifetimeBoundaryCrossedEvent e )
{
2020-09-21 17:27:15 +08:00
}
2020-04-23 10:16:59 +08:00
2020-11-10 21:49:02 +08:00
#endregion
2020-11-27 12:36:40 +08:00
/// <summary>
/// Invoked when a <see cref="DrawableHitObject"/> is added to this container.
/// </summary>
/// <remarks>
/// This method is not invoked for nested <see cref="DrawableHitObject"/>s.
/// </remarks>
protected virtual void OnAdd ( DrawableHitObject drawableHitObject )
{
}
/// <summary>
/// Invoked when a <see cref="DrawableHitObject"/> is removed from this container.
/// </summary>
/// <remarks>
/// This method is not invoked for nested <see cref="DrawableHitObject"/>s.
/// </remarks>
protected virtual void OnRemove ( DrawableHitObject drawableHitObject )
{
}
2021-04-28 17:27:40 +08:00
public virtual void Clear ( )
2020-09-21 17:27:15 +08:00
{
2020-11-10 21:49:02 +08:00
lifetimeManager . ClearEntries ( ) ;
2021-05-04 15:44:48 +08:00
foreach ( var drawable in nonPooledDrawableMap . Values )
removeDrawable ( drawable ) ;
2021-04-28 17:27:40 +08:00
nonPooledDrawableMap . Clear ( ) ;
2021-05-04 15:44:48 +08:00
Debug . Assert ( InternalChildren . Count = = 0 & & startTimeMap . Count = = 0 & & aliveDrawableMap . Count = = 0 , "All hit objects should have been removed" ) ;
2020-04-23 10:16:59 +08:00
}
2020-11-12 11:55:42 +08:00
protected override bool CheckChildrenLife ( )
{
bool aliveChanged = base . CheckChildrenLife ( ) ;
2020-11-12 17:34:50 +08:00
aliveChanged | = lifetimeManager . Update ( Time . Current - PastLifetimeExtension , Time . Current + FutureLifetimeExtension ) ;
2020-11-12 11:55:42 +08:00
return aliveChanged ;
}
2020-11-10 21:49:02 +08:00
private void onNewResult ( DrawableHitObject d , JudgementResult r ) = > NewResult ? . Invoke ( d , r ) ;
private void onRevertResult ( DrawableHitObject d , JudgementResult r ) = > RevertResult ? . Invoke ( d , r ) ;
#region Comparator + StartTime tracking
private void bindStartTime ( DrawableHitObject hitObject )
2019-12-18 01:56:29 +08:00
{
2020-11-10 21:49:02 +08:00
var bindable = hitObject . StartTimeBindable . GetBoundCopy ( ) ;
2020-11-12 15:58:40 +08:00
bindable . BindValueChanged ( _ = >
{
2020-11-12 16:07:20 +08:00
if ( LoadState > = LoadState . Ready )
2020-11-12 15:58:40 +08:00
SortInternal ( ) ;
} ) ;
2020-11-06 21:15:00 +08:00
2020-11-10 21:49:02 +08:00
startTimeMap [ hitObject ] = bindable ;
}
2020-11-06 21:15:00 +08:00
2020-11-10 21:49:02 +08:00
private void unbindStartTime ( DrawableHitObject hitObject )
2020-11-06 21:15:00 +08:00
{
2020-11-10 21:49:02 +08:00
startTimeMap [ hitObject ] . UnbindAll ( ) ;
startTimeMap . Remove ( hitObject ) ;
}
2018-04-13 17:19:50 +08:00
2020-11-10 21:49:02 +08:00
private void unbindAllStartTimes ( )
{
foreach ( var kvp in startTimeMap )
kvp . Value . UnbindAll ( ) ;
startTimeMap . Clear ( ) ;
2020-11-06 23:57:33 +08:00
}
2020-11-06 21:15:00 +08:00
2018-04-13 17:19:50 +08:00
protected override int Compare ( Drawable x , Drawable y )
{
if ( ! ( x is DrawableHitObject xObj ) | | ! ( y is DrawableHitObject yObj ) )
return base . Compare ( x , y ) ;
// Put earlier hitobjects towards the end of the list, so they handle input first
2020-11-10 21:49:02 +08:00
int i = yObj . HitObject . StartTime . CompareTo ( xObj . HitObject . StartTime ) ;
2018-04-13 17:19:50 +08:00
return i = = 0 ? CompareReverseChildID ( x , y ) : i ;
}
2019-01-29 14:25:27 +08:00
2020-11-10 21:49:02 +08:00
#endregion
2020-11-06 23:57:33 +08:00
2020-11-10 21:49:02 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
unbindAllStartTimes ( ) ;
2019-01-29 14:25:27 +08:00
}
2018-04-13 17:19:50 +08:00
}
}