// 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 } }