1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 16:07:25 +08:00
osu-lazer/osu.Game/Rulesets/UI/HitObjectContainer.cs

227 lines
7.9 KiB
C#
Raw Normal View History

// 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
using System;
2018-04-13 17:19:50 +08:00
using System.Collections.Generic;
using System.Diagnostics;
2018-04-13 17:19:50 +08:00
using System.Linq;
using osu.Framework.Allocation;
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.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
2018-04-13 17:19:50 +08:00
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Pooling;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Rulesets.UI
{
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);
public IEnumerable<DrawableHitObject> AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime);
/// <summary>
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
/// </summary>
public event Action<DrawableHitObject, JudgementResult> NewResult;
/// <summary>
/// Invoked when a <see cref="DrawableHitObject"/> judgement is reverted.
/// </summary>
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>
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>
internal event Action<HitObject> HitObjectUsageFinished;
2020-11-12 17:32:20 +08:00
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
2021-04-29 13:42:41 +08:00
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> nonPooledDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
[Resolved(CanBeNull = true)]
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
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 16:07:20 +08:00
base.LoadAsyncComplete();
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.
SortInternal();
}
#region Pooling support
public override bool Remove(HitObjectLifetimeEntry entry)
2021-04-29 13:42:41 +08:00
{
if (!base.Remove(entry)) return false;
// 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);
return true;
}
protected sealed override DrawableHitObject GetDrawable(HitObjectLifetimeEntry entry)
{
if (nonPooledDrawableMap.TryGetValue(entry, out var drawable))
return drawable;
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
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
{
if (!nonPooledDrawableMap.ContainsKey(entry))
{
addDrawable(drawable);
HitObjectUsageBegan?.Invoke(entry.HitObject);
}
OnAdd(drawable);
}
protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
{
drawable.OnKilled();
if (!nonPooledDrawableMap.ContainsKey(entry))
{
removeDrawable(drawable);
HitObjectUsageFinished?.Invoke(entry.HitObject);
}
OnRemove(drawable);
}
private void addDrawable(DrawableHitObject drawable)
{
drawable.OnNewResult += onNewResult;
drawable.OnRevertResult += onRevertResult;
bindStartTime(drawable);
AddInternal(drawable);
}
private void removeDrawable(DrawableHitObject drawable)
{
drawable.OnNewResult -= onNewResult;
drawable.OnRevertResult -= onRevertResult;
unbindStartTime(drawable);
RemoveInternal(drawable);
}
#endregion
#region Non-pooling support
public virtual void Add(DrawableHitObject drawable)
{
if (drawable.Entry == null)
throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated");
nonPooledDrawableMap.Add(drawable.Entry, drawable);
addDrawable(drawable);
Add(drawable.Entry);
}
public virtual bool Remove(DrawableHitObject drawable)
{
if (drawable.Entry == null)
2019-12-18 11:03:15 +08:00
return false;
return Remove(drawable.Entry);
}
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
#endregion
/// <summary>
/// Invoked after a <see cref="DrawableHitObject"/> is added to this container.
/// </summary>
protected virtual void OnAdd(DrawableHitObject drawableHitObject)
{
Debug.Assert(drawableHitObject.LoadState >= LoadState.Ready);
}
/// <summary>
/// Invoked after a <see cref="DrawableHitObject"/> is removed from this container.
/// </summary>
protected virtual void OnRemove(DrawableHitObject drawableHitObject)
{
}
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)
{
var bindable = hitObject.StartTimeBindable.GetBoundCopy();
bindable.BindValueChanged(_ =>
{
2020-11-12 16:07:20 +08:00
if (LoadState >= LoadState.Ready)
SortInternal();
});
startTimeMap[hitObject] = bindable;
}
private void unbindStartTime(DrawableHitObject hitObject)
{
startTimeMap[hitObject].UnbindAll();
startTimeMap.Remove(hitObject);
}
2018-04-13 17:19:50 +08:00
private void unbindAllStartTimes()
{
foreach (var kvp in startTimeMap)
kvp.Value.UnbindAll();
startTimeMap.Clear();
2020-11-06 23:57:33 +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
int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime);
2018-04-13 17:19:50 +08:00
return i == 0 ? CompareReverseChildID(x, y) : i;
}
#endregion
2020-11-06 23:57:33 +08:00
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
unbindAllStartTimes();
}
2018-04-13 17:19:50 +08:00
}
}