#if UNITY_5_3_OR_NEWER using System; using System.Collections; using System.Collections.Generic; using System.Threading; namespace UniRx.Toolkit { /// /// Bass class of ObjectPool. /// public abstract class ObjectPool : IDisposable where T : UnityEngine.Component { bool isDisposed = false; Queue q; /// /// Limit of instace count. /// protected int MaxPoolCount { get { return int.MaxValue; } } /// /// Create instance when needed. /// protected abstract T CreateInstance(); /// /// Called before return to pool, useful for set active object(it is default behavior). /// protected virtual void OnBeforeRent(T instance) { instance.gameObject.SetActive(true); } /// /// Called before return to pool, useful for set inactive object(it is default behavior). /// protected virtual void OnBeforeReturn(T instance) { instance.gameObject.SetActive(false); } /// /// Called when clear or disposed, useful for destroy instance or other finalize method. /// protected virtual void OnClear(T instance) { if (instance == null) return; var go = instance.gameObject; if (go == null) return; UnityEngine.Object.Destroy(go); } /// /// Current pooled object count. /// public int Count { get { if (q == null) return 0; return q.Count; } } /// /// Get instance from pool. /// public T Rent() { if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed."); if (q == null) q = new Queue(); var instance = (q.Count > 0) ? q.Dequeue() : CreateInstance(); OnBeforeRent(instance); return instance; } /// /// Return instance to pool. /// public void Return(T instance) { if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed."); if (instance == null) throw new ArgumentNullException("instance"); if (q == null) q = new Queue(); if ((q.Count + 1) == MaxPoolCount) { throw new InvalidOperationException("Reached Max PoolSize"); } OnBeforeReturn(instance); q.Enqueue(instance); } /// /// Clear pool. /// public void Clear(bool callOnBeforeRent = false) { if (q == null) return; while (q.Count != 0) { var instance = q.Dequeue(); if (callOnBeforeRent) { OnBeforeRent(instance); } OnClear(instance); } } /// /// Trim pool instances. /// /// 0.0f = clear all ~ 1.0f = live all. /// Min pool count. /// If true, call OnBeforeRent before OnClear. public void Shrink(float instanceCountRatio, int minSize, bool callOnBeforeRent = false) { if (q == null) return; if (instanceCountRatio <= 0) instanceCountRatio = 0; if (instanceCountRatio >= 1.0f) instanceCountRatio = 1.0f; var size = (int)(q.Count * instanceCountRatio); size = Math.Max(minSize, size); while (q.Count > size) { var instance = q.Dequeue(); if (callOnBeforeRent) { OnBeforeRent(instance); } OnClear(instance); } } /// /// If needs shrink pool frequently, start check timer. /// /// Interval of call Shrink. /// 0.0f = clearAll ~ 1.0f = live all. /// Min pool count. /// If true, call OnBeforeRent before OnClear. public IDisposable StartShrinkTimer(TimeSpan checkInterval, float instanceCountRatio, int minSize, bool callOnBeforeRent = false) { return Observable.Interval(checkInterval) .TakeWhile(_ => !isDisposed) .Subscribe(_ => { Shrink(instanceCountRatio, minSize, callOnBeforeRent); }); } /// /// Fill pool before rent operation. /// /// Pool instance count. /// Create count per frame. public IObservable PreloadAsync(int preloadCount, int threshold) { if (q == null) q = new Queue(preloadCount); return Observable.FromMicroCoroutine((observer, cancel) => PreloadCore(preloadCount, threshold, observer, cancel)); } IEnumerator PreloadCore(int preloadCount, int threshold, IObserver observer, CancellationToken cancellationToken) { while (Count < preloadCount && !cancellationToken.IsCancellationRequested) { var requireCount = preloadCount - Count; if (requireCount <= 0) break; var createCount = Math.Min(requireCount, threshold); for (int i = 0; i < createCount; i++) { try { var instance = CreateInstance(); Return(instance); } catch (Exception ex) { observer.OnError(ex); yield break; } } yield return null; // next frame. } observer.OnNext(Unit.Default); observer.OnCompleted(); } #region IDisposable Support protected virtual void Dispose(bool disposing) { if (!isDisposed) { if (disposing) { Clear(false); } isDisposed = true; } } public void Dispose() { Dispose(true); } #endregion } /// /// Bass class of ObjectPool. If needs asynchronous initialization, use this instead of standard ObjectPool. /// public abstract class AsyncObjectPool : IDisposable where T : UnityEngine.Component { bool isDisposed = false; Queue q; /// /// Limit of instace count. /// protected int MaxPoolCount { get { return int.MaxValue; } } /// /// Create instance when needed. /// protected abstract IObservable CreateInstanceAsync(); /// /// Called before return to pool, useful for set active object(it is default behavior). /// protected virtual void OnBeforeRent(T instance) { instance.gameObject.SetActive(true); } /// /// Called before return to pool, useful for set inactive object(it is default behavior). /// protected virtual void OnBeforeReturn(T instance) { instance.gameObject.SetActive(false); } /// /// Called when clear or disposed, useful for destroy instance or other finalize method. /// protected virtual void OnClear(T instance) { if (instance == null) return; var go = instance.gameObject; if (go == null) return; UnityEngine.Object.Destroy(go); } /// /// Current pooled object count. /// public int Count { get { if (q == null) return 0; return q.Count; } } /// /// Get instance from pool. /// public IObservable RentAsync() { if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed."); if (q == null) q = new Queue(); if (q.Count > 0) { var instance = q.Dequeue(); OnBeforeRent(instance); return Observable.Return(instance); } else { var instance = CreateInstanceAsync(); return instance.Do(x => OnBeforeRent(x)); } } /// /// Return instance to pool. /// public void Return(T instance) { if (isDisposed) throw new ObjectDisposedException("ObjectPool was already disposed."); if (instance == null) throw new ArgumentNullException("instance"); if (q == null) q = new Queue(); if ((q.Count + 1) == MaxPoolCount) { throw new InvalidOperationException("Reached Max PoolSize"); } OnBeforeReturn(instance); q.Enqueue(instance); } /// /// Trim pool instances. /// /// 0.0f = clear all ~ 1.0f = live all. /// Min pool count. /// If true, call OnBeforeRent before OnClear. public void Shrink(float instanceCountRatio, int minSize, bool callOnBeforeRent = false) { if (q == null) return; if (instanceCountRatio <= 0) instanceCountRatio = 0; if (instanceCountRatio >= 1.0f) instanceCountRatio = 1.0f; var size = (int)(q.Count * instanceCountRatio); size = Math.Max(minSize, size); while (q.Count > size) { var instance = q.Dequeue(); if (callOnBeforeRent) { OnBeforeRent(instance); } OnClear(instance); } } /// /// If needs shrink pool frequently, start check timer. /// /// Interval of call Shrink. /// 0.0f = clearAll ~ 1.0f = live all. /// Min pool count. /// If true, call OnBeforeRent before OnClear. public IDisposable StartShrinkTimer(TimeSpan checkInterval, float instanceCountRatio, int minSize, bool callOnBeforeRent = false) { return Observable.Interval(checkInterval) .TakeWhile(_ => !isDisposed) .Subscribe(_ => { Shrink(instanceCountRatio, minSize, callOnBeforeRent); }); } /// /// Clear pool. /// public void Clear(bool callOnBeforeRent = false) { if (q == null) return; while (q.Count != 0) { var instance = q.Dequeue(); if (callOnBeforeRent) { OnBeforeRent(instance); } OnClear(instance); } } /// /// Fill pool before rent operation. /// /// Pool instance count. /// Create count per frame. public IObservable PreloadAsync(int preloadCount, int threshold) { if (q == null) q = new Queue(preloadCount); return Observable.FromMicroCoroutine((observer, cancel) => PreloadCore(preloadCount, threshold, observer, cancel)); } IEnumerator PreloadCore(int preloadCount, int threshold, IObserver observer, CancellationToken cancellationToken) { while (Count < preloadCount && !cancellationToken.IsCancellationRequested) { var requireCount = preloadCount - Count; if (requireCount <= 0) break; var createCount = Math.Min(requireCount, threshold); var loaders = new IObservable[createCount]; for (int i = 0; i < createCount; i++) { var instanceFuture = CreateInstanceAsync(); loaders[i] = instanceFuture.ForEachAsync(x => Return(x)); } var awaiter = Observable.WhenAll(loaders).ToYieldInstruction(false, cancellationToken); while (!(awaiter.HasResult || awaiter.IsCanceled || awaiter.HasError)) { yield return null; } if (awaiter.HasError) { observer.OnError(awaiter.Error); yield break; } else if (awaiter.IsCanceled) { yield break; // end. } } observer.OnNext(Unit.Default); observer.OnCompleted(); } #region IDisposable Support protected virtual void Dispose(bool disposing) { if (!isDisposed) { if (disposing) { Clear(false); } isDisposed = true; } } public void Dispose() { Dispose(true); } #endregion } } #endif