// 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; 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; } public override double LifetimeStart { get => base.LifetimeStart; set { if (Entry == null && LifetimeStart != value) throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); if (Entry == null) return; // Cannot write it as `base.LifetimeStart = Entry.LifetimeStart = value` because the change may be blocked (when `HitObjectLifetimeEntry.KeepAlive` is true). Entry.LifetimeStart = value; base.LifetimeStart = Entry.LifetimeStart = value; } } public override double LifetimeEnd { get => base.LifetimeEnd; set { if (Entry == null && LifetimeEnd != value) throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime)} when entry is not set"); if (Entry == null) return; Entry.LifetimeEnd = value; base.LifetimeEnd = Entry.LifetimeEnd; } } 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; entry.LifetimeChanged += setLifetimeFromEntry; setLifetimeFromEntry(entry); 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.LifetimeChanged -= setLifetimeFromEntry; Entry = null; base.LifetimeStart = double.MinValue; base.LifetimeEnd = double.MaxValue; HasEntryApplied = false; } private void setLifetimeFromEntry(LifetimeEntry entry) { Debug.Assert(entry == Entry); base.LifetimeStart = entry.LifetimeStart; base.LifetimeEnd = entry.LifetimeEnd; } } }