Search Unity

Casting a generic class to an interface?

Discussion in 'Scripting' started by Freaking-Pingo, Jan 23, 2015.

  1. Freaking-Pingo

    Freaking-Pingo

    Joined:
    Aug 1, 2012
    Posts:
    310
    Hi Uniteers,
    I am currently working on my own object pooling system, and I am for once trying to use generics, but there is something which I don't understand, and searching the web doesn't aid me.

    What I am trying to do is casting a class taking a type argument as input to an interface, but the compile error I get is:

    This is a stripped down version of my code:

    Code (CSharp):
    1. public interface IPool
    2. {
    3.     T GetAvailable<T>();
    4. }
    5.  
    6. public class ObjectList<T> where T : MonoBehaviour, IPool
    7. {
    8.     public T GetAvailable()
    9.     {
    10.         // Some functionallity
    11.     }
    12. }
    At some point, I am trying to do this:
    Code (CSharp):
    1.     public static class ObjectPool
    2.     {
    3.         public static void Get<T>() where T : MonoBehaviour
    4.         {
    5.             IPool poolRef = (IPool)new ObjectList<T>(); //This is where I get the compile error
    6.         }
    7.     }
    And that is where I get the error. What I don't grasp is, why can't I cast my ObjectList<T>() to an IPool? Can someone pleas knock me one on the head and help me deduce why I can't do this, what am I failing to grasp here?

    The way I see it, ObjectList, regardless of the type argument, will always have the interface implemented.



     
  2. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    Replace this
    Code (csharp):
    1. IPool poolRef = (IPool)new ObjectList<T>();
    With this:
    Code (csharp):
    1. ObjectList<T> list = new ObjectList<T>();
    2. IPool listAsPool = list as IPool;
    I believe that should fix your problem. However, your interface may need to be generic as well, to cast a generic class to it.
     
  3. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    ObjectList doesn't implement IPool so you can't cast to it. If it did, a cast wouldn't even be necessary because ObjectList would be an IPool already.

    Code (csharp):
    1.  
    2. public class ObjectList<T> : IPool where T : MonoBehaviour, IPool
    3.  
    4.  
    5. IPool somePool = new ObjectList<SomeIPoolComponent>();
    6.  
    I also don't understand why you're not putting a generic on IPool. Your whole structure is a bit weird.
     
  4. Freaking-Pingo

    Freaking-Pingo

    Joined:
    Aug 1, 2012
    Posts:
    310
    I won't argue about that, but I just can't seem to grasp a better method.
    The reason I am not puttin a generic on IPool is because I am using IPool as a common reference for generic lists.
    I am trying to make a dictionary that looks like this:

    Code (CSharp):
    1. public static class ObjectPool
    2. {
    3.     Dictionary<System.Type, IPool> poolingDictionary;
    4. }
    The class which implements IPool contains a list of generic type so:

    Code (CSharp):
    1. public class ObjectList<T> : IPool where T: MonoBehaviour
    2. {
    3.     List<T> listOfObjects;
    4. }
    The reason I am doing this, is because I can't find a proper way of using a generic list as a value in a dictionary (This creates a compile error):

    Code (CSharp):
    1. public static class ObjectPool
    2. {
    3.     Dictionary<System.Type, List<T>>
    4. }
     
  5. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Because T isn't defined anywhere. This should work

    Code (csharp):
    1.  
    2. public class ObjectPool<T>
    3. {
    4.     Dictionary<Type, List<T>> objectList;
    5. }
    6.  
    Or if you want to hold multiple types you could do something like this
    Code (csharp):
    1.  
    2. public void CreatePool<T>()
    3. {
    4.     objectList[typeOf(T)] = new List<T>();
    5. }
    6.  
     
  6. Freaking-Pingo

    Freaking-Pingo

    Joined:
    Aug 1, 2012
    Posts:
    310
    In your code example, you removed the static parameter for ObjectPool.The idea is that the ObjectPool is static so there can only be one instance of ObjectPool. The reason it for being static is to have a centralized class which can be accesed to perform all object pooling commands.
     
    Last edited: Jan 23, 2015
  7. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Ok. Shouldn't really effect what you're trying to do from a generics perspective and I still don't see where the interface fits in.

    In either case, you could also do generic constraints on the methods and leave the class alone.

    Code (csharp):
    1.  
    2. static class ObjectPool
    3. {
    4.    public static void CreatePool<T>()
    5.     {
    6.  
    7.     }
    8. }
    9.  
     
  8. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    You can't use generics in static code. T has no meaning if it's not in instantiated code. That, I imagine, is what throws your compiler error. For that matter, there's no such thing in C# as a static class, only static members of the class. Otherwise, your Dictionary should do fine.

    Consider where you're using a static instance. The way I typically do it is to have a static instance of my generic pool with every class I pool. Then, the pool is managed with references like this:
    Code (csharp):
    1. Item item = ItemManager.pool.Get();
    2. ItemManager.pool.Return(item);
    Which is fine on small projects, although it creates lots of tight coupling.
     
  9. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    To add to the excellent answers are already given, it seems like you've decided to pool instances of MonoBehaviour, which isn't very useful as not the most general usecase. It's GameObjects that are expensive to create and that need pooling. MonoBeahviours are just tagging along for the ride.

    It would be far more useful to pool GameObjects grouped by the prefabs that they were created from rather than pooling MonoBehaviours based on type. This is how Path-o-logical's PoolManger does it, and it works really well.

    Maybe you're working on another approach or you have a very specific need in mind, but your intent is not obvious from your examples.
     
  10. Freaking-Pingo

    Freaking-Pingo

    Joined:
    Aug 1, 2012
    Posts:
    310
    Great suggestion, but the reason I am pooling Monobehaviours is to avoid using GetComponents after the object have been pooled as it can become relatively expensive when a multitude of instances have to get accesed. If I don't store their Monobehaviour reference, I'll have to use GetComponent.

    After this thread, I have come to realize, my design may be pretty butched. With my new knowledge on generics, I'll properly redo my work, and see if I can come up with a better solution.
     
  11. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    Uh, what? Static methods and classes definitely can be generic.
    So you're telling me that this doesn't compile:
    Code (csharp):
    1.  
    2. public static class NewBehaviourScript<T> {
    3.  
    4.    static public void DoSomething()
    5.    {
    6.  
    7.    }
    8.  
    9.    static public T DoSomething<T>(T ini)
    10.    {
    11.       return ini;
    12.    }
    13. }
    Am I misunderstanding you?
     
    KelsoMRK and Dameon_ like this.
  12. Freaking-Pingo

    Freaking-Pingo

    Joined:
    Aug 1, 2012
    Posts:
    310
    Well, as for what I have understood until now, its possible to create a static class generic, but it would be quiet useless, because there can only exist on instance of NewBehaviourScript. I don't see a use case scenario of a generic static.
     
  13. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    Ouch, got me! You're totally right. I was mistaken for whatever reason.
     
  14. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    Then it's not really pooling that you're after. You're trying to solve a different problem here.

    I think that is not a problem worth solving. GetComponent is expensive, true. But only in the sense that it gets expensive if it's done repeatedly (i.e. in every frame). Even then it hardly registers in the profiler. If you use GetComponent only once at the start of a MonoBehaviour's life and cache the components you want to access then it should't be a cause for concern. And that's a problem that is best addressed on a script-by-script bases.

    If you have other specialized needs, like requiring to frequently access all instances of a certain kind of MonoBehaviour, then you're better off having static collection and adding all new instances of said behavior to it inside the Awake or Start events.
     
  15. Freaking-Pingo

    Freaking-Pingo

    Joined:
    Aug 1, 2012
    Posts:
    310
    I don't think I understand your note fully. I am well aware that the pooling can be done using GameObjects instead of Monobehaviours, but then I would be forced to use the GetComponent method each time I am required to access a script on the object. It all depends on how frequent GetComponent is to be called, but in our game we will have be using our object pooler a lot, as our game is a whack-a-mole type of game (A lot of objects appear and disappear frequently).

    So I am not sure what you mean when saying I am trying to solve a different problem. Guess I am just misinterpreting your note :)
     
  16. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    Creating and destroying GameObjects is an expensive operation that is a surefire way to trigger garbage collection, which is a big no-no, especially on mobile devices.

    Getting a reference to a MonoBehaviour through GetComponent is a far less expensive operation that has no effect on memory allocation at all.

    Two separate problems, two different optimization strategies. You can mash them into one if you decide that, in your particular case, you're going to assume that you only need to access one MonoBehaviour on a given game object, which is not the most general case since a GameObject can have multiple MonoBehaviours attached to it.

    In this particular example you cited, I think a more useful solution is to avoid calling GetComonent all together. Those objects that are being created and destroyed could be made autonomous, i.e. once created at a particular location, they should be able to control their own movement and lifetime on their own. If one of them is hit, then it can communicate that fact either through messaging or by calling a method on some central ScoreKeeper script. If one of them survives then it should be able to return itself to the object pool and hide itself from view.
     
    Freaking-Pingo likes this.
  17. Freaking-Pingo

    Freaking-Pingo

    Joined:
    Aug 1, 2012
    Posts:
    310
    I see where you are getting at. My solution solves a non-existent problem with the expense of a less general solution.
     
  18. Freaking-Pingo

    Freaking-Pingo

    Joined:
    Aug 1, 2012
    Posts:
    310
    Thank for your help guys :) After some struggling, playing around, I finally reached a solution I am satisfied with. The code work as followed. The base class Pool is an abstract class, which takes a generic type input and contains ordinary pooling methods. When inheriting from Pool, the child class have to implement three methods: GetPoolType which returns the proper object from a gameobject. ChangeObjectState, which allows the pool to activate and deactive the object from the base class, and IsObjectActive which return the current state of the item.

    Here is the code if anyone is interested:

    Base class Pool:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public abstract class Pool<PoolType> {
    6.  
    7.     protected List<PoolType> inactivePool = new List<PoolType>();
    8.     protected List<PoolType> activePool = new List<PoolType>();
    9.  
    10.     protected GameObject prefabReference;
    11.     protected Transform parentObject;
    12.  
    13.     public void InitializeObjectPool(GameObject prefabReference, int initialQuantity)
    14.     {
    15.         parentObject = new GameObject().transform;
    16.         parentObject.name = "Parent: " + prefabReference.name;
    17.  
    18.         this.prefabReference = prefabReference;
    19.         for (int i = 0; i < initialQuantity; i++)
    20.         {
    21.             PoolType item = InstantiateInstance();
    22.             inactivePool.Add(item);
    23.             ChangeObjectState(item, false);
    24.         }
    25.     }
    26.  
    27.     public virtual PoolType Get()
    28.     {
    29.         UpdatePools();
    30.  
    31.         if (inactivePool.Count > 0)
    32.             return GetInactiveObject();
    33.         else
    34.         {
    35.             PoolType item = InstantiateInstance();
    36.             activePool.Add(item);
    37.             return item;
    38.         }
    39.          
    40.     }
    41.  
    42.     protected virtual void UpdatePools()
    43.     {
    44.         for (int i = 0; i < activePool.Count; i++)
    45.         {
    46.             PoolType item = activePool[i];
    47.             if (!IsObjectActive(item))
    48.             {
    49.                 activePool.RemoveAt(i);
    50.                 inactivePool.Add(item);
    51.             }
    52.         }
    53.     }
    54.  
    55.     protected PoolType GetInactiveObject()
    56.     {
    57.         PoolType item = inactivePool[0];
    58.         inactivePool.RemoveAt(0);
    59.         activePool.Add(item);
    60.         ChangeObjectState(item, true);
    61.         return item;
    62.     }
    63.  
    64.     PoolType InstantiateInstance()
    65.     {
    66.         GameObject instantiatedObject = (GameObject)MonoBehaviour.Instantiate(prefabReference);
    67.         instantiatedObject.transform.parent = parentObject;
    68.         PoolType typeInstance = GetPoolType(instantiatedObject);
    69.         return typeInstance;
    70.     }
    71.  
    72.     protected abstract void ChangeObjectState(PoolType item, bool toState);
    73.     protected abstract bool IsObjectActive(PoolType item);
    74.     protected abstract PoolType GetPoolType(GameObject gameObject);
    75. }
    76.  
    Here is a type of pool that allows for monobehaviours:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class ObjectPool<PoolType> : Pool<PoolType> where PoolType : MonoBehaviour {
    5.  
    6.     public ObjectPool(GameObject prefabReference)
    7.     {
    8.         InitializeObjectPool(prefabReference, 0);
    9.     }
    10.  
    11.     public ObjectPool(GameObject prefabReference, int initialQuantity)
    12.     {
    13.         InitializeObjectPool(prefabReference, initialQuantity);
    14.     }
    15.  
    16.     protected override PoolType GetPoolType(GameObject gameObject)
    17.     {
    18.         PoolType type = gameObject.GetComponent<PoolType>();
    19.         return type;
    20.     }
    21.  
    22.     protected override void ChangeObjectState(PoolType item, bool toState)
    23.     {
    24.         item.gameObject.SetActive(toState);
    25.     }
    26.  
    27.     protected override bool IsObjectActive(PoolType item)
    28.     {
    29.         return item.gameObject.activeSelf;
    30.     }
    31. }
    32.  
    And if you only wants a pool for GameObjects only:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class GameObjectPool : Pool<GameObject>  {
    5.  
    6.     public GameObjectPool(GameObject prefabReference)
    7.     {
    8.         InitializeObjectPool(prefabReference, 0);
    9.     }
    10.  
    11.     public GameObjectPool(GameObject prefabReference, int initialQuantity)
    12.     {
    13.         InitializeObjectPool(prefabReference, initialQuantity);
    14.     }
    15.  
    16.     protected override GameObject GetPoolType(GameObject gameObject)
    17.     {
    18.         return gameObject;
    19.     }
    20.  
    21.     protected override void ChangeObjectState(GameObject item, bool toState)
    22.     {
    23.         item.SetActive(toState);
    24.     }
    25.  
    26.     protected override bool IsObjectActive(GameObject item)
    27.     {
    28.         return item.activeSelf;
    29.     }
    30. }
     
  19. janimator

    janimator

    Joined:
    Apr 5, 2013
    Posts:
    52
    Strangely I'm sorta following your shoes with this thread and came to the same conclusion as you. It would be nice to utilize interfaces, but I failed in my attempts. Base Pool Object class it is.