// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Extensions.ListExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; using osu.Framework.Lists; namespace osu.Game.Rulesets.Objects.Pooling { /// /// A container of s dynamically added/removed by model 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. /// /// The type of entries managed by this container. /// The type of drawables corresponding to the entries. public abstract partial class PooledDrawableWithLifetimeContainer : CompositeDrawable where TEntry : LifetimeEntry where TDrawable : Drawable { /// /// All entries added to this container, including dead entries. /// /// /// The enumeration order is undefined. /// public IEnumerable Entries => allEntries; /// /// All alive entries and drawables corresponding to the entries. /// /// /// The enumeration order is undefined. /// public readonly SlimReadOnlyDictionaryWrapper AliveEntries; /// /// Whether to remove an entry when clock goes backward and crossed its . /// Used when entries are dynamically added at its to prevent duplicated entries. /// protected virtual bool RemoveRewoundEntry => false; /// /// The amount of time prior to the current time within which entries should be considered alive. /// internal double PastLifetimeExtension { get; set; } /// /// The amount of time after the current time within which entries should be considered alive. /// internal double FutureLifetimeExtension { get; set; } private readonly Dictionary aliveDrawableMap = new Dictionary(); private readonly HashSet allEntries = new HashSet(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); protected PooledDrawableWithLifetimeContainer() { lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; AliveEntries = aliveDrawableMap.AsSlimReadOnly(); } /// /// Add a to be managed by this container. /// /// /// The aliveness of the entry is not updated until . /// public virtual void Add(TEntry entry) { allEntries.Add(entry); lifetimeManager.AddEntry(entry); } /// /// Remove a from this container. /// /// /// If the entry was alive, the corresponding drawable is removed. /// /// Whether the entry was in this container. public virtual bool Remove(TEntry entry) { if (!lifetimeManager.RemoveEntry(entry)) return false; allEntries.Remove(entry); return true; } /// /// Initialize new corresponding . /// /// The corresponding to the entry. 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); } /// /// Add a corresponding to to this container. /// /// /// Invoked when the entry became alive and a is obtained by . /// 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); } /// /// Remove a corresponding to from this container. /// /// /// Invoked when the entry became dead. /// protected virtual void RemoveDrawable(TEntry entry, TDrawable drawable) => RemoveInternal(drawable, false); private void entryCrossedBoundary(LifetimeEntry lifetimeEntry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { if (RemoveRewoundEntry && kind == LifetimeBoundaryKind.Start && direction == LifetimeBoundaryCrossingDirection.Backward) Remove((TEntry)lifetimeEntry); } /// /// Remove all s. /// public void Clear() { foreach (var entry in Entries.ToArray()) Remove(entry); Debug.Assert(aliveDrawableMap.Count == 0); } protected override bool CheckChildrenLife() { if (!IsPresent) return false; bool aliveChanged = base.CheckChildrenLife(); aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension); return aliveChanged; } } }