// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace SixLabors.Fonts {
///
/// A pool for reusing objects of type .
///
/// The type to pool objects for.
///
/// This implementation keeps a cache of retained objects.
/// This means that if objects are returned when the pool has already reached "maximumRetained" objects they will be available to be Garbage Collected.
///
internal sealed class ObjectPool
where T : class
{
private readonly Func createFunc;
private readonly Func returnFunc;
private readonly int maxCapacity;
private int numItems;
private readonly ConcurrentQueue items = new();
private T? fastItem;
///
/// Initializes a new instance of the class.
///
/// The pooling policy to use.
public ObjectPool(IPooledObjectPolicy policy)
: this(policy, Environment.ProcessorCount * 2)
{
}
///
/// Initializes a new instance of the class.
///
/// The pooling policy to use.
/// The maximum number of objects to retain in the pool.
public ObjectPool(IPooledObjectPolicy policy, int maximumRetained)
{
// cache the target interface methods, to avoid interface lookup overhead
this.createFunc = policy.Create;
this.returnFunc = policy.Return;
this.maxCapacity = maximumRetained - 1; // -1 to account for fastItem
}
///
/// Gets an object from the pool if one is available, otherwise creates one.
///
/// A .
public T Get()
{
T? item = this.fastItem;
if (item == null || Interlocked.CompareExchange(ref this.fastItem, null, item) != item)
{
if (this.items.TryDequeue(out item))
{
_ = Interlocked.Decrement(ref this.numItems);
return item;
}
// no object available, so go get a brand new one
return this.createFunc();
}
return item;
}
///
/// Return an object to the pool.
///
/// The object to add to the pool.
public void Return(T obj) => this.ReturnCore(obj);
///
/// Returns an object to the pool.
///
/// true if the object was returned to the pool
private bool ReturnCore(T obj)
{
if (!this.returnFunc(obj))
{
// policy says to drop this object
return false;
}
if (this.fastItem != null || Interlocked.CompareExchange(ref this.fastItem, obj, null) != null)
{
if (Interlocked.Increment(ref this.numItems) <= this.maxCapacity)
{
this.items.Enqueue(obj);
return true;
}
// no room, clean up the count and drop the object on the floor
_ = Interlocked.Decrement(ref this.numItems);
return false;
}
return true;
}
}
///
/// Represents a policy for managing pooled objects.
///
/// The type of object which is being pooled.
#pragma warning disable SA1201 // Elements should appear in the correct order
internal interface IPooledObjectPolicy
#pragma warning restore SA1201 // Elements should appear in the correct order
where T : notnull
{
///
/// Create a .
///
/// The which was created.
public T Create();
///
/// Runs some processing when an object was returned to the pool. Can be used to reset the state of an object and indicate if the object should be returned to the pool.
///
/// The object to return to the pool.
/// if the object should be returned to the pool. if it's not possible/desirable for the pool to keep the object.
public bool Return(T obj);
}
}