| | | 1 | | #nullable enable |
| | | 2 | | |
| | | 3 | | using System; |
| | | 4 | | using System.Collections.Generic; |
| | | 5 | | using System.ComponentModel; |
| | | 6 | | using System.Diagnostics.CodeAnalysis; |
| | | 7 | | using Hertzole.ScriptableValues.Helpers; |
| | | 8 | | using UnityEngine; |
| | | 9 | | using UnityEngine.Assertions; |
| | | 10 | | #if SCRIPTABLE_VALUES_PROPERTIES |
| | | 11 | | using Unity.Properties; |
| | | 12 | | #endif |
| | | 13 | | |
| | | 14 | | namespace Hertzole.ScriptableValues |
| | | 15 | | { |
| | | 16 | | /// <summary> |
| | | 17 | | /// Base class for all <see cref="ScriptablePool{T}" />. |
| | | 18 | | /// </summary> |
| | | 19 | | /// <remarks>You should probably inherit from <see cref="ScriptablePool{T}" /> instead.</remarks> |
| | | 20 | | #if UNITY_EDITOR |
| | | 21 | | [HelpURL(Documentation.SCRIPTABLE_POOL_URL)] |
| | | 22 | | #endif |
| | | 23 | | public abstract class ScriptablePool : RuntimeScriptableObject |
| | | 24 | | { |
| | 12 | 25 | | public static readonly PropertyChangingEventArgs countAllChangingEventArgs = new PropertyChangingEventArgs(nameo |
| | 12 | 26 | | public static readonly PropertyChangedEventArgs countAllChangedEventArgs = new PropertyChangedEventArgs(nameof(C |
| | | 27 | | |
| | 12 | 28 | | public static readonly PropertyChangingEventArgs countActiveChangingEventArgs = new PropertyChangingEventArgs(na |
| | 12 | 29 | | public static readonly PropertyChangedEventArgs countActiveChangedEventArgs = new PropertyChangedEventArgs(nameo |
| | | 30 | | |
| | 12 | 31 | | public static readonly PropertyChangingEventArgs countInactiveChangingEventArgs = new PropertyChangingEventArgs( |
| | 12 | 32 | | public static readonly PropertyChangedEventArgs countInactiveChangedEventArgs = new PropertyChangedEventArgs(nam |
| | | 33 | | |
| | | 34 | | /// <summary> |
| | | 35 | | /// How many total objects that the pool is keeping track of. |
| | | 36 | | /// </summary> |
| | | 37 | | #if SCRIPTABLE_VALUES_PROPERTIES |
| | | 38 | | #if SCRIPTABLE_VALUES_PROPERTIES_SUPPORTS_READONLY |
| | | 39 | | [CreateProperty(ReadOnly = true)] |
| | | 40 | | #else |
| | | 41 | | [CreateProperty] |
| | | 42 | | #endif // SCRIPTABLE_VALUES_PROPERTIES_SUPPORTS_READONLY |
| | | 43 | | #endif // SCRIPTABLE_VALUES_PROPERTIES |
| | | 44 | | public abstract int CountAll { get; protected set; } |
| | | 45 | | |
| | | 46 | | /// <summary> |
| | | 47 | | /// How many objects that are currently active. |
| | | 48 | | /// </summary> |
| | | 49 | | #if SCRIPTABLE_VALUES_PROPERTIES |
| | | 50 | | #if SCRIPTABLE_VALUES_PROPERTIES_SUPPORTS_READONLY |
| | | 51 | | [CreateProperty(ReadOnly = true)] |
| | | 52 | | #else |
| | | 53 | | [CreateProperty] |
| | | 54 | | #endif // SCRIPTABLE_VALUES_PROPERTIES_SUPPORTS_READONLY |
| | | 55 | | #endif // SCRIPTABLE_VALUES_PROPERTIES |
| | | 56 | | public abstract int CountActive { get; protected set; } |
| | | 57 | | |
| | | 58 | | /// <summary> |
| | | 59 | | /// How many objects that are currently inactive. |
| | | 60 | | /// </summary> |
| | | 61 | | #if SCRIPTABLE_VALUES_PROPERTIES |
| | | 62 | | #if SCRIPTABLE_VALUES_PROPERTIES_SUPPORTS_READONLY |
| | | 63 | | [CreateProperty(ReadOnly = true)] |
| | | 64 | | #else |
| | | 65 | | [CreateProperty] |
| | | 66 | | #endif // SCRIPTABLE_VALUES_PROPERTIES_SUPPORTS_READONLY |
| | | 67 | | #endif // SCRIPTABLE_VALUES_PROPERTIES |
| | | 68 | | public abstract int CountInactive { get; protected set; } |
| | | 69 | | } |
| | | 70 | | |
| | | 71 | | /// <summary> |
| | | 72 | | /// A <see cref="UnityEngine.ScriptableObject" /> that holds a pool of values. |
| | | 73 | | /// </summary> |
| | | 74 | | /// <typeparam name="T">The type of to pool. Must be a class.</typeparam> |
| | | 75 | | public abstract partial class ScriptablePool<T> : ScriptablePool, IPoolCallbacks<T> where T : class |
| | | 76 | | { |
| | | 77 | | internal readonly List<T> activeObjects = new List<T>(); |
| | | 78 | | internal readonly Stack<T> pool = new Stack<T>(); |
| | | 79 | | |
| | | 80 | | private int countAll; |
| | | 81 | | private int countActive; |
| | | 82 | | private int countInactive; |
| | | 83 | | |
| | | 84 | | /// <inheritdoc /> |
| | | 85 | | public sealed override int CountAll |
| | | 86 | | { |
| | | 87 | | get { return countAll; } |
| | | 88 | | protected set |
| | | 89 | | { |
| | | 90 | | Assert.AreEqual(activeObjects.Count + pool.Count, value, |
| | | 91 | | $"CountAll should be equal to the sum of active and inactive objects but was {value} instead."); |
| | | 92 | | |
| | | 93 | | SetField(ref countAll, value, countAllChangingEventArgs, countAllChangedEventArgs); |
| | | 94 | | } |
| | | 95 | | } |
| | | 96 | | |
| | | 97 | | /// <inheritdoc /> |
| | | 98 | | public sealed override int CountActive |
| | | 99 | | { |
| | | 100 | | get { return countActive; } |
| | | 101 | | protected set |
| | | 102 | | { |
| | | 103 | | Assert.AreEqual(activeObjects.Count, value, $"CountActive should be equal to the number of active object |
| | | 104 | | SetField(ref countActive, value, countActiveChangingEventArgs, countActiveChangedEventArgs); |
| | | 105 | | } |
| | | 106 | | } |
| | | 107 | | |
| | | 108 | | /// <inheritdoc /> |
| | | 109 | | public sealed override int CountInactive |
| | | 110 | | { |
| | | 111 | | get { return countInactive; } |
| | | 112 | | protected set |
| | | 113 | | { |
| | | 114 | | Assert.AreEqual(pool.Count, value, $"CountInactive should be equal to the number of inactive objects but |
| | | 115 | | SetField(ref countInactive, value, countInactiveChangingEventArgs, countInactiveChangedEventArgs); |
| | | 116 | | } |
| | | 117 | | } |
| | | 118 | | |
| | | 119 | | #if UNITY_EDITOR |
| | | 120 | | internal const int ORDER = ScriptableList<object>.ORDER + 50; |
| | | 121 | | #endif |
| | | 122 | | |
| | | 123 | | public event PoolChangedArgs<T>? OnPoolChanged; |
| | | 124 | | |
| | | 125 | | /// <summary> |
| | | 126 | | /// Gets an object from the pool. |
| | | 127 | | /// </summary> |
| | | 128 | | /// <returns>The newly acquired object from the pool.</returns> |
| | | 129 | | public T Get() |
| | | 130 | | { |
| | | 131 | | T? item = null; |
| | | 132 | | // Objects may be destroyed when switching scenes, so we need to check if they are null. |
| | | 133 | | // If the returned object is null, just keep going until we find one that isn't. |
| | | 134 | | // If it's still null, we'll create a new one. |
| | | 135 | | while (EqualityHelper.IsNull(item)) |
| | | 136 | | { |
| | | 137 | | if (pool.Count > 0) |
| | | 138 | | { |
| | | 139 | | item = pool.Pop(); |
| | | 140 | | } |
| | | 141 | | else |
| | | 142 | | { |
| | | 143 | | item = CreateObject(); |
| | | 144 | | OnPoolChanged?.Invoke(PoolAction.CreatedObject, item); |
| | | 145 | | } |
| | | 146 | | } |
| | | 147 | | |
| | | 148 | | Assert.IsNotNull(item); |
| | | 149 | | |
| | | 150 | | activeObjects.Add(item); |
| | | 151 | | |
| | | 152 | | OnPoolChanged?.Invoke(PoolAction.RentedObject, item); |
| | | 153 | | OnGetInternal(item); |
| | | 154 | | |
| | | 155 | | UpdateCounts(); |
| | | 156 | | |
| | | 157 | | AddStackTrace(); |
| | | 158 | | |
| | | 159 | | return item; |
| | | 160 | | } |
| | | 161 | | |
| | | 162 | | /// <summary> |
| | | 163 | | /// Returns an object to the pool. |
| | | 164 | | /// </summary> |
| | | 165 | | /// <param name="item">The item to return.</param> |
| | | 166 | | public void Release(T item) |
| | | 167 | | { |
| | | 168 | | Guard.IsNotNull(item, nameof(item)); |
| | | 169 | | |
| | | 170 | | activeObjects.Remove(item); |
| | | 171 | | |
| | | 172 | | OnReturnInternal(item); |
| | | 173 | | OnPoolChanged?.Invoke(PoolAction.ReleasedObject, item); |
| | | 174 | | |
| | | 175 | | pool.Push(item); |
| | | 176 | | |
| | | 177 | | UpdateCounts(); |
| | | 178 | | |
| | | 179 | | AddStackTrace(); |
| | | 180 | | } |
| | | 181 | | |
| | | 182 | | /// <summary> |
| | | 183 | | /// Clears the pool. All objects, both active and inactive, will be destroyed. |
| | | 184 | | /// </summary> |
| | | 185 | | public void Clear() |
| | | 186 | | { |
| | | 187 | | // Destroy all active objects. |
| | | 188 | | // Go in reverse order so that we don't mess up the list. |
| | | 189 | | for (int i = activeObjects.Count - 1; i >= 0; i--) |
| | | 190 | | { |
| | | 191 | | T item = activeObjects[i]; |
| | | 192 | | |
| | | 193 | | DestroyObjectInternal(item); |
| | | 194 | | activeObjects.RemoveAt(i); |
| | | 195 | | } |
| | | 196 | | |
| | | 197 | | // Destroy all inactive objects. |
| | | 198 | | while (pool.TryPop(out T item)) |
| | | 199 | | { |
| | | 200 | | DestroyObjectInternal(item); |
| | | 201 | | } |
| | | 202 | | |
| | | 203 | | UpdateCounts(); |
| | | 204 | | |
| | | 205 | | // Make sure that the pool is empty. |
| | | 206 | | Assert.AreEqual(0, CountAll, $"CountAll should be 0 after clearing the pool but was {CountAll}."); |
| | | 207 | | |
| | | 208 | | AddStackTrace(); |
| | | 209 | | } |
| | | 210 | | |
| | | 211 | | private void UpdateCounts() |
| | | 212 | | { |
| | | 213 | | CountAll = activeObjects.Count + pool.Count; |
| | | 214 | | CountActive = activeObjects.Count; |
| | | 215 | | CountInactive = pool.Count; |
| | | 216 | | } |
| | | 217 | | |
| | | 218 | | /// <summary> |
| | | 219 | | /// Internal method for getting objects. |
| | | 220 | | /// </summary> |
| | | 221 | | /// <param name="item">The object that was just received from the pool.</param> |
| | | 222 | | internal virtual void OnGetInternal(T item) |
| | | 223 | | { |
| | | 224 | | // If the item implements IPoolable, call OnUnpooled. |
| | | 225 | | if (item is IPoolable poolable) |
| | | 226 | | { |
| | | 227 | | poolable.OnUnpooled(); |
| | | 228 | | } |
| | | 229 | | |
| | | 230 | | // Call the OnGet method. |
| | | 231 | | OnGet(item); |
| | | 232 | | } |
| | | 233 | | |
| | | 234 | | internal virtual void OnReturnInternal(T item) |
| | | 235 | | { |
| | | 236 | | // If the item implements IPoolable, call OmPooled. |
| | | 237 | | if (item is IPoolable poolable) |
| | | 238 | | { |
| | | 239 | | poolable.OnPooled(); |
| | | 240 | | } |
| | | 241 | | |
| | | 242 | | // Call the OnReturn method. |
| | | 243 | | OnReturn(item); |
| | | 244 | | } |
| | | 245 | | |
| | | 246 | | internal virtual void DestroyObjectInternal(T item) |
| | | 247 | | { |
| | | 248 | | OnPoolChanged?.Invoke(PoolAction.DestroyedObject, item); |
| | | 249 | | DestroyObject(item); |
| | | 250 | | } |
| | | 251 | | |
| | | 252 | | /// <summary> |
| | | 253 | | /// Called when a new object needs to be created. |
| | | 254 | | /// </summary> |
| | | 255 | | /// <returns></returns> |
| | | 256 | | protected abstract T CreateObject(); |
| | | 257 | | |
| | | 258 | | /// <summary> |
| | | 259 | | /// Called when an object needs to be destroyed. |
| | | 260 | | /// </summary> |
| | | 261 | | /// <param name="item">The object to be destroyed.</param> |
| | | 262 | | protected abstract void DestroyObject(T item); |
| | | 263 | | |
| | | 264 | | /// <summary> |
| | | 265 | | /// Called when an object is retrieved from the pool. |
| | | 266 | | /// </summary> |
| | | 267 | | /// <param name="item">The object that was retrieved from the pool.</param> |
| | | 268 | | protected virtual void OnGet(T item) { } |
| | | 269 | | |
| | | 270 | | /// <summary> |
| | | 271 | | /// Called when an object is returned to the pool. |
| | | 272 | | /// </summary> |
| | | 273 | | /// <param name="item">The object that was returned to the pool.</param> |
| | | 274 | | protected virtual void OnReturn(T item) { } |
| | | 275 | | |
| | | 276 | | protected override void OnPreStart() |
| | | 277 | | { |
| | | 278 | | // Remove any subscribers that are left over from play mode. |
| | | 279 | | // Don't warn if there are any subscribers left over because we already do that in OnExitPlayMode. |
| | | 280 | | ClearSubscribers(); |
| | | 281 | | } |
| | | 282 | | |
| | | 283 | | /// <inheritdoc /> |
| | | 284 | | protected override void OnDisabled() |
| | | 285 | | { |
| | | 286 | | WarnIfLeftOverSubscribers(); |
| | | 287 | | |
| | | 288 | | Clear(); |
| | | 289 | | } |
| | | 290 | | |
| | | 291 | | /// <summary> |
| | | 292 | | /// Warns if there are any left-over subscribers to the events. |
| | | 293 | | /// </summary> |
| | | 294 | | /// <remarks>This will only be called in the Unity editor and builds with the DEBUG flag.</remarks> |
| | | 295 | | protected override void WarnIfLeftOverSubscribers() |
| | | 296 | | { |
| | | 297 | | base.WarnIfLeftOverSubscribers(); |
| | | 298 | | EventHelper.WarnIfLeftOverSubscribers(OnPoolChanged, nameof(OnPoolChanged), this); |
| | | 299 | | } |
| | | 300 | | |
| | | 301 | | /// <summary> |
| | | 302 | | /// Removes any subscribers from the event. |
| | | 303 | | /// </summary> |
| | | 304 | | /// <param name="warnIfLeftOver"> |
| | | 305 | | /// If true, a warning will be printed in the console if there are any subscribers. |
| | | 306 | | /// The warning will only be printed in the editor and debug builds. |
| | | 307 | | /// </param> |
| | | 308 | | public void ClearSubscribers(bool warnIfLeftOver = false) |
| | | 309 | | { |
| | | 310 | | #if DEBUG |
| | | 311 | | if (warnIfLeftOver) |
| | | 312 | | { |
| | | 313 | | WarnIfLeftOverSubscribers(); |
| | | 314 | | } |
| | | 315 | | #endif |
| | | 316 | | |
| | | 317 | | OnPoolChanged = null; |
| | | 318 | | } |
| | | 319 | | |
| | | 320 | | #region Obsolete |
| | | 321 | | #if UNITY_EDITOR |
| | | 322 | | /// <summary> |
| | | 323 | | /// Called when an object is created. |
| | | 324 | | /// </summary> |
| | | 325 | | [Obsolete("Use 'OnPoolChanged' instead. This will be removed in build.", true)] |
| | | 326 | | #pragma warning disable CS0067 // Event is never used |
| | | 327 | | public event Action<T>? OnCreateObject; |
| | | 328 | | /// <summary> |
| | | 329 | | /// Called when an object is destroyed. |
| | | 330 | | /// </summary> |
| | | 331 | | [Obsolete("Use 'OnPoolChanged' instead. This will be removed in build.", true)] |
| | | 332 | | public event Action<T>? OnDestroyObject; |
| | | 333 | | /// <summary> |
| | | 334 | | /// Called when an object is retrieved. |
| | | 335 | | /// </summary> |
| | | 336 | | [Obsolete("Use 'OnPoolChanged' instead. This will be removed in build.", true)] |
| | | 337 | | public event Action<T>? OnGetObject; |
| | | 338 | | /// <summary> |
| | | 339 | | /// Called when an object is put back into the pool. |
| | | 340 | | /// </summary> |
| | | 341 | | [Obsolete("Use 'OnPoolChanged' instead. This will be removed in build.", true)] |
| | | 342 | | public event Action<T>? OnReturnObject; |
| | | 343 | | #pragma warning restore CS0067 // Event is never used |
| | | 344 | | |
| | | 345 | | [Obsolete("Use 'Release' instead. This will be removed in build.", true)] |
| | | 346 | | [ExcludeFromCodeCoverage] |
| | | 347 | | public void Return(T item) |
| | | 348 | | { |
| | | 349 | | throw new NotSupportedException("'Return' is obsolete. Use 'Release' instead."); |
| | | 350 | | } |
| | | 351 | | #endif |
| | | 352 | | #endregion |
| | | 353 | | } |
| | | 354 | | } |