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 osu.Framework.Extensions.EnumExtensions;
6using osu.Framework.Graphics.Containers;
7using osu.Framework.Layout;
8using osu.Framework.Threading;
9
10namespace osu.Framework.Graphics.Pooling
11{
12 public class PoolableDrawable : CompositeDrawable
13 {
14 public override bool DisposeOnDeathRemoval => pool == null && base.DisposeOnDeathRemoval;
15
16 /// <summary>
17 /// Whether this pooled drawable is currently in use.
18 /// </summary>
19 public bool IsInUse { get; private set; }
20
21 /// <summary>
22 /// Whether this drawable is currently managed by a pool.
23 /// </summary>
24 public bool IsInPool => pool != null;
25
26 private IDrawablePool pool;
27
28 /// <summary>
29 /// A flag to keep the drawable present to guarantee the prepare call can be performed as a scheduled call.
30 /// </summary>
31 private bool waitingForPrepare;
32
33 private ScheduledDelegate scheduledPrepare;
34
35 public override bool IsPresent => waitingForPrepare || base.IsPresent;
36
37 protected override void LoadComplete()
38 {
39 base.LoadComplete();
40
41 // this allows a PooledDrawable to still function outside of a pool.
42 if (!IsInPool)
43 Assign();
44 }
45
46 /// <summary>
47 /// Return this drawable to its pool manually. Note that this is not required if the drawable is using lifetime cleanup.
48 /// </summary>
49 public void Return()
50 {
51 if (!IsInUse)
52 throw new InvalidOperationException($"This {nameof(PoolableDrawable)} was already returned");
53
54 IsInUse = false;
55
56 FreeAfterUse();
57
58 // intentionally don't throw if a pool was not associated or otherwise.
59 // supports use of PooledDrawables outside of a pooled scenario without special handling.
60 pool?.Return(this);
61 waitingForPrepare = false;
62 }
63
64 /// <summary>
65 /// Perform any initialisation on new usage of this drawable.
66 /// This is scheduled to the first update frame and may not be run if this is never reached.
67 /// </summary>
68 protected virtual void PrepareForUse()
69 {
70 }
71
72 /// <summary>
73 /// Perform any clean-up required before returning this drawable to a pool.
74 /// This is called regardless of whether <see cref="PrepareForUse"/> was executed.
75 /// </summary>
76 protected virtual void FreeAfterUse()
77 {
78 }
79
80 /// <summary>
81 /// Set the associated pool this drawable is currently associated with.
82 /// </summary>
83 /// <param name="pool">The target pool, or null to disassociate from all pools (and cause the drawable to be disposed as if it was not pooled). </param>
84 /// <exception cref="InvalidOperationException">Thrown if this drawable is still in use, or is already in another pool.</exception>
85 internal void SetPool(IDrawablePool pool)
86 {
87 if (IsInUse)
88 throw new InvalidOperationException($"This {nameof(PoolableDrawable)} is still in use");
89
90 if (pool != null && this.pool != null)
91 throw new InvalidOperationException($"This {nameof(PoolableDrawable)} is already in a pool");
92
93 this.pool = pool;
94 }
95
96 /// <summary>
97 /// Assign this drawable to a consumer.
98 /// </summary>
99 /// <exception cref="InvalidOperationException">Thrown if this drawable is still in use.</exception>
100 internal void Assign()
101 {
102 if (IsInUse)
103 throw new InvalidOperationException($"This {nameof(PoolableDrawable)} is already in use");
104
105 IsInUse = true;
106
107 waitingForPrepare = true;
108
109 // prepare call is scheduled as it may contain user code dependent on the clock being updated.
110 // must use Scheduler.Add, not Schedule as we may have the wrong clock at this point in load.
111 scheduledPrepare?.Cancel();
112 scheduledPrepare = Scheduler.Add(() =>
113 {
114 PrepareForUse();
115 waitingForPrepare = false;
116 });
117 }
118
119 protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
120 {
121 if (source != InvalidationSource.Child && invalidation.HasFlagFast(Invalidation.Parent))
122 {
123 if (IsInUse && Parent == null)
124 Return();
125 }
126
127 return base.OnInvalidate(invalidation, source);
128 }
129 }
130}