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
{
2018-12-13 13:55:28 +08:00
public class HitObjectContainer : LifetimeManagementContainer
2018-04-13 17:19:50 +08:00
{
2020-11-10 21:49:02 +08:00
/// <summary>
/// All currently in-use <see cref="DrawableHitObject"/>s.
/// </summary>
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
/// <summary>
/// All currently in-use <see cref="DrawableHitObject"/>s that are alive.
/// </summary>
/// <remarks>
/// If this <see cref="HitObjectContainer"/> uses pooled objects, this is equivalent to <see cref="Objects"/>.
/// </remarks>
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>
public event Action < HitObject > HitObjectUsageBegan ;
/// <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>
public event Action < HitObject > HitObjectUsageFinished ;
2020-11-10 21:49:02 +08:00
private readonly Dictionary < DrawableHitObject , IBindable > startTimeMap = new Dictionary < DrawableHitObject , IBindable > ( ) ;
private readonly Dictionary < HitObjectLifetimeEntry , DrawableHitObject > drawableMap = new Dictionary < HitObjectLifetimeEntry , DrawableHitObject > ( ) ;
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager ( ) ;
[Resolved(CanBeNull = true)]
private DrawableRuleset drawableRuleset { 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 ;
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
public void Add ( HitObjectLifetimeEntry entry ) = > lifetimeManager . AddEntry ( entry ) ;
2020-11-10 22:32:30 +08:00
public bool Remove ( HitObjectLifetimeEntry entry ) = > lifetimeManager . RemoveEntry ( entry ) ;
2020-11-10 21:49:02 +08:00
private void entryBecameAlive ( LifetimeEntry entry ) = > addDrawable ( ( HitObjectLifetimeEntry ) entry ) ;
private void entryBecameDead ( LifetimeEntry entry ) = > removeDrawable ( ( HitObjectLifetimeEntry ) entry ) ;
private void addDrawable ( HitObjectLifetimeEntry entry )
{
Debug . Assert ( ! drawableMap . ContainsKey ( entry ) ) ;
2020-11-12 12:18:44 +08:00
var drawable = drawableRuleset . GetPooledDrawableRepresentation ( entry . HitObject ) ;
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()}." ) ;
2020-11-10 21:49:02 +08:00
drawable . OnNewResult + = onNewResult ;
drawable . OnRevertResult + = onRevertResult ;
bindStartTime ( drawable ) ;
AddInternal ( drawableMap [ entry ] = drawable , false ) ;
2020-11-12 17:32:20 +08:00
HitObjectUsageBegan ? . Invoke ( entry . HitObject ) ;
2020-11-10 21:49:02 +08:00
}
private void removeDrawable ( HitObjectLifetimeEntry entry )
2019-12-18 01:56:29 +08:00
{
2020-11-10 21:49:02 +08:00
Debug . Assert ( drawableMap . ContainsKey ( entry ) ) ;
2020-11-06 23:57:33 +08:00
2020-11-10 21:49:02 +08:00
var drawable = drawableMap [ entry ] ;
drawable . OnNewResult - = onNewResult ;
drawable . OnRevertResult - = onRevertResult ;
drawable . OnKilled ( ) ;
drawableMap . Remove ( entry ) ;
unbindStartTime ( drawable ) ;
RemoveInternal ( drawable ) ;
2020-11-12 17:32:20 +08:00
HitObjectUsageFinished ? . Invoke ( entry . HitObject ) ;
2020-11-10 21:49:02 +08:00
}
#endregion
#region Non - pooling support
public virtual void Add ( DrawableHitObject hitObject )
{
bindStartTime ( hitObject ) ;
hitObject . OnNewResult + = onNewResult ;
hitObject . OnRevertResult + = onRevertResult ;
2020-11-12 11:55:42 +08:00
AddInternal ( hitObject ) ;
2019-12-18 01:56:29 +08:00
}
public virtual bool Remove ( DrawableHitObject hitObject )
{
2019-12-18 11:03:15 +08:00
if ( ! RemoveInternal ( hitObject ) )
return false ;
2019-12-18 01:56:29 +08:00
2020-11-10 21:49:02 +08:00
hitObject . OnNewResult - = onNewResult ;
hitObject . OnRevertResult - = onRevertResult ;
unbindStartTime ( hitObject ) ;
2019-12-18 01:56:29 +08:00
2019-12-18 11:03:15 +08:00
return true ;
2019-12-18 01:56:29 +08:00
}
2020-11-10 21:49:02 +08:00
public int IndexOf ( DrawableHitObject hitObject ) = > IndexOfInternal ( hitObject ) ;
protected override void OnChildLifetimeBoundaryCrossed ( LifetimeBoundaryCrossedEvent e )
2020-04-23 10:16:59 +08:00
{
2020-11-10 21:49:02 +08:00
if ( ! ( e . Child is DrawableHitObject hitObject ) )
return ;
if ( ( e . Kind = = LifetimeBoundaryKind . End & & e . Direction = = LifetimeBoundaryCrossingDirection . Forward )
| | ( e . Kind = = LifetimeBoundaryKind . Start & & e . Direction = = LifetimeBoundaryCrossingDirection . Backward ) )
{
hitObject . OnKilled ( ) ;
}
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-06 23:57:33 +08:00
public virtual void Clear ( bool disposeChildren = true )
2020-09-21 17:27:15 +08:00
{
2020-11-10 21:49:02 +08:00
lifetimeManager . ClearEntries ( ) ;
2020-11-06 23:57:33 +08:00
ClearInternal ( disposeChildren ) ;
2020-11-10 21:49:02 +08:00
unbindAllStartTimes ( ) ;
2020-04-23 10:16:59 +08:00
}
2020-11-12 11:55:42 +08:00
protected override bool CheckChildrenLife ( )
{
bool aliveChanged = base . CheckChildrenLife ( ) ;
aliveChanged | = lifetimeManager . Update ( Time . Current , Time . Current ) ;
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
}
}