#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