using System;
using System.Collections.Generic;
using UnityEngine;
namespace DunGen.Pooling
{
///
/// A simple object pool that groups objects into buckets based on a key
///
/// The key type that determines how objects are bucketed
/// The type of pooled object
public sealed class BucketedObjectPool where TObject : class
{
private readonly Dictionary> buckets = new Dictionary>();
private readonly Func objectFactory;
private readonly Action takeAction;
private readonly Action returnAction;
private readonly int initialCapacity;
private readonly Dictionary owningBuckets = new Dictionary();
public BucketedObjectPool(Func objectFactory,
Action takeAction = null,
Action returnAction = null,
int initialCapacity = 0)
{
this.objectFactory = objectFactory;
this.takeAction = takeAction;
this.returnAction = returnAction;
this.initialCapacity = initialCapacity;
}
///
/// Clears out all the existing objects in the pool
///
public void Clear()
{
buckets.Clear();
owningBuckets.Clear();
}
///
/// Takes an object from the pool, creating a new one if the pool is empty
///
/// The bucket to take from
/// The object returned from the pool
public TObject TakeObject(TKey key)
{
TryTakeObject(key, out var obj);
return obj;
}
///
/// Takes an object from the pool, creating a new one if the pool is empty
///
/// The bucket to take from
/// Object returned from the pool
/// Did we take an existing object from the pool? False if a new instance had to be created
public bool TryTakeObject(TKey key, out TObject obj)
{
if(key == null)
throw new ArgumentNullException(nameof(key));
// Create the bucket if it doesn't exist yet
if (!buckets.TryGetValue(key, out var bucket))
bucket = InitialiseBucket(key);
// Take an object from the bucket if there is one..
if (bucket.Count > 0)
{
obj = bucket[bucket.Count - 1];
bucket.RemoveAt(bucket.Count - 1);
takeAction?.Invoke(obj);
return true;
}
// ..otherwise create a new object
else
{
var newObject = objectFactory(key);
owningBuckets[newObject] = key;
obj = newObject;
return false;
}
}
///
/// Returns an object to the pool
///
/// The object to return
/// True if successful
public bool ReturnObject(TObject obj)
{
if (obj == null)
return false;
// Find which bucket owns this object
if (!owningBuckets.TryGetValue(obj, out var key))
return false;
returnAction?.Invoke(obj);
buckets[key].Add(obj);
return true;
}
///
/// Inserts an object into the pool. This is useful for pre-warming the pool with existing objects
///
/// The bucket to insert into
/// The object to insert
/// True if successful
public bool InsertObject(TKey key, TObject obj)
{
if(key == null || obj == null)
return false;
if (owningBuckets.TryGetValue(obj, out var existingKey))
{
Debug.LogError("Tried to 'Insert' an object into the pool that already belongs to it, use ReturnObject() instead");
return false;
}
// Create the bucket if it doesn't exist yet
if (!buckets.TryGetValue(key, out var bucket))
bucket = InitialiseBucket(key);
returnAction?.Invoke(obj);
buckets[key].Add(obj);
owningBuckets[obj] = key;
return true;
}
private List InitialiseBucket(TKey key)
{
var bucket = new List(initialCapacity);
buckets[key] = bucket;
for (int i = 0; i < initialCapacity; i++)
{
var newObject = objectFactory(key);
owningBuckets[newObject] = key;
}
return bucket;
}
public void DumpPoolInfo(Func getBucketName = null)
{
foreach (var pair in buckets)
{
string bucketName = getBucketName?.Invoke(pair.Key) ?? pair.Key.ToString();
Debug.Log($"Bucket: {bucketName}, Count: {pair.Value.Count}");
}
}
}
}