A game framework written with osu! in mind.
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}