// zlib/libpng License
//
// Copyright (c) 2018 Sinoa
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
using System;
using System.Collections.Generic;
namespace IceMilkTea.Core
{
///
/// 特定の型のインスタンスをプールすることが出来るプール抽象クラスです。
///
///
/// このプールクラスのインスタンスによって生成されたオブジェクトは、プールから提供されたままでも
/// プール自体が解放される時に、オブジェクトの解放を行います。
///
/// プールするインスタンスの型
public abstract class ObjectPool : IDisposable
{
// メンバ変数定義
private List createdObjectPool;
private Queue nextProvideObjectQueue;
private bool allowCreateObject;
private bool disposed;
///
/// プールから直ちに取り出せる数
///
///
/// このプロパティが 0 の場合でも allowCreateObject が true ならばオブジェクトの取得は可能です
///
public int AvailableCount => nextProvideObjectQueue.Count;
#region コンストラクタ&解放
///
/// ObjectPool のインスタンスを初期化します
///
public ObjectPool()
{
// リストやキューの初期化のみ行う
createdObjectPool = new List();
nextProvideObjectQueue = new Queue();
// 新しくオブジェクトを生成することを許可する
allowCreateObject = true;
}
///
/// ObjectPool のインスタンスを指定された初期生成数でプールを初期化します
///
/// 初期生成を行うオブジェクト数
/// initializeCount に負の範囲が指定されました
public ObjectPool(int initializeCount) : this(initializeCount, true)
{
}
///
/// ObjectPool のインスタンスを指定された初期生成数でプールを初期化します
///
/// 初期生成を行うオブジェクト数
/// プールが空の時新しくオブジェクトを生成することを許可する場合は true を、許可しない場合は false
public ObjectPool(int initializeCount, bool allowCreateObject)
{
// 初期個数が負の値なら
if (initializeCount < 0)
{
// 負の個数ってどうやって作るのだ
throw new ArgumentOutOfRangeException(nameof(initializeCount), $"{nameof(initializeCount)} に負の範囲が指定されました");
}
// リストとキューの長さを初期の数分生成する
createdObjectPool = new List(initializeCount);
nextProvideObjectQueue = new Queue(initializeCount);
// 初期生成の数分回る
for (int i = 0; i < initializeCount; ++i)
{
// オブジェクトを生成してリストとキューに追加
var newObject = Create();
createdObjectPool.Add(newObject);
nextProvideObjectQueue.Enqueue(newObject);
}
// オブジェクト生成許可情報を受取る
this.allowCreateObject = allowCreateObject;
}
///
/// ObjectPool の解放をします
///
~ObjectPool()
{
// ファイナライザからのDispose呼び出し
Dispose(false);
}
///
/// ObjectPool のリソースを解放します
///
public void Dispose()
{
// DisposeからのDispose呼び出しをして、GCのファイナライザは呼ばないようにしてもらう
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// ObjectPool のリソースを解放します
///
/// マネージド解放なら turu を、アンマネージ解放なら false を指定
protected virtual void Dispose(bool disposing)
{
// 既に解放済みなら
if (disposed)
{
// 何もせず終了
return;
}
// マネージの解放なら
if (disposing)
{
// 自身によって生成または、Releaseから渡されたオブジェクト分回る
for (int i = 0; i < createdObjectPool.Count; ++i)
{
// 解放を呼ぶ
Destroy(createdObjectPool[i]);
}
}
// 解放済みマーク
disposed = true;
}
#endregion
#region プール操作
///
/// 指定された数がプールから直ちに取り出せるように、追加のオブジェクトを生成しプールします。
/// ただし、指定された数のオブジェクトが既にプールから取り出せる場合は、この関数は何もしません。
///
/// 保証するべきプールの数
/// 新しくオブジェクトを作ることが許可されていないため、数の確保が出来ません
public void Ensure(int poolCount)
{
// 要求された数が既にプールにあるのなら
if (poolCount <= AvailableCount)
{
// 要求には答えられるサイズはあるので直ちに終了
return;
}
// 新しくオブジェクトを作ることが許可されていないのなら
if (!allowCreateObject)
{
// 新しく作ることが出来ないので例外を吐く
throw new InvalidOperationException("新しくオブジェクトを作ることが許可されていないため、数の確保が出来ません");
}
// 足りない数分回る
int requiredCount = poolCount - AvailableCount;
for (int i = 0; i < requiredCount; ++i)
{
// オブジェクトを生成してリストとキューに追加
var newObject = Create();
createdObjectPool.Add(newObject);
nextProvideObjectQueue.Enqueue(newObject);
}
}
///
/// プールからオブジェクトを取り出します
///
///
/// プールが空になった場合 allowCreateObject が true の時新しくオブジェクトを生成しますが
/// allowCreateObject が false の場合、この関数は例外をスローします。
///
/// 取り出されたオブジェクトを返します
/// プールが空のためオブジェクトを取り出すことが出来ません
public T Take()
{
// 例外判定を入れる
ThrowIfDisposed();
// もしキューが空なら
if (nextProvideObjectQueue.Count == 0)
{
// もし新しくオブジェクトを生成する事が許されていないなら
if (!allowCreateObject)
{
// プールが空でオブジェクトを提供することが出来ない例外を吐く
throw new InvalidOperationException("プールが空のためオブジェクトを取り出すことが出来ません");
}
// 新しくオブジェクトを生成してリストに追加後返す
var newerObject = Create();
createdObjectPool.Add(newerObject);
return newerObject;
}
// キューから取り出して初期化の振る舞いを実行して返す
var providableObject = nextProvideObjectQueue.Dequeue();
InitializeObject(providableObject);
return providableObject;
}
///
/// プールからオブジェクトの取り出しを試みます。
/// プールが空の場合は通常新しくオブジェクトが生成されますが allowCreateObject が false の時は T の既定値を返します
///
/// 取り出されたオブジェクトを返します
public T TryTake()
{
// 例外判定を入れる
ThrowIfDisposed();
// もしキューが空なら
if (nextProvideObjectQueue.Count == 0)
{
// もし新しくオブジェクトを生成する事が許されていないなら
if (!allowCreateObject)
{
// 既定値を返す
return default(T);
}
// 新しくオブジェクトを生成してリストに追加後返す
var newerObject = Create();
createdObjectPool.Add(newerObject);
return newerObject;
}
// キューから取り出して初期化の振る舞いを実行して返す
var providableObject = nextProvideObjectQueue.Dequeue();
InitializeObject(providableObject);
return providableObject;
}
///
/// プールから取り出したオブジェクトまたは外部のオブジェクトをプールに返します
///
/// 返すオブジェクト
/// obj が null です
public void Release(T obj)
{
// 例外判定を入れる
ThrowIfDisposed();
// nullを渡されたら
if (obj == null)
{
// nullのプールは許可されない
throw new ArgumentNullException(nameof(obj));
}
// オブジェクトの解放の振る舞いを実行して提供キューに追加する
FinalizeObject(obj);
nextProvideObjectQueue.Enqueue(obj);
}
#endregion
#region インスタンス操作
///
/// プールする新しいオブジェクトを生成します
///
/// 新しく生成したオブジェクトを返します
protected abstract T Create();
///
/// プールが生成したオブジェクトを解放します
///
/// 解放するオブジェクト
protected virtual void Destroy(T obj)
{
// もしIDisposableを実装しているのなら
if (obj is IDisposable)
{
// Disposeを呼ぶ
((IDisposable)obj).Dispose();
}
}
///
/// プールからオブジェクトが取り出され、そのオブジェクトを初期化する振る舞いを実行します
///
/// 初期化を行うオブジェクト
protected virtual void InitializeObject(T obj)
{
}
///
/// オブジェクトがプールに返される時に、そのオブジェクトを解放する振る舞いを実行します
///
/// 解放を行うオブジェクト
protected virtual void FinalizeObject(T obj)
{
}
#endregion
#region 内部ロジック
///
/// オブジェクトが解放済みの時に例外をスローします
///
/// このインスタンスは既に解放済みです
private void ThrowIfDisposed()
{
// 解放済みなら
if (disposed)
{
// オブジェクト解放済み例外を投げる
throw new ObjectDisposedException(GetType().FullName);
}
}
#endregion
}
}