// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Graphics.Performance; namespace osu.Framework.Graphics.Containers { /// /// This container is optimized for when number of alive children is significantly smaller than number of all children. /// Specifically, the time complexity of should be closer to /// O(number of alive children + 1) rather than O(number of all children + 1) for typical frames. /// /// /// This container assumes child's is the same as ours and /// is not overrided. /// Also, should be false. /// public class LifetimeManagementContainer : CompositeDrawable { private readonly LifetimeEntryManager manager = new LifetimeEntryManager(); private readonly Dictionary drawableMap = new Dictionary(); /// /// List of drawables that do not have their lifetime managed by us, but still need to have their aliveness processed once. /// private readonly List unmanagedDrawablesToProcess = new List(); public LifetimeManagementContainer() { manager.EntryBecameAlive += entryBecameAlive; manager.EntryBecameDead += entryBecameDead; manager.EntryCrossedBoundary += entryCrossedBoundary; } protected internal override void AddInternal(Drawable drawable) => AddInternal(drawable, true); /// /// Adds a to this . /// /// The to add. /// Whether the lifetime of should be managed by this . protected internal void AddInternal(Drawable drawable, bool withManagedLifetime) { Trace.Assert(!drawable.RemoveWhenNotAlive, $"{nameof(RemoveWhenNotAlive)} is not supported for {nameof(LifetimeManagementContainer)}"); base.AddInternal(drawable); if (withManagedLifetime) manager.AddEntry(drawableMap[drawable] = new DrawableLifetimeEntry(drawable)); else if (drawable.LoadState >= LoadState.Ready) MakeChildAlive(drawable); else unmanagedDrawablesToProcess.Add(drawable); } protected internal override bool RemoveInternal(Drawable drawable) { unmanagedDrawablesToProcess.Remove(drawable); if (!drawableMap.TryGetValue(drawable, out var entry)) return base.RemoveInternal(drawable); manager.RemoveEntry(entry); drawableMap.Remove(drawable); entry.Dispose(); return base.RemoveInternal(drawable); } protected internal override void ClearInternal(bool disposeChildren = true) { manager.ClearEntries(); unmanagedDrawablesToProcess.Clear(); foreach (var (_, entry) in drawableMap) entry.Dispose(); drawableMap.Clear(); base.ClearInternal(disposeChildren); } protected override bool CheckChildrenLife() { bool aliveChanged = unmanagedDrawablesToProcess.Count > 0; foreach (var d in unmanagedDrawablesToProcess) MakeChildAlive(d); unmanagedDrawablesToProcess.Clear(); aliveChanged |= manager.Update(Time.Current); return aliveChanged; } private void entryBecameAlive(LifetimeEntry entry) => MakeChildAlive(((DrawableLifetimeEntry)entry).Drawable); private void entryBecameDead(LifetimeEntry entry) { bool removed = MakeChildDead(((DrawableLifetimeEntry)entry).Drawable); Trace.Assert(!removed, $"{nameof(RemoveWhenNotAlive)} is not supported for children of {nameof(LifetimeManagementContainer)}"); } private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) => OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(((DrawableLifetimeEntry)entry).Drawable, kind, direction)); /// /// Called when the clock is crossed child lifetime boundary. /// If child's lifetime is changed during this callback and that causes additional crossings, /// those events are queued and this method will be called later (on the same frame). /// protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) { } private class DrawableLifetimeEntry : LifetimeEntry, IDisposable { public readonly Drawable Drawable; public DrawableLifetimeEntry(Drawable drawable) { Drawable = drawable; Drawable.LifetimeChanged += drawableLifetimeChanged; drawableLifetimeChanged(drawable); } private void drawableLifetimeChanged(Drawable drawable) { LifetimeStart = drawable.LifetimeStart; LifetimeEnd = drawable.LifetimeEnd; } public void Dispose() { if (Drawable != null) Drawable.LifetimeChanged -= drawableLifetimeChanged; } } } /// /// Represents that the clock is crossed 's child lifetime boundary i.e. or , /// public readonly struct LifetimeBoundaryCrossedEvent { /// /// The drawable. /// public readonly Drawable Child; /// /// Which lifetime boundary is crossed. /// public readonly LifetimeBoundaryKind Kind; /// /// The direction of the crossing. /// public readonly LifetimeBoundaryCrossingDirection Direction; public LifetimeBoundaryCrossedEvent(Drawable child, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) { Child = child; Kind = kind; Direction = direction; } public override string ToString() => $"({Child.ChildID}, {Kind}, {Direction})"; } }