- Home /
Coroutine does not stop when having a coroutine inside.
Inside of my combat logic for my AI I use coroutines to time the behaviour. Basically I start a random coroutine containing one behaviour, wait, then stop the coroutine and repeat. However the stop coroutine part does not work for one behaviour which has another coroutine inside. This results in stacking those coroutines over time. Thus after a while I get an almost constant stream of bullets.
While i was able to work arround this issue by merging both coroutines I am still curious to find out why my initial approach did not work. I am still learning so every help at improving my comprehension is highly appreciated. Thanks in advance.
The essential parts of my code are the following:
function Start(){
Combatlogic();
}
function CombatLogic() : IEnumerator{
while(true){
if(!sleeping){
yield ChooseBehaviour(healthRatio);
}
yield;
}
}
function ChooseBehaviour(modi : float) : IEnumerator{
//some code
}else if (** randomized condition **){
//Debug.Log("Used Fire");
StartCoroutine( "executeShootNeedle");
yield WaitForSeconds(needleFireTime);
StopCoroutine( "executeShootNeedle");
}
//some more code
}
function ShootNeedle() : IEnumerator{
yield WaitForSeconds(needleAim);
//attack code
yield WaitForSeconds( needleDelay);
}
function executeShootNeedle() : IEnumerator{
while(true){
fireTransform.LookAt(target.position);
yield ShootNeedle();
}
}
Answer by kmeboe · Sep 29, 2012 at 12:19 AM
Actually, I don't think Bunny's answer is quite correct. I've turned your example into the equivalent c#, and have it posted below (hence the need for an answer, with formatting, rather than a comment). You can create a new project and attach this script (as "test.cs") to the main camera, and see the original behavior.
using UnityEngine; using System.Collections;
using System.Collections.Generic;
public class test : MonoBehaviour
{
void Start()
{
StartCoroutine(Example());
}
IEnumerator Example() {
StartCoroutine("executeShootNeedle");
yield return new WaitForSeconds(10);
print("Attempting to stop the coroutine...");
StopCoroutine("executeShootNeedle");
}
IEnumerator ShootNeedle()
{
print ("Aim...");
yield return new WaitForSeconds(3);
print ("Fire!");
yield return new WaitForSeconds(1);
}
IEnumerator executeShootNeedle()
{
while(true){
print ("Starting firing sequence.");
yield return StartCoroutine(ShootNeedle());
}
}
}
What's very interesting to note is this: StopCoroutine() is clearly getting called; however, executeShootNeedle() (and therefore ShootNeedle()) continues operating after this. If what Bunny said were true, ShootNeedle() should continue running, but executeShootNeedle() should stop running. As we can see from the logs, this is not the case.
The conclusion is that StopCoroutine() is not functioning at all. According to how I interpret the docs, this is a bug. I wonder what's going on under the covers?
Edit:
In addition, I should also point out that if you simply remove the "yield return StartCoroutine..." call from executeShootNeedle(), the stop works correctly. For example:
IEnumerator executeShootNeedle()
{
while(true){
print ("Starting firing sequence.");
yield return new WaitForSeconds(2);
}
}
Another Edit: another thing I tried was to add a "yield return 0;" after the call to ShootNeedle(), like so:
IEnumerator executeShootNeedle()
{
while(true){
print ("Starting firing sequence.");
yield return StartCoroutine(ShootNeedle());
yield return 0;
}
}
My thinking was that perhaps "yield return StartCoroutine()" didn't give StopCoroutine() a chance to actually stop the coroutine. But even with this addition, StopCoroutine() has no effect on anything.
That's actually really a strange behaviour ;) The StopCoroutine does work, but it only does when the inner coroutine is also stopped... It actually makes no sense...
Test it with those functions:
IEnumerator Example()
{
StartCoroutine("executeShootNeedle");
yield return new WaitForSeconds(10);
print("Attempting to stop the coroutine...");
StopCoroutine("executeShootNeedle");
yield return new WaitForSeconds(3);
print("stop ShootNeedle ...");
StopCoroutine("ShootNeedle");
}
IEnumerator executeShootNeedle()
{
while(true)
{
print ("Starting firing sequence.");
yield return StartCoroutine("ShootNeedle");
}
}
To be honest, i never use StopCoroutine since i never use StartCoroutine with a string. Stopping a coroutine falls in the same category as goto-statements imo.
Anyway, the behaviour doesn't make any sense at all. Feel free to file a bug report :)
Thank you for your answer. I tested it again and sent a bugreport to unity.
Crazy stuff. I probably wasted way too much time researching this. Oh well...I feel like I know more about coroutines now. And maybe Bunny's advice is good: stay away from StopCoroutine, and all is well with the world. :D
Although goto...I have to admit, I love goto in C++. A strategically-placed goto can be great. <puts up fists>
Answer by Bunny83 · Sep 28, 2012 at 11:52 PM
Sure, each coroutine runs on it's own. When you start a coroutine and yield on this coroutine the calling coroutine just waits for the "nested" coroutine to finish. When you stop the calling coroutine it won't stop the coroutine you started in this coroutine.
The best fix would be to not use a coroutine for ShootNeedle and place the yields in ExecuteShootNeedle:
function ShootNeedle(){ // Not a coroutine anymore
//attack code
}
function executeShootNeedle() : IEnumerator{
while(true){
fireTransform.LookAt(target.position);
yield WaitForSeconds(needleAim);
ShootNeedle(); // just call it as normal function
yield WaitForSeconds( needleDelay);
}
}
Stopcoroutine can only halt a coroutine while it's yielding. Keep in mind Unity don't uses threads for the scripting. All your scripts and coroutines are running sequential in the main-thread. Whenever a coroutine yields another coroutine or just the gameloop can continue.
Thank you for your answer. I understand that StopCoroutine would not stop the nested coroutine. I wanted the last initiated attack to finish when the executeShootNeedle coroutine stopped. But what exactly do you mean by "Stopcoroutine can only halt a coroutine while it's yielding"? I thought this would be the case when waiting for ShootNeedle to finish.
I also don't know about threads. When I started using unity my program$$anonymous$$g skill were zero. I learned everything from tutorials and the documentations. So unforunately I don't have a deeper understandig of what runs behind the scene. But I am eager to learn more (thats basically the reason I asked this question).
See below...the mystery continues (imo).
As far as threads go: coroutines do not represent different threads, but rather they are simply a way to delay and/or reorder execution. From what I understand, all code still runs on a single thread (as Bunny said).