// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable enable using System.Diagnostics; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Pooling; namespace osu.Game.Rulesets.Objects.Pooling { /// /// A that is controlled by to implement drawable pooling and replay rewinding. /// /// The type storing state and controlling this drawable. public abstract class PoolableDrawableWithLifetime : PoolableDrawable where TEntry : LifetimeEntry { /// /// The entry holding essential state of this . /// public TEntry? Entry { get; private set; } /// /// Whether is applied to this . /// When an initial entry is specified in the constructor, is set but not applied until loading is completed. /// protected bool HasEntryApplied { get; private set; } // Drawable's lifetime gets out of sync with entry's lifetime if entry's lifetime is modified. // We cannot delegate getter to `Entry.LifetimeStart` because it is incompatible with `LifetimeManagementContainer` due to how lifetime change is detected. public override double LifetimeStart { get => base.LifetimeStart; set { base.LifetimeStart = value; if (Entry != null) Entry.LifetimeStart = value; } } public override double LifetimeEnd { get => base.LifetimeEnd; set { base.LifetimeEnd = value; if (Entry != null) Entry.LifetimeEnd = value; } } public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; protected PoolableDrawableWithLifetime(TEntry? initialEntry = null) { Entry = initialEntry; } protected override void LoadAsyncComplete() { base.LoadAsyncComplete(); // Apply the initial entry given in the constructor. if (Entry != null && !HasEntryApplied) Apply(Entry); } /// /// Applies a new entry to be represented by this drawable. /// If there is an existing entry applied, the entry will be replaced. /// public void Apply(TEntry entry) { if (HasEntryApplied) free(); Entry = entry; base.LifetimeStart = entry.LifetimeStart; base.LifetimeEnd = entry.LifetimeEnd; OnApply(entry); HasEntryApplied = true; } protected sealed override void FreeAfterUse() { base.FreeAfterUse(); // We preserve the existing entry in case we want to move a non-pooled drawable between different parent drawables. if (HasEntryApplied && IsInPool) free(); } /// /// Invoked to apply a new entry to this drawable. /// protected virtual void OnApply(TEntry entry) { } /// /// Invoked to revert application of the entry to this drawable. /// protected virtual void OnFree(TEntry entry) { } private void free() { Debug.Assert(Entry != null && HasEntryApplied); OnFree(Entry); Entry = null; base.LifetimeStart = double.MinValue; base.LifetimeEnd = double.MaxValue; HasEntryApplied = false; } } }