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}"); } } } }