Files
Main/Assets/Plugins/References/FuncellBase/ObjectPool/ObjectPool.cs
2025-01-25 04:38:09 +08:00

291 lines
12 KiB
C#

/*
* Generic Object Pool Implementation
*
* Implemented by Ofir Makmal, 28/1/2013
*
* My Blog: Blogs.microsoft.co.il/blogs/OfirMakmal
* Email: Ofir.Makmal@gmail.com
*
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
namespace Thousandto.Core.Base
{
/// <summary>
/// Generic object pool
/// </summary>
/// <typeparam name="T">The type of the object that which will be managed by the pool. The pooled object have to be a sub-class of PooledObject.</typeparam>
public class ObjectPool<T> where T : PooledObject {
#region Consts
private const int DefaultPoolMinimumSize = 5;
private const int DefaultPoolMaximumSize = 100;
#endregion
#region Private Members
// Pool internal data structure
private LinkedList<T> PooledObjects { get; set; }
// Action to be passed to the pooled objects to allow them to return to the pool
private MyAction<PooledObject, bool> _returnToPoolAction;
// Indication flag that states whether Adjusting operating is in progress.
// The type is Int, altought it looks like it should be bool - this was done for Interlocked CAS operation (CompareExchange)
private int _adjustPoolSizeIsInProgressCASFlag = 0; // 0 state false
#endregion
#region Public Properties
/// <summary>
/// Gets the Diagnostics class for the current Object Pool.
/// </summary>
public ObjectPoolDiagnostics Diagnostics { get; private set; }
/// <summary>
/// Gets the count of the objects currently in the pool.
/// </summary>
public int ObjectsInPoolCount {
get { return PooledObjects.Count; }
}
private int _minimumPoolSize;
/// <summary>
/// Gets or sets the minimum number of objects in the pool.
/// </summary>
public int MinimumPoolSize {
get { return _minimumPoolSize; }
set {
// Validating pool limits, exception is thrown if invalid
ValidatePoolLimits( value, _maximumPoolSize );
_minimumPoolSize = value;
AdjustPoolSizeToBounds();
}
}
private int _maximumPoolSize;
/// <summary>
/// Gets or sets the maximum number of objects that could be available at the same time in the pool.
/// </summary>
public int MaximumPoolSize {
get { return _maximumPoolSize; }
set {
// Validating pool limits, exception is thrown if invalid
ValidatePoolLimits( _minimumPoolSize, value );
_maximumPoolSize = value;
AdjustPoolSizeToBounds();
}
}
private MyFunc<T> _factoryMethod = null;
/// <summary>
/// Gets the Factory method that will be used for creating new objects.
/// </summary>
public MyFunc<T> FactoryMethod {
get { return _factoryMethod; }
private set { _factoryMethod = value; }
}
#endregion
#region C'tor and Initialization code
/// <summary>
/// Initializes a new pool with default settings.
/// </summary>
public ObjectPool() {
InitializePool( DefaultPoolMinimumSize, DefaultPoolMaximumSize, null );
}
/// <summary>
/// Initializes a new pool with specified minimum pool size and maximum pool size
/// </summary>
/// <param name="minimumPoolSize">The minimum pool size limit.</param>
/// <param name="maximumPoolSize">The maximum pool size limit</param>
public ObjectPool( int minimumPoolSize, int maximumPoolSize ) {
InitializePool( minimumPoolSize, maximumPoolSize, null );
}
/// <summary>
/// Initializes a new pool with specified factory method.
/// </summary>
/// <param name="factoryMethod">The factory method that will be used to create new objects.</param>
public ObjectPool( MyFunc<T> factoryMethod ) {
InitializePool( DefaultPoolMinimumSize, DefaultPoolMaximumSize, factoryMethod );
}
/// <summary>
/// Initializes a new pool with specified factory method and minimum and maximum size.
/// </summary>
/// <param name="minimumPoolSize">The minimum pool size limit.</param>
/// <param name="maximumPoolSize">The maximum pool size limit</param>
/// <param name="factoryMethod">The factory method that will be used to create new objects.</param>
public ObjectPool( int minimumPoolSize, int maximumPoolSize, MyFunc<T> factoryMethod ) {
InitializePool( minimumPoolSize, maximumPoolSize, factoryMethod );
}
private void InitializePool( int minimumPoolSize, int maximumPoolSize, MyFunc<T> factoryMethod ) {
// Validating pool limits, exception is thrown if invalid
ValidatePoolLimits( minimumPoolSize, maximumPoolSize );
// Assigning properties
FactoryMethod = factoryMethod;
_maximumPoolSize = maximumPoolSize;
_minimumPoolSize = minimumPoolSize;
// Initializing the internal pool data structure
PooledObjects = new LinkedList<T>();
// Creating a new instnce for the Diagnostics class
Diagnostics = new ObjectPoolDiagnostics();
// Setting the action for returning to the pool to be integrated in the pooled objects
_returnToPoolAction = ReturnObjectToPool;
// Initilizing objects in pool
AdjustPoolSizeToBounds();
}
#endregion
#region Private Methods
private void ValidatePoolLimits( int minimumPoolSize, int maximumPoolSize ) {
if ( minimumPoolSize < 0 ) {
throw new ArgumentException( "Minimum pool size must be greater or equals to zero." );
}
if ( maximumPoolSize < 1 ) {
throw new ArgumentException( "Maximum pool size must be greater than zero." );
}
if ( minimumPoolSize > maximumPoolSize ) {
throw new ArgumentException( "Maximum pool size must be greater than the maximum pool size." );
}
}
private void AdjustPoolSizeToBounds() {
// If there is an Adjusting operation in progress, skip and return.
if ( Interlocked.CompareExchange( ref _adjustPoolSizeIsInProgressCASFlag, 1, 0 ) == 0 ) {
// If we reached this point, we've set the AdjustPoolSizeIsInProgressCASFlag to 1 (true) - using the above CAS function
// We can now safely adjust the pool size without interferences
// Adjusting...
while ( ObjectsInPoolCount < MinimumPoolSize ) {
PooledObjects.AddLast( CreatePooledObject() );
}
while ( ObjectsInPoolCount > MaximumPoolSize ) {
if ( PooledObjects.Count > 0 ) {
T dequeuedObjectToDestroy = PooledObjects.First.Value;
PooledObjects.RemoveFirst();
// Diagnostics update
Diagnostics.IncrementPoolOverflowCount();
DestroyPooledObject( dequeuedObjectToDestroy );
}
}
// Finished adjusting, allowing additional callers to enter when needed
_adjustPoolSizeIsInProgressCASFlag = 0;
}
}
private T CreatePooledObject() {
T newObject;
if ( FactoryMethod != null ) {
newObject = FactoryMethod();
} else {
// Throws an exception if the type doesn't have default ctor - on purpose! I've could've add a generic constraint with new (), but I didn't want to limit the user and force a parameterless c'tor
newObject = (T)Activator.CreateInstance( typeof( T ) );
}
// Diagnostics update
Diagnostics.IncrementObjectsCreatedCount();
// Setting the 'return to pool' action in the newly created pooled object
newObject.ReturnToPool = (MyAction<PooledObject, bool>)_returnToPoolAction;
return newObject;
}
private void DestroyPooledObject( PooledObject objectToDestroy ) {
// Making sure that the object is only disposed once (in case of application shutting down and we don't control the order of the finalization)
if ( !objectToDestroy.Disposed ) {
// Deterministically release object resources, nevermind the result, we are destroying the object
objectToDestroy.ReleaseResources();
objectToDestroy.Disposed = true;
// Diagnostics update
Diagnostics.IncrementObjectsDestroyedCount();
}
// The object is being destroyed, resources have been already released deterministically, so we di no need the finalizer to fire
GC.SuppressFinalize( objectToDestroy );
}
#endregion
#region Pool Operations
/// <summary>
/// Get a monitored object from the pool.
/// </summary>
/// <returns></returns>
public T GetObject() {
T dequeuedObject = null;
if ( PooledObjects.Count > 0 ) {
dequeuedObject = PooledObjects.First.Value;
PooledObjects.RemoveFirst();
// Invokes AdjustPoolSize asynchronously
AdjustPoolSizeToBounds();
// Diagnostics update
Diagnostics.IncrementPoolObjectHitCount();
return dequeuedObject;
} else {
// This should not happen normally, but could be happening when there is stress on the pool
// No available objects in pool, create a new one and return it to the caller
System.Diagnostics.Debug.WriteLine( "Object pool failed to return a pooled object. pool is empty. consider increasing the number of minimum pooled objects." );
// Diagnostics update
Diagnostics.IncrementPoolObjectMissCount();
return CreatePooledObject();
}
}
internal void ReturnObjectToPool( PooledObject objectToReturnToPool, bool reRegisterForFinalization ) {
T returnedObject = (T)objectToReturnToPool;
// Diagnostics update
if ( reRegisterForFinalization )
Diagnostics.IncrementObjectRessurectionCount();
// Checking that the pool is not full
if ( ObjectsInPoolCount < MaximumPoolSize ) {
// Reset the object state (if implemented) before returning it to the pool. If reseting the object have failed, destroy the object
if ( !returnedObject.ResetState() ) {
// Diagnostics update
Diagnostics.IncrementResetStateFailedCount();
DestroyPooledObject( returnedObject );
return;
}
// re-registering for finalization - in case of resurrection (called from Finalize method)
if ( reRegisterForFinalization ) {
GC.ReRegisterForFinalize( returnedObject );
}
// Diagnostics update
Diagnostics.IncrementReturnedToPoolCount();
// Adding the object back to the pool
PooledObjects.AddLast( returnedObject );
} else {
// Diagnostics update
Diagnostics.IncrementPoolOverflowCount();
//The Pool's upper limit has exceeded, there is no need to add this object back into the pool and we can destroy it.
DestroyPooledObject( returnedObject );
}
}
#endregion
#region Finalizer
~ObjectPool() {
// The pool is going down, releasing the resources for all objects in pool
foreach ( var item in PooledObjects ) {
DestroyPooledObject( item );
}
}
#endregion
}
}