Pools
Pool GameObjects for Performance
Pool.cs
1 // With thanks to Jason Weimann -- jason@unity3d.college
2 
3 using System.Collections.Generic;
4 using UnityEngine;
5 
6 namespace Askowl {
7  /// <a href="http://bit.ly/2BQH2pA">MonoBehaviour to provide pooling functionality for child GameObjects. To be eligible they must be created with `Acquire` and disabled but not destroyed.</a> <inheritdoc />
8  public sealed class Pool : MonoBehaviour {
9  private bool isPoolContainer;
10 
11  private void Start() {
12  Fiber.Start.Begin.WaitFor(hasReturns).Do(SetParentOnReturn).Again.Finish();
13  }
14 
15  private void OnEnable() {
16  if (gameObject.name.EndsWith("Pool") || GetComponents<Component>().Length <= 2) {
17  isPoolContainer = true;
18  // This is a list of predefined game objects to be pooled
19  DontDestroyOnLoad(target: gameObject);
20  Transform[] children = GetComponentsInChildren<Transform>();
21 
22  // skip first as it is the Pool itself
23  for (var i = 1; i < children.Length; ++i) CreatePoolQueue(children[i].gameObject);
24  }
25  else if (!queues.ContainsKey(gameObject.name)) {
26  Destroy(this);
27  // we are inside a game object that wants to be pooled
28  PoolFor(gameObject.GetComponent<PoolMonitor>()?.MasterName ?? gameObject.name);
29  }
30  }
31 
32  private PoolQueue CreatePoolQueue(GameObject master) {
33  string poolName = $"{master.name} Pool";
34 
35  if (!queues.ContainsKey(poolName)) {
36  PoolMonitor poolMonitor = master.AddComponent<PoolMonitor>();
37  poolMonitor.MasterName = master.name;
38  queues[poolName] = new PoolQueue {Master = master, Parent = master.transform.parent};
39  GameObject poolRoot = new GameObject(poolName);
40  poolRoot.transform.parent = transform;
41  master.transform.SetParent(poolMonitor.PoolRoot = poolRoot.transform, worldPositionStays: false);
42  master.SetActive(true);
43  return queues[poolName];
44  }
45 
46  return null;
47  }
48 
49  /// <a href="http://bit.ly/2QdJ6kr">Use `Pool.Acquire&lt;T>()` to get a reference to a cloned gameObject instead of `Instantiate&lt;T>()`. All parameters bar name are optional.</a>
50  public static T Acquire<T>(
51  string name, Vector3 position = default, Quaternion rotation = default, Transform parent = default
52  , bool enable = true, bool poolOnDisable = true) where T : Component {
53  GameObject clone = Acquire(name, position, rotation, parent, enable, poolOnDisable);
54  return clone == null ? null : clone.GetComponent<T>();
55  }
56 
57  /// <a href="http://bit.ly/2BQvdQi">Use `Pool.Acquire()` to get a reference to a cloned gameObject instead of `Instantiate()`. All parameters bar name are optional.</a>
58  public static GameObject Acquire(
59  string name,
60  Vector3 position = default,
61  Quaternion rotation = default,
62  Transform parent = null,
63  bool enable = true,
64  bool poolOnDisable = true) {
65  PoolQueue pool = PoolFor(name);
66  if (pool == null) return null;
67 
68  GameObject clone;
69  PoolMonitor poolMonitor;
70 
71  do {
72  clone = pool.Fetch();
73  poolMonitor = clone.gameObject.GetComponent<PoolMonitor>();
74  } while (!poolMonitor.OkToPool);
75 
76  if (parent != null) clone.transform.SetParent(parent);
77  clone.transform.position = position;
78  clone.transform.rotation = rotation;
79  poolMonitor.PoolOnDisable = poolOnDisable;
80  clone.gameObject.SetActive(enable);
81  poolMonitor.InPool = false;
82  poolMonitor.OkToPool = true;
83  return clone;
84  }
85 
86  /// <a href="http://bit.ly/2BScbco">Give an object created by `Acquire` in any pool, return it to the pool for reuse. Only needed if `poolOnDisable` is false</a>
87  public static void Return(GameObject clone) {
88  returns.Push(clone);
89  if (returns.Count == 1) hasReturns.Fire();
90  }
91 
92  private static readonly Fifo<GameObject> returns = Fifo<GameObject>.Instance;
93  private static readonly Emitter hasReturns = Emitter.Instance;
94 
95  private void SetParentOnReturn(Fiber fiber) {
96  while (!returns.Empty) {
97  GameObject clone = returns.Pop();
98 
99  PoolMonitor poolMonitor = clone.GetComponent<PoolMonitor>();
100  PoolQueue pool = PoolFor(poolMonitor.MasterName);
101 
102  if (pool != null) {
103  poolMonitor.InPool = poolMonitor.OkToPool = true;
104  pool.Push(clone);
105  if (clone.transform.parent != poolMonitor.PoolRoot) clone.transform.SetParent(poolMonitor.PoolRoot, false);
106  }
107  else {
108  Debug.LogErrorFormat("**** Error: {0} was not created in a pool", clone.name);
109  }
110 
111  clone.SetActive(false);
112  }
113  }
114 
115  /// <a href="http://bit.ly/2QzQENF">Retrieve a reference to the pooling queue for the GameObject named</a>
116  public static PoolQueue PoolFor(string name, Pool pool = null) {
117  string poolName = $"{name} Pool";
118  if (queues.ContainsKey(poolName)) return queues[poolName];
119 
120  GameObject gameObject = Objects.Find<GameObject>(name);
121 
122  if (gameObject == null) {
123  gameObject = Resources.Load<GameObject>(name);
124 
125  if (gameObject == null) {
126  Debug.LogErrorFormat("Cannot find Prefab or loaded GameObject named '{0}'", name);
127  return null;
128  }
129  }
130 
131  // instantiate if it is a prefab
132  if (gameObject.scene.name == null) {
133  gameObject = Instantiate(gameObject);
134  gameObject.name = name;
135  }
136 
137  var monitor = gameObject.GetComponent<PoolMonitor>();
138  if (monitor != null) return queues[poolName] = queues[monitor.MasterName];
139 
140  var queue = (pool ? pool : FindPool()).CreatePoolQueue(gameObject);
141  if (!queues.ContainsKey(name)) queues[name] = queue; // name may be a path to the prefab
142  return queue;
143  }
144 
145  private static Pool FindPool() {
146  Pool pool = null;
147  var anyMonitor = FindObjectOfType<PoolMonitor>();
148 
149  if (anyMonitor != null) pool = anyMonitor.GetComponentInParent<Pool>();
150 
151  if (pool == null) {
152  var pools = FindObjectsOfType<Pool>();
153 
154  for (var i = 0; i < pools.Length && pool == null; i++) {
155  if (pools[i].isPoolContainer) pool = pools[i];
156  }
157  }
158 
159  if (pool == null) pool = Components.Create<Pool>("Pool");
160  return pool;
161  }
162 
163  private sealed class PoolDict : Dictionary<string, PoolQueue> { }
164 
165  private static readonly PoolDict queues = new PoolDict();
166 
167  /// <a href="">Each GameObject enabled for pooling resides in a `PoolQueue`.</a> <inheritdoc />
168  public sealed class PoolQueue : Fifo<GameObject> {
169  /// <a href="">Master GameObject from which copies are cloned</a>
170  public GameObject Master;
171 
172  /// <a href=""></a>
173  public Transform Parent;
174 
175  private int count;
176 
177  /// <a href="">Fetch a cloned GameObject - either from a list of those returned or instantiating a new instance</a>
178  public GameObject Fetch() {
179  GameObject clone;
180 
181  if (Count > 0) {
182  clone = Pop();
183  }
184  else {
185  clone = Instantiate(Master);
186  clone.name = $"{Master.name} (clone {++count})";
187  }
188 
189  clone.transform.SetParent(Parent);
190 
191  return clone;
192  }
193  }
194  }
195 }
MonoBehaviour to provide pooling functionality for child GameObjects. To be eligible they must be cre...
Definition: Pool.cs:8
Definition: Pool.cs:6
static T Acquire< T >(string name, Vector3 position=default, Quaternion rotation=default, Transform parent=default, bool enable=true, bool poolOnDisable=true)
Use Pool.Acquire<T>() to get a reference to a cloned gameObject instead of Instantiate<T>(). All parameters bar name are optional.
Definition: Pool.cs:50
Transform Parent
Definition: Pool.cs:173
static PoolQueue PoolFor(string name, Pool pool=null)
Retrieve a reference to the pooling queue for the GameObject named
Definition: Pool.cs:116
Each GameObject enabled for pooling resides in a PoolQueue.
Definition: Pool.cs:168
Transform PoolRoot
Definition: PoolMonitor.cs:21
GameObject Master
Master GameObject from which copies are cloned
Definition: Pool.cs:170
static void Return(GameObject clone)
Give an object created by Acquire in any pool, return it to the pool for reuse. Only needed if poolOn...
Definition: Pool.cs:87
static GameObject Acquire(string name, Vector3 position=default, Quaternion rotation=default, Transform parent=null, bool enable=true, bool poolOnDisable=true)
Use Pool.Acquire() to get a reference to a cloned gameObject instead of Instantiate(). All parameters bar name are optional.
Definition: Pool.cs:58
GameObject Fetch()
Fetch a cloned GameObject - either from a list of those returned or instantiating a new instance ...
Definition: Pool.cs:178