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
2022-06-17 15:37:17 +08:00
#nullable disable
2020-11-10 21:49:02 +08:00
using System ;
2018-04-13 17:19:50 +08:00
using System.Collections.Generic ;
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 ;
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 ;
2021-05-31 21:57:43 +08:00
using osu.Game.Rulesets.Objects.Pooling ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Rulesets.UI
{
2021-05-31 21:57:43 +08:00
public class HitObjectContainer : PooledDrawableWithLifetimeContainer < HitObjectLifetimeEntry , DrawableHitObject > , IHitObjectContainer
2018-04-13 17:19:50 +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
2021-05-31 21:57:43 +08:00
public IEnumerable < DrawableHitObject > AliveObjects = > AliveEntries . Select ( pair = > pair . Drawable ) . 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-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-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
[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-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-05-31 21:57:43 +08:00
public override bool Remove ( HitObjectLifetimeEntry entry )
2021-04-29 13:42:41 +08:00
{
2021-05-31 21:57:43 +08:00
if ( ! base . Remove ( 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-28 17:27:40 +08:00
return true ;
}
2020-11-10 21:49:02 +08:00
2021-05-31 21:57:43 +08:00
protected sealed override DrawableHitObject GetDrawable ( HitObjectLifetimeEntry entry )
2020-11-10 21:49:02 +08:00
{
2021-05-31 21:57:43 +08:00
if ( nonPooledDrawableMap . TryGetValue ( entry , out var drawable ) )
return drawable ;
2020-11-10 21:49:02 +08:00
2021-05-31 21:57:43 +08:00
return pooledObjectProvider ? . GetPooledDrawableRepresentation ( entry . HitObject , null ) ? ?
throw new InvalidOperationException ( $"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}." ) ;
}
2020-11-12 17:32:20 +08:00
2021-05-31 21:57:43 +08:00
protected override void AddDrawable ( HitObjectLifetimeEntry entry , DrawableHitObject drawable )
{
2021-05-31 22:07:32 +08:00
if ( nonPooledDrawableMap . ContainsKey ( entry ) ) return ;
2021-05-04 15:44:48 +08:00
2021-05-31 22:07:32 +08:00
addDrawable ( drawable ) ;
HitObjectUsageBegan ? . Invoke ( entry . HitObject ) ;
2020-11-10 21:49:02 +08:00
}
2021-05-31 21:57:43 +08:00
protected override void RemoveDrawable ( HitObjectLifetimeEntry entry , DrawableHitObject drawable )
2019-12-18 01:56:29 +08:00
{
2021-01-21 06:55:54 +08:00
drawable . OnKilled ( ) ;
2021-05-31 22:07:32 +08:00
if ( nonPooledDrawableMap . ContainsKey ( entry ) ) return ;
2021-05-04 15:44:48 +08:00
2021-05-31 22:07:32 +08:00
removeDrawable ( drawable ) ;
HitObjectUsageFinished ? . Invoke ( entry . HitObject ) ;
2021-05-04 15:44:48 +08:00
}
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 ) ;
#endregion
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
}
}