UnityEngine.Pool Wrapper having issues with generics.

I am trying to create a wrapper around Unity’s built it object pooling. It isn’t all that good for expandability so I thought a wrapped would make it better for it.

It’s a pretty straight forward wrapper with two classes. A PoolObject class that any object that is pooled will inherit from and a Pool class that holds the pool for the inherited class.

Here is the PoolObject which all pooled objects inherit from.

public abstract class PoolObject : MonoBehaviour
{
    public bool Active { get => gameObject.activeSelf; set => gameObject.SetActive(value); }
    public IObjectPool<PoolObject> Pool { get; set; }//This is the part I am having problems with

    public void Release()
    {
        OnRelease();

        if (Pool != null)
        {
            Pool.Release(this);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    protected virtual void OnRelease()
    {

    }
}

Here is the Pool itself.

[System.Serializable]
public class Pool<T> where T : PoolObject
{
    public event System.Action<T> OnCreated;
    public event System.Action<T> OnGet;
    public event System.Action<T> OnReleased;
    public event System.Action<T> OnDestroyed;

    public GameObject prefab;

    private IObjectPool<T> pool;

    public void Initialize()
    {
        if (prefab == null)
        {
            throw new System.Exception("Prefab is null!");
        }

        pool = new ObjectPool<T>(CreatObject, GetObject, ReleaseObject, DestroyObject);
    }

    private T CreatObject()
    {
        GameObject go = Object.Instantiate(prefab);
        T obj = go.GetComponent<T>();
        obj.Pool = pool as IObjectPool<PoolObject>;//How do I assign this without it being null?
        Debug.Log(obj.Pool);
        OnCreated?.Invoke(obj);
        return obj;
    }

    private void GetObject(T obj)
    {
        Debug.Log($"Object {obj} Get!");
        OnGet?.Invoke(obj);
    }

    private void ReleaseObject(T obj)
    {
        Debug.Log($"Object {obj} Released!");
        OnReleased?.Invoke(obj);
        obj.Active = false;
    }

    private void DestroyObject(T obj)
    {
        Debug.Log($"Object {obj} Destroyed!");
        OnDestroyed?.Invoke(obj);
        Object.Destroy(obj.gameObject);
    }

    public T Get()
    {
        return pool.Get();
    }
}

The only time it does work is if I use Pool which is of course obvious. The rest of the time I get null when setting the Pool property on the PoolObject if I do something like Pool.

Is there something I am missing here. T is restricted to PoolObject or anything that inherits it, so it should always be IObjectPool if cast to that. At least that is my understanding. I’ve been programming for years and this is not something I have ever come across as a problem.

I know this is more of a C# thing and not Unity specific, but I am really stumped as to how to fix this.

In before Kurt posts his usual anti-pooling blurb.

In any case, Generics don’t work with inheritance as you might imagine. SomeType<BaseClass> is a different type completely to SomeType<InheritedClass>. The second type does not interoperate with the former type.

Though I feel like the assignment could work as: obj.Pool = pool as IObjectPool<T>; instead. Otherwise you can have a non-generic base interface type that the generic interface inherits from.

Note that casting with as returns nulls if the cast fails and should only be used where you have a situation where you can expect it to fail and work your logic around that. Otherwise if it should be completely type safe, you should use an explicit cast obj.Pool = (IObjectPool<T>)pool; so the compiler tells you if you’re trying to do a cast that will likely never work.

6 Likes

Negative, doesn’t work with obj.Pool = pool as IObjectPool<T>;

I guess all my years of semi-self education is showing through.

You are correct with the explicit cast of course, I did that first and it failed to cast. Just me being dumb trying the as cast thinking it would work.

I guess we will wait for Kurt to post his rant on pooling. In the mean time I will create my own pooling system, which I should have done in the first place. One that will work without all this crazy thinking that Unity built. Why? Because pooling has it’s place. Why put un-needed stress on the GC creating and destroying objects when you can use a little memory and reuse objects?

lol… don’t worry, we still luv ya Kurt!

Personally I make my own object pooling, it’s basically a list of classes, and you just iterate through that list, get a disabled object, turn it on, and return it. Then when it’s life’s done just disable it. So it’s pretty simple logic.

I also use Inheritance, and it’s pretty easy to make a single generic function that can call any child, by referencing the parent class.

However I think Unity’s pooling goes more in depth than my method, as it moves disabled objects into their own list, so you only call index[0] to enable(instantiate) a new object. But I’ve never compared the two, as I can tell it’s just probably the same versus: many iterations vs adding/removing from lists.

But, personally am a fan of Object Pooling ← :smile:

Looking again I can I can see that the issue is that your property in PoolObject is IObjectPool<PoolObject>. The only type that will match that is an ObjectPool<PoolObject>, any other type will encounter the problem I mentioned before.

You could of course make PoolObject generic too, and this seems to be what Unity do themselves.

I’m surprised (or maybe I’m not) Unity didn’t actually make a non-generic base interface here, as it’s generally advised to do. Were I to do this, I might make something like this:

public interface IObjectPool
{
    int CountInactive { get; }
  
    object Get();
  
    PooledObject(out object v);
  
    void Release(object element);
  
    void Clear();
}

public interface IObjectPool<T> : IObjectPool
    where T : class
{
    new T Get();
  
    new PooledObject<T> Get(out T v);
  
    new void Release(T element);
}

(Some of those new’s might not actually be needed)

Sure… sure… when it makes a difference.

You did check and see that your pooler is vastly more efficient than Unity’s built-in Instantiate, right?

I think you might be surprised at how close they are. :slight_smile:

But remember pooling has a HORRIBLE dreadful cost, making a mess out of your code, and on top of it all, you now have to micro-manage every property of every pooled object.

No. Thanks. That’s why I use game engines. If I wanted to write bookkeeping code like that, I’d write a game engine.

The costs and issues associated with object pooling / pools:

https://discussions.unity.com/t/892797/10

https://discussions.unity.com/t/833104/2

1 Like

It’s funny how your posts end up in a recursive loop only quoting yourself.

Give me some figures for god sake. Quoting yourself in an endless loop, seems like your trolling at this point. OR you’ve just gotten lazy. Are you helping me or helping yourself?

Metric’s, in which you measure cost vs reward.

What part of game development isn’t bookkeeping?

I mean his point about measuring results is valid. There’s no reason to make an object pooling system if you:
A: Don’t have a performance hot spot to begin with, and
B: The pooling system isn’t that much faster than the normal process, as it wasn’t a performance issue to begin with

I’ve only used a pooling system twice, where I did have a performance issue, both times in my internal audio toolkit. One to pool Audio Source components for reuse, and another to pool a rather heavyweight class that emulates all the properties of a audio source for the sake of modifying these pooled audio sources.

In both times the pooling was very much internal as well - nothing outside the audio system has access to it - so very little in the way of book keeping.

1 Like

I would suggest it, but still have Unity’s way available so you can benchmark/profile side by side.

Not sure if my method of Object Pooling will help, so I’ll just toss out an example:

public class Master : MonoBehaviour // mono here since I use Inheritance
{
    public static List<Projectile> allBullets = new List<Projectile>();
    public static List<Projectile> allPlasma = new List<Projectile>();
    public static List<Projectile> allLasers = new List<Projectile>();
   
    public Projectile GetProjectileFromList(List<Projectile> list, Vector3 position, GameObject prefab)
    {
        if (list.Count > 0)
        {
            for (int i = 0; i < list.Count; i++)
            {
                if (!list[i].gameObject.activeSelf)
                {
                    list[i].trans.position = position;
                    list[i].gameObject.SetActive(true);
                    return list[i];
                }
            }
        }
        Instantiate(prefab, position, Quaternion.identity);
        return list[list.Count - 1];
    }
}
public class Projectile : Master // or other grouped parent
{
    // variables used by all projectile types
    // public functions used by each type(groups)
    // NO Awake(), Start(), Update(), etc...
}
public class Bullet : Projectile
{
    void Awake() // needed for list proper calculations
    {
        allBullets.Add(this);
        // set base damage, lifeTime, knockBack, etc...
    }
   
    // if hits enemy = gameObject.SetActive(false);
   
    void OnEnable() // or OnDisable()
    {
        lifeTime = 0;
        damage = baseDamage;
        // other resets, etc...
    }       
}
public class Gun : Master // or other grouped parent
{
    void Shoot()
    {
        Bullet bullet = (Bullet)GetProjectileFromList(
            allBullets, shootPos, bulletPrefab);
        bullet.rotation = shootDirection;
        bullet.shootSpeed = gunShootSpeed;
        // any other changes for bullet, etc...
    }
}

Now one could argue the difference from Instantiating a bunch at load time, versus only adding in more if you need it? But that’s no different than not using Object Pooling to begin with, so I say moot point.

And another could argue if the list eventually gets so big that iterating through it could be a performance loss(which I haven’t noticed yet), but is that performance loss comparable to making another list that constantly adds/removes to keep track of “disabled/available” from pool? not sure, never benchmarked it.

But from my understanding, Instantiating/Destroying/Garbage Collection/Modifying Lists(add/remove) are all aspects to why Object Pooling was created in the first place. So sure some can say “well maybe only Mobile”, I call B.S. because not everyone has an up-to-date PC, some of us are working on dinosaurs(which mobile is faster than now).

It’s just my personal experience of always having minimum requirements, and having games made by people who don’t care about performance, and me the user dealing with massive lag spikes and spf(seconds per frame) as my gaming experience.

So when I hear a dev say “I’m not worried about optimizing”, it makes me grind my teeth and my dwarven battle axe, lol… It’s just a pet-peeve, and I hope to never fall into that “screw the little guy” attitude.

But take Skyrim for an example, for Xbox it was heavily optimized(10-12 years ago), to the point you can Mod it to your hearts content and it still runs smooth. And better yet, my Xbox warped it’s motherboard so I can’t play any games on it(even poker) without it over-heating… except Skyrim works just fine(no mods)… I envy that level of care and dedication when making a game, and hope I can slightly get close to that level one day. :roll_eyes:

1 Like

…. aaand before you write your own custom pooler, have you checked whether the free Master Object Pooler suffices? :wink:

1 Like

@Kurt-Dekker 's point is not about performance. If he was concerned about it he might pool but Unity is a beast.

His point is all the extra coding required to reset the object to it’s default values, which is done for you when you instantiate directly. It takes discipline, it takes time and it can be quite a pain to debug. Use it where it works, bullets and other highly instantiated and destroyed objects(particle effects anyone.) More complex objects means more bookkeeping, which is already a major part of game development so he says why add that extra layer on it, build the damn game already.

Kurt, I’m sorry for the way I worded my response to you. The post was quite recursive in it’s nature both in talking point and link following, it bothered me and I posted in haste(and after a few beer.) You do a great service to the community. Please be mindful in how you preach what you practice it can end up being a disservice.

In the end I used an event to call back to the pool wrapper so the object could be released back in without worrying about generics and it’s limitations. I’ll test it for issues if it becomes one.

public abstract class PooledObject : MonoBehaviour
{
    public event System.Action<PooledObject> OnReleased;

    public bool Active { get => gameObject.activeInHierarchy; set => gameObject.SetActive(value); }

    public virtual void Release()
    {
        if (OnReleased != null)
        {
            OnReleased.Invoke(this);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

You have characterized my position on pooling accurately.

Appreciate hearing that, no offense taken. I do tend to be blunt, so thank you for the reminder. :slight_smile:

As to why I feel this way about pooling:

I am generally triggered by excess complexity.

I am especially triggered by mindlessly-applied excess complexity.

I am ESPECIALLY triggered by dogmatically-mindlessly-applied excess complexity.

Pooling is all of these things, except in the narrow cases where it benefits the problem space.

I also regularly fix other people’s pooling mistakes. A lot.

“Hm, when I set the pool size to zero, the bug goes away.”

Pooling is simply Enable/Disable, instead of Instantiate/Destroy…

If some like to keep destroying and re-instantiating the same thing over and over? that’s just what they like to do, I’m not gonna knock it, I’ve seen some do far worse…

But to me, it’s like throwing out your fork after it gets dirty, then you go to the store to buy a new one… Just wash the damn thing! lol

Cleaning a fork requires you to have water, soap and a cloth. You can use a dishwasher, but for a single fork it’s overkill.
A disposable fork eliminates the need to for all that.

Both have an associated cost, that is what testing and analytics is for. Which one benefits us the most, washing the fork or using a disposable one? Efficiency wise, the disposable one. Start accounting for other variables and washing the fork starts to out weigh the benefit of the disposable one. But that is a fork.

That’s more like a light bulb. Enabled it’s lit. Disabled it’s dark. Even bullets aren’t that simple.

It’s really not. You need to manage/reset the state of the objects, which can be tedious if there’s a lot of data held inside the pooled objects.

For example, my AudioSourceState class, which effectively emulates every property of AudioSource, does need to reset every single field when released from the pooling system. That’s a lot of room for error there. Though if you implement IDisposable, you can use a using statement to make it relatively automatic (you still have to remember to use it).

Eg:

using (var state = AudioSourceState.GetState())
{
    // modify state
    state.ApplyState(audioSource);
    audioSource.PlayOneShot(clip);
    state.ResetAudioSource(audioSource);
}

If you use Unity’s way of object pooling, sure… my way is just my shirt.

Pooling is better, glad you agree!

huh…

public example Off : CommonSense
{
    void HitEnemy()
    {
        gameObject.SetActive(false);
    }
}
   
public example On : CommonSense
{
    void OnEnable()
    {
        lifeTime = 0;
        damage = baseDamage;
        // other resets, etc...
    }
   
    void Shoot()
    {
        Bullet bullet = (Bullet)GetProjectileFromList(
            allBullets, shootPos, bulletPrefab);
        bullet.rotation = shootDirection;
        bullet.shootSpeed = gunShootSpeed;
        // any other changes for bullet, etc...
    }
}

Looks pretty simple to me :smile: