A game framework written with osu! in mind.
at master 130 lines 4.8 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 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}