-
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 1 commit
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,161 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using UnityEngine; | ||
|
|
||
| namespace DUCK.Pooling | ||
| { | ||
| public abstract class PoolableComponent : MonoBehaviour | ||
| { | ||
| protected internal virtual void OnAddedToPool() | ||
|
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. I'm not sure we need internal here, duck will currently always exist in the project's
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. Done |
||
| { | ||
| enabled = false; | ||
| gameObject.SetActive(false); | ||
| } | ||
|
|
||
| protected internal virtual void OnRemovedFromPool() | ||
| { | ||
| gameObject.SetActive(true); | ||
| enabled = true; | ||
| } | ||
|
|
||
| protected internal abstract void CopyFrom(PoolableComponent instance); | ||
| } | ||
|
|
||
| public static class ObjectPool | ||
|
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. 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.
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 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. |
||
| { | ||
| private abstract class AbstractPool | ||
|
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. Given theres no implementation, this may as well just be an interface,
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. True - done |
||
| { | ||
| public abstract Type ObjectType { get; } | ||
|
|
||
| public abstract void Add(PoolableComponent obj); | ||
| } | ||
|
|
||
| private class Pool<T> : AbstractPool | ||
| where T : PoolableComponent | ||
| { | ||
| private Queue<T> pooledObjects = new Queue<T>(); | ||
|
|
||
| public override Type ObjectType | ||
| { | ||
| get | ||
| { | ||
| return typeof(T); | ||
| } | ||
| } | ||
|
|
||
| public T Remove() | ||
|
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. Perhaps
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. Renamed |
||
| { | ||
| var obj = (pooledObjects.Count > 0) | ||
| ? pooledObjects.Dequeue() | ||
| : null; | ||
|
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. Might be worth considering letting the pool manage object creation as well since it handles the other end of the lifecycle and would be much more convenient for the end user. Its not unusual for pools to return a new instance of the object where you have
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. It does - ObjectPool is a private class so this will never be called by the end user. See the static Instantiate functions which are for external use - is that not what you mean? They perform the 'create or retrieve' step, either from a prefab or resourcePath |
||
|
|
||
| if (obj) | ||
| { | ||
| obj.OnRemovedFromPool(); | ||
| } | ||
|
|
||
| return obj; | ||
| } | ||
|
|
||
| public override void Add(PoolableComponent obj) | ||
| { | ||
| if (obj != null) | ||
| { | ||
| obj.OnAddedToPool(); | ||
| pooledObjects.Enqueue((T)obj); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static Dictionary<Type, AbstractPool> poolLookup = new Dictionary<Type, AbstractPool>(); | ||
|
|
||
| public static T Instantiate<T>(T original = null) where T : PoolableComponent | ||
| { | ||
| 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 : PoolableComponent | ||
| { | ||
| T obj = null; | ||
|
|
||
| if (PoolExists(typeof(T))) | ||
| { | ||
| obj = GetFromPool<T>(); | ||
| } | ||
| else | ||
| { | ||
| CreatePool<T>(); | ||
| } | ||
|
|
||
| return obj ?? Resources.Load<T>(resourcePath); | ||
| } | ||
|
|
||
| public static void Destroy(PoolableComponent obj) | ||
| { | ||
| if (obj == null) return; | ||
|
|
||
| if (PoolExists(obj.GetType())) | ||
| { | ||
| ReturnToPool(obj); | ||
| } | ||
| else | ||
| { | ||
| Destroy(obj); | ||
| } | ||
| } | ||
|
|
||
| private static void CreatePool<T>() where T : PoolableComponent | ||
| { | ||
| if (PoolExists(typeof(T))) return; | ||
|
|
||
| poolLookup.Add(typeof(T), new Pool<T>()); | ||
| } | ||
|
|
||
| private static Pool<T> FindPool<T>() where T : PoolableComponent | ||
| { | ||
| 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 : PoolableComponent | ||
| { | ||
| return PoolExists(typeof(T)) | ||
| ? FindPool<T>().Remove() | ||
| : null; | ||
| } | ||
|
|
||
| private static void ReturnToPool(PoolableComponent 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.
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.
Correct me if i'm wrong, but the API here requires you to subclass this component in order to be poolable. If this is so can I suggest a slight API modification; make this one non abstract and we can just add this to our object if we want to pool it, otherwise I can see a lot of subclassing this just to add it, or unnecessary inheritance. Composition could be our friend here.
I also see opportunity to add a function here to return the object to the pool (destroy it without destroying it), that way we don't need access to the pool from within the object that is pooled. We can just go
GetComponent<PoolableComponnet>().ReturnToPool()If it's non abstract I'd probably think about renaming it. Soime suggestions
PoolableBehaviour,PooledItem,PooledElementThere 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.
OK - done