mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 18:23:04 +08:00
Factor out lifetime management logic of HitObjectContainer
This commit is contained in:
parent
86a7ab791e
commit
fe2934db1d
@ -0,0 +1,157 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Performance;
|
||||
|
||||
namespace osu.Game.Rulesets.Objects.Pooling
|
||||
{
|
||||
/// <summary>
|
||||
/// A container of <typeparamref name="TDrawable"/>s dynamically added/removed by model <typeparamref name="TEntry"/>s.
|
||||
/// When an entry became alive, a drawable corresponding to the entry is obtained (potentially pooled), and added to this container.
|
||||
/// The drawable is removed when the entry became dead.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntry">The type of entries managed by this container.</typeparam>
|
||||
/// <typeparam name="TDrawable">The type of drawables corresponding to the entries.</typeparam>
|
||||
public abstract class PooledDrawableWithLifetimeContainer<TEntry, TDrawable> : CompositeDrawable
|
||||
where TEntry : LifetimeEntry
|
||||
where TDrawable : Drawable
|
||||
{
|
||||
/// <summary>
|
||||
/// All entries added to this container, including dead entries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The enumeration order is undefined.
|
||||
/// </remarks>
|
||||
public IEnumerable<TEntry> Entries => allEntries;
|
||||
|
||||
/// <summary>
|
||||
/// All alive entries and drawables corresponding to the entries.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The enumeration order is undefined.
|
||||
/// </remarks>
|
||||
public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value));
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time prior to the current time within which entries should be considered alive.
|
||||
/// </summary>
|
||||
internal double PastLifetimeExtension { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time after the current time within which entries should be considered alive.
|
||||
/// </summary>
|
||||
internal double FutureLifetimeExtension { get; set; }
|
||||
|
||||
private readonly Dictionary<TEntry, TDrawable> aliveDrawableMap = new Dictionary<TEntry, TDrawable>();
|
||||
private readonly HashSet<TEntry> allEntries = new HashSet<TEntry>();
|
||||
|
||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||
|
||||
protected PooledDrawableWithLifetimeContainer()
|
||||
{
|
||||
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
||||
lifetimeManager.EntryBecameDead += entryBecameDead;
|
||||
lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <typeparamref name="TEntry"/> to be managed by this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The aliveness of the entry is not updated until <see cref="CheckChildrenLife"/>.
|
||||
/// </remarks>
|
||||
public virtual void Add(TEntry entry)
|
||||
{
|
||||
allEntries.Add(entry);
|
||||
lifetimeManager.AddEntry(entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a <typeparamref name="TEntry"/> from this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the entry was alive, the corresponding drawable is removed.
|
||||
/// </remarks>
|
||||
/// <returns>Whether the entry was in this container.</returns>
|
||||
public virtual bool Remove(TEntry entry)
|
||||
{
|
||||
if (!lifetimeManager.RemoveEntry(entry)) return false;
|
||||
|
||||
allEntries.Remove(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize new <typeparamref name="TDrawable"/> corresponding <paramref name="entry"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <typeparamref name="TDrawable"/> corresponding to the entry.</returns>
|
||||
protected abstract TDrawable GetDrawable(TEntry entry);
|
||||
|
||||
private void entryBecameAlive(LifetimeEntry lifetimeEntry)
|
||||
{
|
||||
var entry = (TEntry)lifetimeEntry;
|
||||
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
||||
|
||||
TDrawable drawable = GetDrawable(entry);
|
||||
aliveDrawableMap[entry] = drawable;
|
||||
AddDrawable(entry, drawable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <typeparamref name="TDrawable"/> corresponding to <paramref name="entry"/> to this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Invoked when the entry became alive and a <typeparamref name="TDrawable"/> is obtained by <see cref="GetDrawable"/>.
|
||||
/// </remarks>
|
||||
protected virtual void AddDrawable(TEntry entry, TDrawable drawable) => AddInternal(drawable);
|
||||
|
||||
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
||||
{
|
||||
var entry = (TEntry)lifetimeEntry;
|
||||
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
||||
|
||||
TDrawable drawable = aliveDrawableMap[entry];
|
||||
aliveDrawableMap.Remove(entry);
|
||||
RemoveDrawable(entry, drawable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a <typeparamref name="TDrawable"/> corresponding to <paramref name="entry"/> from this container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Invoked when the entry became dead.
|
||||
/// </remarks>
|
||||
protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable);
|
||||
|
||||
private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) =>
|
||||
OnEntryCrossedBoundary((TEntry)lifetimeEntry, kind, direction);
|
||||
|
||||
protected virtual void OnEntryCrossedBoundary(TEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all <typeparamref name="TEntry"/>s.
|
||||
/// </summary>
|
||||
public virtual void Clear()
|
||||
{
|
||||
foreach (var entry in Entries.ToArray())
|
||||
Remove(entry);
|
||||
Debug.Assert(aliveDrawableMap.Count == 0);
|
||||
}
|
||||
|
||||
protected override bool CheckChildrenLife()
|
||||
{
|
||||
bool aliveChanged = base.CheckChildrenLife();
|
||||
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
|
||||
return aliveChanged;
|
||||
}
|
||||
}
|
||||
}
|
@ -14,24 +14,15 @@ using osu.Framework.Graphics.Performance;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Pooling;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public class HitObjectContainer : CompositeDrawable, IHitObjectContainer
|
||||
public class HitObjectContainer : PooledDrawableWithLifetimeContainer<HitObjectLifetimeEntry, DrawableHitObject>, IHitObjectContainer
|
||||
{
|
||||
/// <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>
|
||||
public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value));
|
||||
|
||||
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||
|
||||
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.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.
|
||||
@ -59,34 +50,16 @@ namespace osu.Game.Rulesets.UI
|
||||
/// </remarks>
|
||||
internal event Action<HitObject> HitObjectUsageFinished;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time prior to the current time within which <see cref="HitObject"/>s should be considered alive.
|
||||
/// </summary>
|
||||
internal double PastLifetimeExtension { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time after the current time within which <see cref="HitObject"/>s should be considered alive.
|
||||
/// </summary>
|
||||
internal double FutureLifetimeExtension { get; set; }
|
||||
|
||||
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
|
||||
|
||||
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> aliveDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
||||
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> nonPooledDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
||||
|
||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||
private readonly HashSet<HitObjectLifetimeEntry> allEntries = new HashSet<HitObjectLifetimeEntry>();
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
||||
|
||||
public HitObjectContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
||||
lifetimeManager.EntryBecameDead += entryBecameDead;
|
||||
lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
|
||||
}
|
||||
|
||||
protected override void LoadAsyncComplete()
|
||||
@ -99,37 +72,29 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
#region Pooling support
|
||||
|
||||
public void Add(HitObjectLifetimeEntry entry)
|
||||
public override bool Remove(HitObjectLifetimeEntry entry)
|
||||
{
|
||||
allEntries.Add(entry);
|
||||
lifetimeManager.AddEntry(entry);
|
||||
}
|
||||
|
||||
public bool Remove(HitObjectLifetimeEntry entry)
|
||||
{
|
||||
if (!lifetimeManager.RemoveEntry(entry)) return false;
|
||||
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);
|
||||
|
||||
allEntries.Remove(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void entryBecameAlive(LifetimeEntry lifetimeEntry)
|
||||
protected sealed override DrawableHitObject GetDrawable(HitObjectLifetimeEntry entry)
|
||||
{
|
||||
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
||||
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
||||
if (nonPooledDrawableMap.TryGetValue(entry, out var drawable))
|
||||
return drawable;
|
||||
|
||||
bool isPooled = !nonPooledDrawableMap.TryGetValue(entry, out var drawable);
|
||||
drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
||||
if (drawable == null)
|
||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||
return pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null) ??
|
||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||
}
|
||||
|
||||
aliveDrawableMap[entry] = drawable;
|
||||
|
||||
if (isPooled)
|
||||
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
||||
{
|
||||
if (!nonPooledDrawableMap.ContainsKey(entry))
|
||||
{
|
||||
addDrawable(drawable);
|
||||
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
||||
@ -138,18 +103,11 @@ namespace osu.Game.Rulesets.UI
|
||||
OnAdd(drawable);
|
||||
}
|
||||
|
||||
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
||||
protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
|
||||
{
|
||||
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
||||
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
||||
|
||||
var drawable = aliveDrawableMap[entry];
|
||||
bool isPooled = !nonPooledDrawableMap.ContainsKey(entry);
|
||||
|
||||
drawable.OnKilled();
|
||||
aliveDrawableMap.Remove(entry);
|
||||
|
||||
if (isPooled)
|
||||
if (!nonPooledDrawableMap.ContainsKey(entry))
|
||||
{
|
||||
removeDrawable(drawable);
|
||||
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
||||
@ -201,9 +159,9 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
|
||||
|
||||
private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
|
||||
protected override void OnEntryCrossedBoundary(HitObjectLifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
|
||||
{
|
||||
if (nonPooledDrawableMap.TryGetValue((HitObjectLifetimeEntry)entry, out var drawable))
|
||||
if (nonPooledDrawableMap.TryGetValue(entry, out var drawable))
|
||||
OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction));
|
||||
}
|
||||
|
||||
@ -228,22 +186,6 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
lifetimeManager.ClearEntries();
|
||||
foreach (var drawable in nonPooledDrawableMap.Values)
|
||||
removeDrawable(drawable);
|
||||
nonPooledDrawableMap.Clear();
|
||||
Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && aliveDrawableMap.Count == 0, "All hit objects should have been removed");
|
||||
}
|
||||
|
||||
protected override bool CheckChildrenLife()
|
||||
{
|
||||
bool aliveChanged = base.CheckChildrenLife();
|
||||
aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
|
||||
return aliveChanged;
|
||||
}
|
||||
|
||||
private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r);
|
||||
private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user