< Summary

Information
Class: Hertzole.ScriptableValues.ScriptablePool
Assembly: Hertzole.ScriptableValues
File(s): ./Packages/se.hertzole.scriptable-values/Runtime/Pools/ScriptablePool.cs
Tag: 52_28197466029
Line coverage
100%
Covered lines: 6
Uncovered lines: 0
Coverable lines: 6
Total lines: 354
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ScriptablePool()0%110100%

File(s)

./Packages/se.hertzole.scriptable-values/Runtime/Pools/ScriptablePool.cs

#LineLine coverage
 1#nullable enable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.ComponentModel;
 6using System.Diagnostics.CodeAnalysis;
 7using Hertzole.ScriptableValues.Helpers;
 8using UnityEngine;
 9using UnityEngine.Assertions;
 10#if SCRIPTABLE_VALUES_PROPERTIES
 11using Unity.Properties;
 12#endif
 13
 14namespace 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    {
 1225        public static readonly PropertyChangingEventArgs countAllChangingEventArgs = new PropertyChangingEventArgs(nameo
 1226        public static readonly PropertyChangedEventArgs countAllChangedEventArgs = new PropertyChangedEventArgs(nameof(C
 27
 1228        public static readonly PropertyChangingEventArgs countActiveChangingEventArgs = new PropertyChangingEventArgs(na
 1229        public static readonly PropertyChangedEventArgs countActiveChangedEventArgs = new PropertyChangedEventArgs(nameo
 30
 1231        public static readonly PropertyChangingEventArgs countInactiveChangingEventArgs = new PropertyChangingEventArgs(
 1232        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}

Methods/Properties

ScriptablePool()