-
Notifications
You must be signed in to change notification settings - Fork 0
Object pooling utility #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
8c1203a
2e4b6b5
0786a97
e59ed73
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using UnityEngine; | ||
|
|
||
| namespace DUCK.Pooling | ||
| { | ||
| public static class ObjectPool | ||
| { | ||
| private interface Pool | ||
| { | ||
| Type ObjectType { get; } | ||
|
|
||
| void Add(PoolableObject obj); | ||
| } | ||
|
|
||
| private class Pool<T> : Pool | ||
| where T : PoolableObject | ||
| { | ||
| private Queue<T> pooledObjects = new Queue<T>(); | ||
|
|
||
| public Type ObjectType | ||
| { | ||
| get | ||
| { | ||
| return typeof(T); | ||
| } | ||
| } | ||
|
|
||
| public T Get() | ||
| { | ||
| T obj = null; | ||
| while (pooledObjects.Count > 0) | ||
| { | ||
| obj = pooledObjects.Dequeue(); | ||
| } | ||
|
|
||
| if (obj) | ||
| { | ||
| obj.RemoveFromPool(); | ||
| } | ||
|
|
||
| return obj; | ||
| } | ||
|
|
||
| public void Add(PoolableObject obj) | ||
| { | ||
| if (obj != null && !pooledObjects.Contains((T)obj)) | ||
| { | ||
| obj.AddToPool(); | ||
| pooledObjects.Enqueue((T)obj); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static Dictionary<Type, Pool> poolLookup = new Dictionary<Type, Pool>(); | ||
|
|
||
| public static T Instantiate<T>(T original = null) where T : PoolableObject | ||
| { | ||
| T obj = null; | ||
|
|
||
| if (PoolExists(typeof(T))) | ||
| { | ||
| obj = GetFromPool<T>(); | ||
|
|
||
| if (obj != null) | ||
| { | ||
| if (original != null) | ||
| { | ||
| obj.CopyFrom(original); | ||
| } | ||
|
|
||
| return obj; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| CreatePool<T>(); | ||
| } | ||
|
|
||
| return obj ?? GameObject.Instantiate<T>(original); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Either line 72 needs removing or this line needs to be |
||
| } | ||
|
|
||
| public static T Instantiate<T>(string resourcePath) where T : PoolableObject | ||
| { | ||
| T obj = null; | ||
|
|
||
| if (PoolExists(typeof(T))) | ||
| { | ||
| obj = GetFromPool<T>(); | ||
| } | ||
| else | ||
| { | ||
| CreatePool<T>(); | ||
| } | ||
|
|
||
| return obj ?? Resources.Load<T>(resourcePath); | ||
| } | ||
|
|
||
| public static void Destroy(PoolableObject obj) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When an object is released to a pool it would be good to invoke a cleanup step.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would you be cleaning up? PoolableObject already expoded AddedToPool and RemovedFromPool events for any components on the gameObject to listen for - this is where I'd expect custom cleanup actions to take place There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be cleaning up events :) and state. I didn't spot those add and remove methods though. Not sure if publicly subscribe-able events will eventually prove problematic in a system that is dealing with objects that at any given time can be hard or soft destroyed. |
||
| { | ||
| if (obj == null) return; | ||
|
|
||
| if (PoolExists(obj.GetType())) | ||
| { | ||
| ReturnToPool(obj); | ||
| } | ||
| else | ||
| { | ||
| Destroy(obj); | ||
| } | ||
| } | ||
|
|
||
| private static void CreatePool<T>() where T : PoolableObject | ||
| { | ||
| if (PoolExists(typeof(T))) return; | ||
|
|
||
| poolLookup.Add(typeof(T), new Pool<T>()); | ||
| } | ||
|
|
||
| private static Pool<T> FindPool<T>() where T : PoolableObject | ||
| { | ||
| if (!PoolExists(typeof(T))) throw new KeyNotFoundException(typeof(T).ToString()); | ||
|
|
||
| return (Pool <T>)poolLookup[typeof(T)]; | ||
| } | ||
|
|
||
| private static bool PoolExists(Type type) | ||
| { | ||
| return poolLookup.ContainsKey(type); | ||
| } | ||
|
|
||
| private static T GetFromPool<T>() where T : PoolableObject | ||
| { | ||
| return PoolExists(typeof(T)) | ||
| ? FindPool<T>().Get() | ||
| : null; | ||
| } | ||
|
|
||
| private static void ReturnToPool(PoolableObject obj) | ||
| { | ||
| if (!PoolExists(obj.GetType())) return; | ||
|
|
||
| poolLookup[obj.GetType()].Add(obj); | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| using DUCK.Utils; | ||
| using System; | ||
| using UnityEngine; | ||
|
|
||
| namespace DUCK.Pooling | ||
| { | ||
| public class PoolableObject : MonoBehaviour | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor bit of weirdness, PoolableObject inherits MonoBehaviour but this whole feature references objects as if that's the class you're dealing with but object is a little further up the inheritance tree.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't follow - where does the ObjectPool reference objects as object/Object? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the naming convention. |
||
| { | ||
| public event Action OnAddedToPool; | ||
| public event Action OnRemovedFromPool; | ||
|
|
||
| public void AddToPool() | ||
| { | ||
| OnAddedToPool.SafeInvoke(); | ||
| } | ||
|
|
||
| public void RemoveFromPool() | ||
| { | ||
| OnRemovedFromPool.SafeInvoke(); | ||
| } | ||
|
|
||
| public void CopyFrom(PoolableObject instance) { } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this needed? To avoid specifically being able to copy a camera? I'd expect CopyFrom to work but to interact with the pool.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No - to mimic the behaviour of Unity's Instantiate(T original) - see ObjectPool.cs:67 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case it might be worth making this abstract or throw. Right now if I'm meant to expect Especially given that sometimes (with the null coalescing) its going to use the original and working GameObject.Inantiate(original); |
||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments documenting how the pool works, when it should be used and any pitfalls would be good.
Part of those comments might be a some a warning against blind or excessive usage of a pool which could cause (assumption alert!) slow down of the GC or creating a large memory footprint and all the usual issues with premature optimisation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to keep this relatively lightweight, so the pools aren't even created until you call ObjectPool.Instantiate - this will create and maintain a pool for the objects to return to when soft-destroyed via ObjectPool.Destroy, but is otherwise transparently identical to the standard Instantiate and Destroy functions, in that they're called as fallbacks if there isn't a pool to use.
I will, though, add guards to prevent a pooled object being pooled a second time, and to prevent the pool from returning destroyed objects, since it's still possible to hard-Destroy something while it's in the pool.