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:
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With