- Home /
Massive resource overhead with StartCoroutine
This problem began when I decided to convert my existing project from JS to C# so forgive me if I'm overlooking something blatantly obvious. I'm still somewhat new to doing things this way. I have a set of objects that need to be instantiated at given intervals with a delay in between each set of objects spawned. This is the relevant part of my current code:
private void Update (){
if (setCreepSpawn) {
if (!initialSpawned) {
if ((Time.time - creepInitialTimer) >= 10) { //Initial delay to spawn creeps
StartCoroutine(CreepSpawnTimer());
initialSpawned = true;
creepWaveTimer = Time.time;
}
}
else {
if ((Time.time - creepWaveTimer) >= 40) { //Delay between waves
StartCoroutine(CreepSpawnTimer());
creepWaveTimer = Time.time;
}
}
}
}
private IEnumerator CreepSpawnTimer (){
for (x = 0; x < 3; x++) {
Network.Instantiate(creepMid, creepSpawnMid.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepMid2, creepSpawnMid2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepBot, creepSpawnBot.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepBot2, creepSpawnBot2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepTop, creepSpawnTop.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepTop2, creepSpawnTop2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
yield return new WaitForSeconds(1);
}
for (x = 0; x < 3; x++) {
Network.Instantiate(creepCasterMid, creepSpawnMid.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterMid2, creepSpawnMid2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterBot, creepSpawnBot.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterBot2, creepSpawnBot2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterTop, creepSpawnTop.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterTop2, creepSpawnTop2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
yield return new WaitForSeconds(1);
}
}
This is obviously using some hardcoded values so ignore the poor practice for the time being. It won't remain that way forever :P What I understand to be happening is that the coroutines are being held open for so long as the instantiated object exists. After the second set of objects spawn my framerate drops to below 20. By the third wave it's <1. Is there any way to use a coroutine to set a delay but not actually do the logic within the IEnumerator function?
Since coroutines return instantly I can't just spawn the objects outside in another function and use the IEnumerator simply as a single statement to 'yield return WaitForSeconds(1)'.
Answer by Bunny83 · Dec 10, 2012 at 11:26 PM
A coroutine does not persist when it's finished, that's a fact. However, i'm not sure if it really can finish. It seems you use a class variable (x) as for-loop-variable. This is in general a very bad idea and in the case of generator functions (IEnumerators) even worse.
A coroutine is not a function, it's an object. This object is stored in Unity's coroutine scheduler where it get processed. When an IEnumerator has "enumerated" it's "collection" (in the case of a coroutine, "code fragments") it is removed from the scheduler because you can't do anything with it once it's finished. Usually the IEnumerator interface forces you to implement a "Restart" method, however generator functions don't implement it.
It makes absolutely no sense why objects should be deleted when you stop the coroutine. I guess your external variable reference totally breaks the coroutine.
Always use local variables as for-variable:
private IEnumerator CreepSpawnTimer()
{
for (int x = 0; x < 3; x++)
{
Network.Instantiate(creepMid, creepSpawnMid.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepMid2, creepSpawnMid2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepBot, creepSpawnBot.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepBot2, creepSpawnBot2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepTop, creepSpawnTop.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepTop2, creepSpawnTop2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
yield return new WaitForSeconds(1);
}
for (int x = 0; x < 3; x++)
{
Network.Instantiate(creepCasterMid, creepSpawnMid.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterMid2, creepSpawnMid2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterBot, creepSpawnBot.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterBot2, creepSpawnBot2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterTop, creepSpawnTop.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
Network.Instantiate(creepCasterTop2, creepSpawnTop2.position + new Vector3(Random.Range(-8,8), 0, Random.Range(-8,8)), Quaternion.identity, 0);
yield return new WaitForSeconds(1);
}
}
+1 to bunny for displaying the most knowledge on coroutines i have yet seen.
Anyways @OP ive been doing some elaborate stuff with coroutines,thats a bad idea, the more complicated you get with them the more impossible your errors become, right now i have code thats plagued with random multiple function calls from a source that only calls once ever.
The problems seem to come about most frequently in coroutines that instantiate objects and are made worse if you call functions on those objects in the same routine.
from what ive learned of coroutines, they work by taking a snapshot of all variables they need to function and locking them in with the function. So taking bunny's point in to consideration I believe that the more external variables and objects you create during the coroutine and sub sequentially reference causes the coroutine to break down and perform erratically.
$$anonymous$$arked this as correct because it seems to be the "most" correct out of all the solutions I've tried. Changing the loops to not use class variables did reduce the performance overhead, but it didn't completely remove it. Where the game use to lag after 72 objects were spawned, it now lags at around its peak of 100. This is an edge case, so it may make more sense for me to change the rest of the game to work around this limitation anyway.
I still find it odd that there is no performance 'cliff' when this is done without using coroutines. I feel as if there may still be something else that could be done.
honestly its partly because of coroutine, thank god for Bunny, I was starting to worry that I had the most knowledge about unity's coroutines around here (which is a grimm perspective). As ive said ive been having mad problems with them, and have been asking for weeks around here now about them in depth, so far this is the most info ive gotten here, the rest i had to ask CS professors and even they were speculative but definitely more informative.
ive been considering this idea for awhile now, if you would like to guinea pig it for me that would be great. $$anonymous$$ove all your instantiation calls and reference to an outside non coroutine function and see if your ODD problems still persist.
However if its just lag it could more likely be draw calls, physics calcs, and max amount of verts, that are tanking your frame rate.
coroutines are a bit tricky, yes ;) The problem is that what they do in the background is something completely different from what you would expect. Another thing is that, apart from the generator function thing, we don't know for sure how Unity's coroutine scheduler works internally. Usually the concept is quite simple, but we already discovered really strange things when a coroutine waits for another one and one of them is stopped with StopCoroutine.
If you really got very strange behaviour and you're not too scared to look at the IL code that is generated by the compiler, you can use ILSpy to see what the compiler has generated. Note: usually most classes can be decompiled to C# but generator functions in Unity uses a goto instruction which isn't possible that way in C#. So you can only view the coroutine class in CIL (common intermediate language).
Also if you want to understand how a generator function works behind the scenes, here's a great custom coroutine scheduler which would work even outside Unity in a normal C# program. It's a bit more simplified but in my opinion more robust than Unity's ;)
At the end of the description the author posted a link to his blog article about coroutines which is really great.
Answer by Dave-Carlile · Dec 10, 2012 at 09:49 PM
The coroutine shouldn't stay around once it's done running. Even if it did, there isn't really much overhead, so I would be surprised if that's your issue.
The coroutine is generating 36 networked objects each time it runs to completion - looks like it takes 6 seconds to do that. Not sure what you mean by "second" and "third" wave. If it's 36 additional objects each time, that would give you 72-108 networked objects. I would guess your problem lies their rather than the coroutine.
But, you should profile to find where your code is running slow. Guessing is a poor use of your time.
The problem is that the coroutine does stay around after it is done running. There was no guessing involved in that area... If I perform the above code with a string StartCoroutine call (ie: StartCoroutine("CreepSpawnTimer")), and then call StopCoroutine on it later, the objects instantiated within the IEnumerator function disappear.
"Waves" was meant to reference each call of the CreepSpawnTimer() function. 6 of these objects are spawned for each 'lane' in the game, totaling 36 per wave every 40 seconds after the initial delay. These objects do not last for very long (they fight eachother/players). At any given time there should be no more than 72 out at a time with the absolute upper end at around 100. Each object also has hardly any visual overhead for the time being (they're cubes). I have attempted disabling every other functionality of these objects including attached scripts prior to making this question. Spawning 72 networked rigidbody cubes with absolutely zero added functionality using the above methodology causes extreme framerate lag.
I actually managed to avoid the problem entirely (albeit in a terrible manner) by checking Time.time vs a few secondary time variables within Update() in order to simulate the time delay. The result is basically the same, but the code is ugly as anything.
So spawning the same number without the coroutine doesn't cause performance problems? $$anonymous$$aybe I'm completely missing something about coroutines, but I don't see how they would be tied to the objects they spawn in any way. And the iterator should go away once it's complete.
That appears to be the case. I have no performance loss when the same action is performed without a coroutine call or if the objects are simply placed on to the scene without a script.
Your answer
Follow this Question
Related Questions
Allowing only one jump? 2 Answers
Using WaitForSeconds and trigger 1 Answer
c# waitforsecconds 2 Answers
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers