A game framework written with osu! in mind.
at master 174 lines 7.0 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6using System.Diagnostics; 7using osu.Framework.Graphics.Performance; 8 9namespace osu.Framework.Graphics.Containers 10{ 11 /// <summary> 12 /// This container is optimized for when number of alive children is significantly smaller than number of all children. 13 /// Specifically, the time complexity of <see cref="Drawable.Update"/> should be closer to 14 /// O(number of alive children + 1) rather than O(number of all children + 1) for typical frames. 15 /// </summary> 16 /// <remarks> 17 /// This container assumes child's <see cref="Drawable.Clock"/> is the same as ours and 18 /// <see cref="Drawable.ShouldBeAlive"/> is not overrided. 19 /// Also, <see cref="Drawable.RemoveWhenNotAlive"/> should be false. 20 /// </remarks> 21 public class LifetimeManagementContainer : CompositeDrawable 22 { 23 private readonly LifetimeEntryManager manager = new LifetimeEntryManager(); 24 private readonly Dictionary<Drawable, DrawableLifetimeEntry> drawableMap = new Dictionary<Drawable, DrawableLifetimeEntry>(); 25 26 /// <summary> 27 /// List of drawables that do not have their lifetime managed by us, but still need to have their aliveness processed once. 28 /// </summary> 29 private readonly List<Drawable> unmanagedDrawablesToProcess = new List<Drawable>(); 30 31 public LifetimeManagementContainer() 32 { 33 manager.EntryBecameAlive += entryBecameAlive; 34 manager.EntryBecameDead += entryBecameDead; 35 manager.EntryCrossedBoundary += entryCrossedBoundary; 36 } 37 38 protected internal override void AddInternal(Drawable drawable) => AddInternal(drawable, true); 39 40 /// <summary> 41 /// Adds a <see cref="Drawable"/> to this <see cref="LifetimeManagementContainer"/>. 42 /// </summary> 43 /// <param name="drawable">The <see cref="Drawable"/> to add.</param> 44 /// <param name="withManagedLifetime">Whether the lifetime of <paramref name="drawable"/> should be managed by this <see cref="LifetimeManagementContainer"/>.</param> 45 protected internal void AddInternal(Drawable drawable, bool withManagedLifetime) 46 { 47 Trace.Assert(!drawable.RemoveWhenNotAlive, $"{nameof(RemoveWhenNotAlive)} is not supported for {nameof(LifetimeManagementContainer)}"); 48 49 base.AddInternal(drawable); 50 51 if (withManagedLifetime) 52 manager.AddEntry(drawableMap[drawable] = new DrawableLifetimeEntry(drawable)); 53 else if (drawable.LoadState >= LoadState.Ready) 54 MakeChildAlive(drawable); 55 else 56 unmanagedDrawablesToProcess.Add(drawable); 57 } 58 59 protected internal override bool RemoveInternal(Drawable drawable) 60 { 61 unmanagedDrawablesToProcess.Remove(drawable); 62 63 if (!drawableMap.TryGetValue(drawable, out var entry)) 64 return base.RemoveInternal(drawable); 65 66 manager.RemoveEntry(entry); 67 drawableMap.Remove(drawable); 68 69 entry.Dispose(); 70 71 return base.RemoveInternal(drawable); 72 } 73 74 protected internal override void ClearInternal(bool disposeChildren = true) 75 { 76 manager.ClearEntries(); 77 unmanagedDrawablesToProcess.Clear(); 78 79 foreach (var (_, entry) in drawableMap) 80 entry.Dispose(); 81 drawableMap.Clear(); 82 83 base.ClearInternal(disposeChildren); 84 } 85 86 protected override bool CheckChildrenLife() 87 { 88 bool aliveChanged = unmanagedDrawablesToProcess.Count > 0; 89 90 foreach (var d in unmanagedDrawablesToProcess) 91 MakeChildAlive(d); 92 unmanagedDrawablesToProcess.Clear(); 93 94 aliveChanged |= manager.Update(Time.Current); 95 96 return aliveChanged; 97 } 98 99 private void entryBecameAlive(LifetimeEntry entry) => MakeChildAlive(((DrawableLifetimeEntry)entry).Drawable); 100 101 private void entryBecameDead(LifetimeEntry entry) 102 { 103 bool removed = MakeChildDead(((DrawableLifetimeEntry)entry).Drawable); 104 Trace.Assert(!removed, $"{nameof(RemoveWhenNotAlive)} is not supported for children of {nameof(LifetimeManagementContainer)}"); 105 } 106 107 private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) 108 => OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(((DrawableLifetimeEntry)entry).Drawable, kind, direction)); 109 110 /// <summary> 111 /// Called when the clock is crossed child lifetime boundary. 112 /// If child's lifetime is changed during this callback and that causes additional crossings, 113 /// those events are queued and this method will be called later (on the same frame). 114 /// </summary> 115 protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) 116 { 117 } 118 119 private class DrawableLifetimeEntry : LifetimeEntry, IDisposable 120 { 121 public readonly Drawable Drawable; 122 123 public DrawableLifetimeEntry(Drawable drawable) 124 { 125 Drawable = drawable; 126 127 Drawable.LifetimeChanged += drawableLifetimeChanged; 128 drawableLifetimeChanged(drawable); 129 } 130 131 private void drawableLifetimeChanged(Drawable drawable) 132 { 133 LifetimeStart = drawable.LifetimeStart; 134 LifetimeEnd = drawable.LifetimeEnd; 135 } 136 137 public void Dispose() 138 { 139 if (Drawable != null) 140 Drawable.LifetimeChanged -= drawableLifetimeChanged; 141 } 142 } 143 } 144 145 /// <summary> 146 /// Represents that the clock is crossed <see cref="LifetimeManagementContainer"/>'s child lifetime boundary i.e. <see cref="Drawable.LifetimeStart"/> or <see cref="Drawable.LifetimeEnd"/>, 147 /// </summary> 148 public readonly struct LifetimeBoundaryCrossedEvent 149 { 150 /// <summary> 151 /// The drawable. 152 /// </summary> 153 public readonly Drawable Child; 154 155 /// <summary> 156 /// Which lifetime boundary is crossed. 157 /// </summary> 158 public readonly LifetimeBoundaryKind Kind; 159 160 /// <summary> 161 /// The direction of the crossing. 162 /// </summary> 163 public readonly LifetimeBoundaryCrossingDirection Direction; 164 165 public LifetimeBoundaryCrossedEvent(Drawable child, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction) 166 { 167 Child = child; 168 Kind = kind; 169 Direction = direction; 170 } 171 172 public override string ToString() => $"({Child.ChildID}, {Kind}, {Direction})"; 173 } 174}