1

I am using static lists of classes, and am trying to simplify or genericize the functions for ease of typing and copy/pasting. I'll try to explain the best I can:

public static List<Resources> allResources = new List<Resources>();
public static List<Resources> allIron = new List<Resources>(); // Iron.cs is child
public static List<Resources> allCoal = new List<Resources>(); // Coal.cs is child

public Iron GetIron(Vector3 position)
    {
        if (allIron.Count > 0)
        {
            for (int i = 0; i < allIron.Count; i++)
            {
                if (!allIron[i].gameObject.activeSelf)
                {
                    allIron[i].trans.position = position;
                    allIron[i].gameObject.SetActive(true);
                    return (Iron)allIron[i];
                }
            }
        }
        Instantiate(prefabIron, position, Quaternion.identity, oreHolder.transform);
        return (Iron)allIron[allIron.Count - 1];
    }

So using this way of Object pooling, in order to have a function that does this, I would have to copy paste this function for each new resource, and change each call to the particular list. What I would like to do, but can't seem to manage, is this:

public Resource GetResource(List<Resource> resources, Vector3 position, GameObject prefab)
    {
        if (resources.Count > 0)
        {
            for (int i = 0; i < resources.Count; i++)
            {
                if (!resources[i].gameObject.activeSelf)
                {
                    resources[i].trans.position = position;
                    resources[i].gameObject.SetActive(true);
                    return resources[i];
                }
            }
        }
        Instantiate(prefab, position, Quaternion.identity, oreHolder.transform);
        return resources[resources.Count - 1];
    }

But when trying to simplify a function like this(way less typing), it doesn't work. Passing a List into a function basically just copies it to be used within the function, and doesn't change the actual Lists entities, even though being static.

So my question is, is this at all possible? Or am I stuck with making a function for each resource, and living in copy/paste hell? Or even better, would there be a way to just make one function that can do it all? All my attempts have failed, any help or insight would be greatly appreciated, cheers!

  • I would like to note, that I could just for-loop through "allResources" since that list contains all of the child classes. However knowing that each new "Get" call is iterating through possibly (100,000 to infinity) would grind my gears just knowing the code is doing that in the background. This is why I made particular lists of each resource, that would cut the iterations down to a measly 10,000, when just looking for a particular item in game.

  • My question actually answered my question. My answer is the last post(website won't allow me to accept my own answer for 3 days). Special thanks to all who replied, you guys are awesome!

WideEyeNow
  • 19
  • 3
  • You can pass the `List resource` with ref keyword. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref – Kundan Singh Chouhan Mar 12 '23 at 16:20
  • @KundanSinghChouhan Thanks for the reply! I have tried to "ref" but in doing so it doesn't allow the call of "gameObject", "transform" or any other Unity.MonoBehaviour calls. So this may be just a UnityEngine thing, but I can't seem to get any values or variables within the class that the list is made up of. I will continue to experiment with this method. Thanks again for your help! – WideEyeNow Mar 12 '23 at 16:35
  • 1
    There's absolutely no need to use the `ref` keyword. List is a reference type so changes to the list itself will be observed outside of the function. You'd only need `ref` if reassigning the variable (`resources = new List()`) and wanting that to reflect outside the function. – pinkfloydx33 Mar 12 '23 at 19:13
  • @pinkfloydx33 Ohhh, I think I see what you're saying. Yeah, I've never had to use ref yet, and reading the documentation was a bit numbing, lol. But for some reason my first attempt didn't work, so I mistakenly took the way the List was fed into the function, as it not using it properly. And after playing with it more, I found that it actually does work(in my case, answer is at bottom), so learned something new today :D But thanks for the information! – WideEyeNow Mar 12 '23 at 19:29

3 Answers3

2

Looks like you should consider using generic method constrained to T : Resources.

There are multiple approaches to this, one could be using Dictionary<Type, List<Resources>> - something along this lines:

public static Dictionary<Type, List<Resource>> ByType = new ();
    
public T Get<T>(Vector3 position) where T : Resource
{
    if (ByType.TryGetValue(typeof(T), out List<Resource> ts) && ts.Any())
    {
        for (int i = 0; i < ts.Count; i++)
        {
            if (!ts[i].gameObject.activeSelf)
            {
                ts[i].trans.position = position;
                ts[i].gameObject.SetActive(true);
                return (T)ts[i];
            }
        }
    }
    // TODO: get prefab of corresponding type, 
    // similar approach can be used - create map from type to prefab for supported types:
    var prefab = ...;
    // make Instantiate generic too? or calculate the type from prefab
    Instantiate<T>(prefab, position, Quaternion.identity, oreHolder.transform); 
    ts = ByType[typeof(T)];
    return (T)ts[ts.Count - 1];
}

Notes:

  • I'm not very familiar with concurrency model of Unity and this code is very unsafe in multithreaded environment. Possibly use corresponding collections from System.Collections.Concurrent
  • Possibly precreate the dictionary(s) for all supported types
  • Also you can make this implementation internal and expose only typed ones like GetIron which will just do Get<Iron> (possibly can pass needed prefab too, also in this case you can keep approach with separate collections and pass them into the Get i.e. GetIron() => Get(allIrons, ironPrefab))
  • Also you can look for ideas in DefaultObjectPool implemented for .NET
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks for the reply! I'm not too familiar with the method you show, so I will need to play around with it. The prefab is just a GameObject, so I can easily add: (Vector3 position, GameObject prefab) to the function call. The class itself when created, in it's Awake() call is what adds it to the static list. So that's just something I didn't think to add to the second part, sorry. But truly, thank you :D – WideEyeNow Mar 12 '23 at 16:51
  • 1
    Return type should be `T`, not `Iron` – pinkfloydx33 Mar 12 '23 at 19:14
  • 1
    Your code still won't work though. The dictionary holds List and not List. At the very least your `out` declaration is bad. Fixing that, the casts now make sense. But on mobile so not sure if anything else is amiss – pinkfloydx33 Mar 12 '23 at 19:38
0

Not sure how it didn't work the first time, but the second snippet was correct, and the code works beautifully. Here is a better explanation that you can test for yourself:

// Main.cs
public static List<Resources> allResources = new List<Resources>();
public static List<Resources> allIron = new List<Resources>(); // Iron.cs is child
public static List<Resources> allCoal = new List<Resources>(); // Coal.cs is child

public Resources GetResource(List<Resources> resources, Vector3 position, GameObject prefab)
    {
        if (resources.Count > 0)
        {
            for (int i = 0; i < resources.Count; i++)
            {
                if (!resources[i].gameObject.activeSelf)
                {
                    resources[i].trans.position = position;
                    resources[i].gameObject.SetActive(true);
                    return resources[i];
                }
            }
        }
        Instantiate(prefab, position, Quaternion.identity, oreHolder.transform);
        return resources[resources.Count - 1];
    }

So by having static List's of classes, and also having the "Get" call within same script, somehow the List is able to be passed without "copying" itself to be used within the function. So no special calls are needed. And during Instantiate() the Awake() method of Iron.cs is called within the same frame, so as you see Iron.cs adds itself to the List being called , then the next line of code gets it's index and returns the correct script.

// Resources.cs is child of Main.cs

// Iron.cs (child of Resources.cs)

public float amount;

void Awake()
{
    allIron.Add(this);
}

^ as you can see, Iron.cs adds itself to the static List(instantly).

Then a simple call of GetResource() is used where needed. And since each script is a child of the previous, leading back to Main.cs, no other "Get" calls are needed. Especially not GetComponent() which is a performance killer, or worse yet a serious performance killer of an interface(IGetResource.cs).

// Drill.cs (also child of Main.cs)

    if (isPowered)
    {
        timer++;

        if (timer > tick)
        {
            Iron iron = (Iron)GetResource(allIron, pos, prefabIron);

            iron.amount = 55.5f;

            inventory.Add(iron);

            timer = 0;
        }

    }

But as I said please test for yourself, and better yet use a benchmark test(min 100k objects). You will clearly see that this is the fastest and most performant way to do this :)

WideEyeNow
  • 19
  • 3
  • In your function, if there is nothing in `resources` you call `Instantiate()`, but then return the last item in `resources`. How does `Instantiate()` know which list to add to if you're not passing a List?... – Idle_Mind Mar 12 '23 at 19:52
  • The prefab of Iron(prefabIron) is a gameObject with the script of Iron.cs. It is not included in the simple example I gave, but to test simply make any object with Iron.cs script, and put in within the function call for prefab. Resource.cs is just a parent, so you can call any of it's children within one function. Also can be greatly used to type one function for any resource, within Resource.cs, and simply call that function within any of it's children. This method of code structure, to what I know it as, is Static Inheritance. – WideEyeNow Mar 13 '23 at 14:48
  • Ohh, you mean how does it know that it is last in list. Simple, when you declare Instantiate, that objects Awake() method gets called during that time frame. So right when it is made, it immediately adds itself to the appropriate list, in this case "allIron". Then, the next line of code is read, which is allIron.Count - 1 – WideEyeNow Mar 13 '23 at 15:01
  • A List is being passed, if you look in Drill.cs it calls (GetResource(allIron)). But that was the problem I had to why I asked the question, how to make a function accept a List, and use it appropriately. For some reason all my previous attempts have failed, but must of had a wrong variable somewhere before. But it all works now :D – WideEyeNow Mar 13 '23 at 15:23
0

I would keep one list, and use .OfType<>() extension to filter for specific classes.

For example

static class Program
{
    static List<Resource> resources = new List<Resource>();

    static void Main(string[] args)
    {
        List<Coal> coal = resources.OfType<Coal>().ToList();
    }
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • This(my) particular code is set in Unity Engine, as I see you show plain C#. But no offense, you are saying to make an extra call, to what I previously can call directly. The less code needs to think, it what makes it more performant. But I had wondered how "OfType<>" was best written out, thanks for the information, cheers! – WideEyeNow Mar 13 '23 at 14:59
  • Also would like to note, you are saying iterate through the list of allResources, basically saying iterate through what could be 1 million entities. By pre-separating them when they are created, the iteration count would only be the max of what the number of that resource, to which could be only 1,000 iterations. So I wouldn't suggest iterating all, unless absolutely needed(which is why I do have a allResources) in case. – WideEyeNow Mar 13 '23 at 16:12
  • Well yes, keeping one list only makes sense if the count is low enough not to hurt performance. I just have had the same issue in the past, and this provides some mechanism for type-safe object enumeration. – John Alexiou Mar 13 '23 at 17:41
  • If performance is critical then do not create a class hierarchy with `Resource` and just keep an `enum` designating the type of resource and use a bunch of `switch` statements to handle the specifics. This will outperform any other solution by orders of magnitude. – John Alexiou Mar 13 '23 at 17:42
  • I'm not sure I follow? But with hierarchy, not only can I easily call anything further up the chain of inheritance easily, but it also allows me to use one function like the one mentioned. So instead of being specific, and needing a function for each(Iron, Coal, etc), I can simply just say Resource in it's place to mean any. I do use an enum for type, in separation checks(like when resource goes into inventories), as I found that to be the most performant way to check against. What do you mean, exactly(in reference to not using inheritance)? – WideEyeNow Mar 14 '23 at 19:36