3
\$\begingroup\$

In a WPF project I'm working on, often multiple objects reuse the same AnimationTimeline, to smooth that out I made an AnimationManager and few supporting classes. Reinitializing manually the same timeline is no longer required as long as you have it stored inside the manager, he will take care of it.

But even so somewhere in the code, new animation is initialized, whether it's going to be in the manager or in some other class is not important. To avoid creating new animations every single time and speed up the application performance a bit, I also added object pooling technique.

PooledObject<>

public class PooledObject<T>
{
    public Func<T> ObjectInitializer { get; }
    public bool CreateOnStartup { get; }

    public PooledObject(Func<T> objectInitializer, bool createOnStartup = true)
    {
        ObjectInitializer = objectInitializer ?? throw new ArgumentNullException(nameof(objectInitializer)); ;
        CreateOnStartup = createOnStartup;
    }
}

It's a simple wrapper for the intializator delegate and a property to signify whether this object should be created right away, or wait for manual population.

ObjectPooler<>

public static class LoopUtilities
{
    public static void Repeat(int count, Action action)
    {
        for (int i = 0; i < count; i++)
        {
            action();
        }
    }
}

public enum PoolRefillMethod
{
    None,

    /// <summary>
    /// Initializes a single new object everytime the pool goes dry.
    /// </summary>
    SingleObject,

    /// <summary>
    /// Reinitializes the whole pool everytime it goes dry.
    /// </summary>
    WholePool
}

public sealed class ObjectPooler<T>
{
    public int Amount { get; }
    public PoolRefillMethod RefillMethod { get; set; }

    private readonly PooledObject<T> _pooledObject;
    private readonly Queue<T> _pooledObjects;

    private readonly Dictionary<PoolRefillMethod, Action> _poolRefillMethods;

    public ObjectPooler(PooledObject<T> pooledObject, int amount, PoolRefillMethod refillMethod)
    {
        _pooledObject = pooledObject ?? throw new ArgumentNullException(nameof(pooledObject));
        Amount = amount;
        RefillMethod = refillMethod;
        _poolRefillMethods = new Dictionary<PoolRefillMethod, Action>
        {
            [PoolRefillMethod.None] = () => throw new InvalidOperationException("Pool is empty"),
            [PoolRefillMethod.SingleObject] = AddNewObject,
            [PoolRefillMethod.WholePool] = InitializePool,
        };

        _pooledObjects = new Queue<T>();
        if (_pooledObject.CreateOnStartup)
        {
            InitializePool();
        }
    }

    public void InitializePool()
    {
        _pooledObjects.Clear();
        LoopUtilities.Repeat(Amount, AddNewObject);
    }

    private void AddNewObject() => _pooledObjects.Enqueue(_pooledObject.ObjectInitializer.Invoke());

    public T GetObject()
    {
        if (_pooledObjects.Count == 0)
        {
            _poolRefillMethods[RefillMethod].Invoke();
        }
        return _pooledObjects.Dequeue();
    }
}

It works, but something I would expect from a object pooler, would be to reuse objects, but I cant seem to find a good way to notify the ObjectPooler<> when an object should be put back in the queue, so that's why I currently remove them completely.

Example usage

Both SingleObject and WholePool give the same result, but the way they achieve it is different. None simply throws an exception when the pool is empty.

public class TestClass
{
    public int Value { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{Value} - {Name}";
    }
}

var amount = 10;
var _pooledObject = new PooledObject<TestClass>(
    () => new TestClass {Value = 1, Name = "SomeName"});
var objectPooler = new ObjectPooler<TestClass>(_pooledObject, amount, PoolRefillMethod.WholePool);

for (int i = 1; i <= amount * 2; i++)
{
    Console.WriteLine($"#{i} {objectPooler.GetObject()}");
}

Any feedback is welcome, but I'm looking primarily for performance improvements as that's the whole point of the class.

\$\endgroup\$
  • 1
    \$\begingroup\$ What is LoopUtilities.Repeat()? \$\endgroup\$ – Jesse C. Slicer Mar 29 '18 at 19:14
  • 2
    \$\begingroup\$ Please check the updated question. \$\endgroup\$ – Denis Mar 29 '18 at 19:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.