I am trying to implement object pooling with a random bag as an exercise, let’s say for a recreation of Tetris for this example.
What I am trying to accomplish is to not create or destroy a new Tetris block every time I need one, so for this I want to use the UnityEngine.ObjectPool API.
I also want to make sure that the Tetris blocks are truly random and eliminate the chance of having bad luck and receiving that L block you desparately need after only a hundred new blocks.
For this, I plan to use a random bag implementation from this github repository:
As of now I do the following:
Create a randomBag and fill it until it contains each Tetris piece x times.
Fill the ObjectPool with each item of this randomBag.
This is where I am stuck, as calling ObjectPool.Get() returns the last item of the internal ObjectPool collection and calling ObjectPool.Release(item) adds it to the end of the collection, which means that I would only truly get a random item if I exhaust all items from the ObjectPool, and I won’t.
Calling RandomBag.PopRandomItem() would return a truly random item, but I would need to call ObjectPool.Get(item) (which would look like ObjectPool.Get(RandomBag.PopRandomItem()) in code) to receive that specific item from the object pool.
There are a few problems with that:
ObjectPool.Get() doesn’t take a specific item as a parameter
Implementing an ObjectPool.Get(item) method is possible in theory, but that would mean that I have to jump through all sorts of hoops to get access to the internal collection of the ObjectPool class, or that I would have to write my own implementation from scratch.
The implementation of an ObjectPool.Get(item) would require iterating over the entire ObjectPool collection (in a worst case scenario), which would not be great for performance if the collection is large.
I would also need a way to shuffle the internal collection of the ObjectPool, which comes with the same problems as 2).
Any help or insight on how to solve this problem would be very helpful!
Object pooling is trivial. It’s just a list of available, a list of used, and an API to return / retrieve it.
Any more doesn’t belong in a pool. A system using a pool should work even if you disable the pool (eg, set its size to zero).
Otherwise, it’s not a pool, it’s a critical part of your business logic.
Same applies to a cache.
I appreciate the desire to work through typical CIS-100 programming challenges like this but just know that using them in actual production practices is EXTREMELY destructive to the average project, making everything three or four times as complicated and introducing MASSIVE surface for difficult-to-diagnose bugs.
As of yesterday I spent the entire morning tracking down what was an object pooling bug in our scrolling front menu… this was an attempt to pool ten (10) little buttons to recycle them and the buttons had grown into a nightmare of saved state that simply was not being restored. Stay away from pooling. It’s AWFUL.
The costs and issues associated with object pooling / pools:
Because I haven’t figured out what the best approach to solving this problem is as of now.
As Kurt mentioned, I don’t want the pool to turn into a critical part of the system but I haven’t found an elegant solution so far.
This is not some CIS-100 challenge but a problem I’m attempting to solve as elegantly as possible for myself.
If I end up with the same conclusion as you that’s fine, but I’d like to try and see for myself rather than not try and make assumptions from something I ended up not doing because I read somewhere it was too complicated and not worth it.
I appreciate the response and I am taking note of your warning though, don’t get me wrong.
For now I am trying to implement this random bag with object pooling in a small game I made and released already, so I know the code inside and out. This problem has been bugging me lately since I can’t seem to come up with a solution though, hence why I came here.
You could have the RandomBag just contain the types of the different blocks, and then use that to map to an object pool that is unique for that block type.
using System;
using System.Collections.Generic;
using UnityEngine.Pool;
using Object = UnityEngine.Object;
public sealed class RandomPool<TObject> where TObject : Object
{
private readonly Dictionary<Type, ObjectPool<TObject>> pools;
private readonly RandomBag<Type> nextTypes;
public RandomPool(params TObject[] prefabs)
{
int count = prefabs.Length;
pools = new(count);
var types = new Type[count];
for(int i = 0; i < count; i++)
{
TObject prefab = prefabs[i];
Type type = prefab.GetType();
pools.Add(type, CreatePool(prefab));
types[i] = type;
}
nextTypes = new(types);
}
public void Get() => pools[nextTypes.PopRandomItem()].Get();
public void Release(TObject instance) => pools[instance.GetType()].Release(instance);
public ObjectPool<TObject> CreatePool(TObject prefab)
{
...
I just want to clarify this: this is not true randomness, this is tampered randomness, or distributed randomness. Yes the statistics won’t change in a sufficiently large sample, but it will drastically change locally (as you yourself stated), as is the case with any kind of token pooling. True randomness is getting 1000 of the same item over and over, however improbable that might seem. The same outcome can theoretically repeat infinitely many times, and who knows, maybe we live in a Universe where this has already happened, and we call this phenomenon “the laws of physics”.
Anyway we humans dislike this property a lot, because we wrongly assume true randomness to be nearly homogeneous. Nothing wrong with the approach, it’s just the nomenclature.
To conclude this thread, I implemented the solution @SisusCo proposed and garbage allocation has been reduced while I make good use of the random bag. Pretty straightforward solution, thank you. @Kurt-Dekker you were right for warning me regarding object pooling. Took me a few hours of troubleshooting before I fixed all sorts of weird issues caused by having to reset the state of the object when returning it to the pool. I guess the takeaway is that object pooling is just not worth implementing unless performance is noticeably impacted by garbage collection, just takes too much managing and therefore time overall.