Hello! I’m trying to learn how to use the Unity Pool API and I’m having some trouble, also i was not sure of which topic to use on the forum so i did a quick research and found some pool related questions in the scripting so i guess it goes here. My code (below) doesn’t give any error warning but it gives the exactly same perfomance as if i was just spawning objects normally, also it never runs the destroy function no matter how much i change the pool size parameters.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
public class SpawnPool : MonoBehaviour
{
public ChestPool chestPrefab; // Prefab of the object to be pooled
public int spawnNum;
public float spawnInterval;
public float spawnStart;
public int defaultCapacity;
public int maxPoolSize;
public static SpawnPool Instance;
public ObjectPool<ChestPool> _pool;
void Start()
{
Instance = this;
_pool = new ObjectPool<ChestPool>(createFunc,actionOnGet,actionOnRelease, actionOnDestroy, true, defaultCapacity, maxPoolSize);
InvokeRepeating("ObjectSpawn", spawnStart, spawnInterval);
}
void ObjectSpawn()
{
for (int i = 0; i < spawnNum; i++)
{
ChestPool chestObj = _pool.Get();
}
}
ChestPool createFunc()
{
ChestPool chestObj = Instantiate(chestPrefab, Random.insideUnitCircle * 3, Quaternion.identity);
chestObj.SetPool(_pool);
return chestObj;
}
void actionOnGet(ChestPool chestObj)
{
chestObj.transform.position = Random.insideUnitCircle * 3;
chestObj.transform.rotation = Quaternion.identity;
chestObj.gameObject.SetActive(true);
}
void actionOnRelease(ChestPool chestObj)
{
chestObj.gameObject.SetActive(false);
}
void actionOnDestroy(ChestPool chestObj)
{
Destroy(chestObj.gameObject);
Debug.Log("Destroy Called");
}
}
//This is my spawn pool code, it it inside a empty game object and i wanted it to spawn objects using object pooling. The script responsible for returning objects to the pool is inside the object i want to return. And it's the following:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
public class ChestPool : MonoBehaviour
{
public ObjectPool<ChestPool> _pool;
private void OnTriggerEnter(Collider other)
{
if(other.gameObject.CompareTag("floor")){
_pool.Release(this);
}
}
public void SetPool(ObjectPool<ChestPool> pool){
_pool = pool;
}
}
//I have checked and it is returning objects to the pool
It is my first time using this forum, and i did the post according to what i understood from the guidelines, sorry if there’s anything wrong. Also i posted the entire code because i have no idea of where the error is. Thanks for the attention!
Are you actually having problems with garbage collection spikes slowing your game down? Pooling will not improve your performance at all, unless you actually have the specific problem that pooling was designed to address.
I had to the a pooling demonstration for a class so it’s not exactly a game i just made it spawn objects to see how it works with pooling. What’s bothering me is that in the profiler im getting the exactly same results for memory allocation, and that makes me think that there’s something wrong with the way i’m building things.
Besides garbage collection, pooling does increasingly make sense the more an object does on creation and destruction. Basically what code you run in Awake, Start, OnEnable, OnDisable and OnDestroy matters.
It is generally advisable to keep code in those methods clean and simple, thus efficient. If for example you perform Resources.Load (or similar) in Start and you instantiate several such objects per frame then pooling may help. But it would be better if the objects wouldn’t call that method, normally you can do that when the scene loads and provide access to the references with a singleton for example.
Do you happen to have one of these SpawnPool objects in each scene and load a new scene for each scene? If so, check if Instance is not null on start and reuse the existing one, instead of the new one. Otherwise you’re just leaking memory. (You might also want to set the initial singleton to be DontDestroyOnLoad and destroy any superfluous pool coming after it. This does not look like a clean example of the Singleton pattern and the pitfalls of that pattern might be a part of your issue here)
The pool will not remove the initial allocation time when your pool is empty, just on reallocation. So whenever your game restarts or a new scene is loaded, that’s where it’ll make a difference, so profile that, instead of the first allocation.
That or preheat you pool by spawning an amount of objects you’ll likely need and despawning it as part of some loading pattern if you know you have a moment where you can spend some extra time on this and then a stretch of time where you really don’t want to take the hit of the allocations or Garbage Collection.
Having profiled the living crap out of action heavy mobile games, I can tell you that pooling is not just a fad (*), but it is certainly easy to be done wrong.
Also UnityEngine.Objects are more heavy to allocate and initialize than just straight up managed C# Objects, and often have associated costs that are not always cleanly shown at the moment of instantiation (e.g. due to delayed initializations or lazy one time allocations, e.g. for those GetComponent calls, which only allocate on the first call, if at all).
(* Though it might have been more noticeable in Unity 2017 and pre Incremental GC. These days one might have to do two runs, one with pooling and Incremental GC off, and one without pooling and Incremental GC on, and checking overall frame performance in Profile Analyser to see the difference. Lastly, if you’re near exclisively GPU bound, pooling might hardly make a difference)
Engineering requires first a measurement, then a choice of appropriate solution followed by iteration and repeated measurement towards an ultimate solution that actually gives a benefit on the intended target hardware.
That’s engineering.
Amateur slapped-on “let’z learn to pool all the things!!!” almost always fails on every level: increased complexity, decreased reliability, and even sometimes decreased performance.
“Software does not run in a magic fairy aether powered by the fevered dreams of CS PhDs.” - Mike Acton
Agreed, but that’s somewhat besides the point when one is specifically trying to learn how to do pooling right.
Back to what could be wrong with the implementation:
If your maxPoolSize (defaulting to 0 in the inspector since it’s not initialized) is lower than the amount of Chests present at a given moment, then you’ll also keep creating chests new, though then returning them to the pool should trigger the Destroy. Similarly if the defaultCapacity (defaulting to 0) is less than the amount likely needed, the initial spawns until you reach your peak pool size will keep allocating. Also, tidbit but: naming that MonoBehaviour ChestPool might be somewhat misleading as its more of a PooledChest, or just a Chest, while the SpawnPool is hardwired to just be a pool for spawning Chests at the moment, so it could be more accurate to call that one a ChestPool.
Beyond all of this and as alluded to by the others, getting familiar with how to debug with your IDE (so that you can see the state of your pool as objects spawn and despawn) and the Profiler is crucial to, respectively, make the right calls on why something might not be working and what architectures to use and how to optimize them. For the use case of reducing GC.Allocs I’d recommend reading up on the Allocation Callstacks feature of the CPU Usage Profiler Module, as well as the manual pages on Managed Memory, the Garbage Collector (and sub-pages) and Unity Objects. You’ll be wanting to know what precisely allocates, how much and why, and why and how that matters to performance. You might not need it immediately when starting out but with game programming but it is knowledge that will definitely come in handy and learning of and thinking about what’s going on behind the easy facade that Unity builds up around these, while getting more familiar with the basics of making a game, will mean you’ll have a far easier time spotting mistakes and potential performance issues, as well as checking if they will actually hurt you. For that, just always think of the worst case you can imagine your game could throw at that system and test and measure against that. If it doesn’t even register, you’ve just learned what you don’t need to care about and can calmly focus on integrating it in the least complex way so that can be easily maintained.
Speaking of maintainability and patterns: http://gameprogrammingpatterns.com is a great resource and also has something on the Singleton pattern you used here.