Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unlimited method arguments without GC

I am trying to make a function that can receive unlimited amount of arguments without crating GC.

I know that this can be done with the params keyword but it creates GC. Also understand that you can pass array to to the function but I want to know if it is possible to pass unlimited method arguments without creating GC and without creating array or list and passing it to the List.

This is the example with the param code:

void Update()
{
    GameObject player1 = GameObject.Find("Player1");
    GameObject player2 = GameObject.Find("Player2");

    GameObject enemy1 = GameObject.Find("Enemy1");
    GameObject enemy2 = GameObject.Find("Enemy2");
    GameObject enemy3 = GameObject.Find("Enemy3");

    Vector3 newPos = new Vector3(0, 0, 0);
    moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3);
}

void moveObjects(Vector3 newPos, float duration, params GameObject[] objs)
{
    for (int i = 0; i < objs.Length; i++)
    {
        //StartCoroutine(moveToNewPos(objs[i].transform, newPos, duration));
    }
}

When executed even with the StartCoroutine function commented out, it allocates 80 bytes. At first, I thought this was happening because I used the foreach loop then I changed that to a for loop but it still creating GC then I realized that params GameObject[] is causing it. See the Profiler below fore more visual information on this:

enter image description here

So, how can I create a method that takes unlimited arguments without generating GC?

Please ignore the use of GameObject.Find function used in the Update function. That's just used for an example to get the reference of Objects I want during run-time. I have a script implemented to handle that but not related what's in this question.

like image 488
PerformanceFreak Avatar asked Apr 21 '18 07:04

PerformanceFreak


People also ask

Can you create a function in C# which can accept varying number of arguments?

By using the params keyword, you can specify a method parameter that takes a variable number of arguments. The parameter type must be a single-dimensional array. No additional parameters are permitted after the params keyword in a method declaration, and only one params keyword is permitted in a method declaration.


2 Answers

If you use params and specify arguments in that way then yes, it will always create an array object.

If you want to avoid that, and if you don't need to worry about recursion or thread safety, you could keep a List<GameObject> around and use the same list multiple times. For example:

private readonly List<GameObject> objectListCache = new List<GameObject>();

private void Update()
{
    cachedObjectList.Clear();
    cachedObjectList.Add(GameObject.Find("Player1"));
    cachedObjectList.Add(GameObject.Find("Player2"));
    cachedObjectList.Add(GameObject.Find("Enemy1"));
    cachedObjectList.Add(GameObject.Find("Enemy2"));
    cachedObjectList.Add(GameObject.Find("Enemy3"));

    Vector3 newPos = new Vector3(0, 0, 0);
    moveObjects(newPos, 3f, cachedObjectList);
    cachedObjectList.Clear();
}

void MoveObjects(Vector3 newPos, float duration, List<GameObject> objs)
{
    foreach (GameObject obj in objs)
    {
        // ...
    }
}

When you clear the List<T> that will set all the elements of the internal buffer to null, but won't discard the buffer - so it can be used again for the next Update call without any allocation.

like image 103
Jon Skeet Avatar answered Oct 07 '22 15:10

Jon Skeet


Yes, it is possible to create a function with unlimited arguments without causing memory allocation.

You can do this with the undocumented __arglist keyword and wrapping our unlimited params inside it.

Change your moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3) to moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3)).

In the moveObjects function, replace params GameObject[] objs with __arglist. Put the __arglist in ArgIterator then loop over it until ArgIterator.GetRemainingCount is no longer more than 0.

To obtain each value from the arguments in the loop, use ArgIterator.GetNextArg to get the TypedReference and then TypedReference.ToObject to cast the object to the Object type passed in the parameter which is GameObject in your example.

The whole changes together:

void Update()
{
    GameObject player1 = GameObject.Find("Player1");
    GameObject player2 = GameObject.Find("Player2");

    GameObject enemy1 = GameObject.Find("Enemy1");
    GameObject enemy2 = GameObject.Find("Enemy2");
    GameObject enemy3 = GameObject.Find("Enemy3");

    Vector3 newPos = new Vector3(0, 0, 0);
    moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3));
}

void moveObjects(Vector3 newPos, float duration, __arglist)
{
    //Put the arguments in ArgIterator
    ArgIterator argIte = new ArgIterator(__arglist);

    //Iterate through the arguments in ArgIterator
    while (argIte.GetRemainingCount() > 0)
    {
        TypedReference typedReference = argIte.GetNextArg();
        object tempObj = TypedReference.ToObject(typedReference);

        GameObject obj = (GameObject)tempObj;
        //StartCoroutine(moveToNewPos(obj.transform, newPos, duration));
    }
}

While this should solve your problem, it's worth noting that it's an undocumented feature which means that it may stop working someday. If you care about that then an array should be used.

EDIT:

John Skeet mentioned about possible incompatibility on some platforms. I did a test again and it works on all the devices I tested it on. I did a test on both Windows and Android and it worked on both Windows and Android. I also expect it to work on iOS too. Too lazy to switch to Mac then fiddle with Xcode to test but there shouldn't be a problem.

Note you must use .NET>=4.6 to get this working

To do that:

1.Go to the Player Settings, change Scripting Runtime Version to "Experimental (.Net 4.6 Equivalent)"

2.Change Api Compatibility Level to .NET 4.6.

3.Change Scripting Backend to Mono instead of IL2CPP. IL2CPP is not supported because Unity did not implement this on it.

like image 37
Programmer Avatar answered Oct 07 '22 15:10

Programmer