How to wait Coroutine callback is executed? - c#

I want to wait StartCoroutine callback is executed.
Anyone knows how to do this?
public float getXXX() {
var result;
StartCoroutine(YYY((r) => result = r)); // how to wait this?
return result;
}
private IEnumerator YYY(System.Action<float> callback) {
LinkedList<float> list = new LinkedList<float>();
while(timeleft > 0) {
timeleft -= Time.deltaTime;
list.add(transform.position.magnitude);
yield return new WaitForSeconds (WAITSPAN);
}
callback(list.max());
yeild return true;
}

You can't and shouldn't try to wait or yield for a coroutine function to return from non coroutine function (getXXX function). It will block in that non coroutine function until this function returns preventing other Unity scripts to run.
To wait for a coroutine function(YYY) in the getXXX function, you must also make the function you are making the call and waiting from in a coroutine function. In this case this is theYYY function, so that should be a corutine function too then you can yield it:
public IEnumerator getXXX()
{
float result = 0f;
yield return StartCoroutine(YYY((r) => result = r)); // how to wait this?
//Use the result variable
Debug.Log(result);
}
OR
If you don't want to make the getXXX function a a coroutine function then don't try to wait there. You can still use the result from the YYY coroutine function but don't try to return the result. Just use it to do whatever you want to do in that function:
public void doSomethingXXX()
{
StartCoroutine(YYY((result) =>
{
//Do something with the result variable
Debug.Log(result);
}));
}
The idea of using coroutine is to be able to do something over multiple frames. The void function will just do that in one frame. You can't yield/wait in a void or non IEnumerator/coroutine functio.

You can only wait inside a coroutine. To do this, your getXXX() method should also be a coroutine. Something like this:
public float someOtherMethod()
{
float result;
StartCoroutine(getXXX(out result));
return result;
}
IEnumerator getXXX(out float result)
{
//more code here...
yield return StartCoroutine(YYY((r) => result = r));
//more code here...
}
IEnumerator YYY(System.Action<float> callback)
{
//your logic here...
}

I found something you may be able to call the class and use the methods inside this code and modify to use this is very similar to your code but less complex:
using UnityEngine;
using System.Collections;
public class WaitForSecondsExample : MonoBehaviour
{
void Start()
{
StartCoroutine(Example());
}
IEnumerator Example()
{
print(Time.time);
yield return new WaitForSeconds(5);
print(Time.time);
}
}
this code was taken from: https://docs.unity3d.com/ScriptReference/WaitForSeconds.html
from there examples
The 5 is the amount of time it will wait unless you want to do this dynamically based upon something. Depends on what you want to do. However notice how they are calling the method Example() inside the StartCoroutine(Example()) and which will go to the IEnumerator Example() and within there you have WaitForSeconds(5); this will make the StartCoroutine wait for 5 seconds. This can be hardcoded or made to wait dynamically by calling another method within that class from within IEnumerator Example() this is just one of many ways you can attack this. Again depends on what you want to do. Actually, you might even be better off making this into a method that passes a value in to the method each time something like
IEnumerator Example(float flSeconds)
{
print(Time.time);
yield return new WaitForSeconds(flSeconds);
print(Time.time);
}
this way you can pass what is in your
LinkedList list = new LinkedList();
Every time

Related

while loop inside a coroutine in Unity

I have two scripts. The first script gets called in the beginning as follows:
Script1.cs
private Script2 script2;
void Start () {
script2= (Script2) GameObject.FindObjectOfType (typeof (Script2));
StartCoroutine("CallTrigger");
}
IEnumerator CallTrigger() {
while(script2.hasTriggered == true){
Debug.Log("Success");
script2.hasTriggered = false;
}
yield return 0;
}
And my script 2 is as follows:
public bool hasTriggered = false;
Since my 1st script gets called first, I want the CallTrigger() function to wait till the bool in script2 is set to true. Unfortunately while() is not the right way I suppose since it is not working for me. I know the best way is to use Update() but I am using multiple instances of this script from which only some get called in the beginning.
So how do I make my CallTrigger await till the hasTriggered in script2 is set to true?
I want the CallTrigger() function to wait till the bool in script2 is set to true
You can simply use WaitUntil
IEnumerator CallTrigger()
{
yield return new WaitUntil(() => script2.hasTriggered);
Debug.Log("Success");
}
which basically equals doing something like
IEnumerator CallTrigger()
{
// As long as the flag is NOT set
while(!script2.hasTriggered)
{
// wait a frame
yield return null;
}
Debug.Log("Success");
}
You could also btw directly make it
// Yes, if Start returns IEnumerator Unity automatcally runs it as a coroutine
IEnumerator Start()
{
// Rather use the generic versions
script2 = GameObject.FindObjectOfType<Script2>();
yield return new WaitUntil(()=> script2.hasTriggered);
Debug.Log("Success");
}

Unity StopCoroutine does not stop coroutines when there are multi-layer (or nested) "yield return"

If I use one-layer yield return, StopCoroutine() can successfully stop my coroutine. See the code example below...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestStopcoroutine : MonoBehaviour {
IEnumerator co = null;
// Use this for initialization
void Start () {
co = FunA();
StartCoroutine(co);
}
private IEnumerator FunA() {
Debug.Log("Enter FunA...");
yield return RepeatPrint();
Debug.Log("FunA end...");
}
private IEnumerator RepeatPrint() {
for (int i = 0; i < 5; i++) {
Debug.Log(i);
yield return new WaitForSeconds(1);
}
}
/// <summary>
/// Set this function to a button on UI Canvas
/// </summary>
public void OnCancelButtonClick() {
if (co != null) {
StopCoroutine(co);
Debug.Log("Stop Coroutine...");
co = null;
}
}
}
this output is...
// Enter FunA...
// 0
// 1
// 2
// 3
// Stop Coroutine...
However, if I add one layer (i.e.FunB()), FunA() will be stopped but the inside coroutine(FunB()) will not be stopped. See the example code below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestStopcoroutine : MonoBehaviour {
IEnumerator co = null;
void Start () {
co = FunA();
StartCoroutine(co);
}
private IEnumerator FunA() {
Debug.Log("Enter FunA...");
yield return FunB();
Debug.Log("FunA end...");
}
private IEnumerator FunB () {
Debug.Log("Enter FunB...");
yield return RepeatPrint();
Debug.Log("FunB end...");
}
private IEnumerator RepeatPrint() {
for (int i = 0; i < 5; i++) {
Debug.Log(i);
yield return new WaitForSeconds(1);
}
}
/// <summary>
/// Set this function to a button on UI Canvas
/// </summary>
public void OnCancelButtonClick() {
if (co != null) {
StopCoroutine(co);
Debug.Log("Stop Coroutine...");
co = null;
}
}
}
this output is...
// Enter FunA...
// Enter FunB...
// 0
// 1
// 2
// Stop Coroutine...
// 3
// 4
// FunB end...
Therefore, I am wondering why StopCoroutine() cannot successfully stop multi-layer yield return coroutine??
For your second code, the log should indeed end at Stop Coroutine.....
There are three possibilities to why it is showing the current output: (Very likely in order why this is happening)
1. You are setting Time.timeScale to 0. Search in your whole project and make sure that you're not doing this: Time.timeScale = 0;. This can pause or upause a coroutine function that's waiting with WaitForSeconds. If you did, temporary remove or comment it out and see if it's the issue.
2. Your project is corrupted and there is now a bug in it. Sometimes, a bug can randomly happen in a Unity project and the only way to fix that is to create a new project and manually move the resources from the old project to the new one.
Create a new project and test your modified code below.
3. A bug with Unity itself. Since you're using Unity 5.6.4p3, there is chance this is bug with Unity. If doing what's in #1 and #2 did not solve your issue then simply update Unity to the latest version (Unity 2018.xxx). This is more likely to fix your issue and don't forget to test with a new project instead of importing the old one.
Use the modified code from your question below to test #2 and #3. It uses the Invoke function to stop the coroutine after one second.
IEnumerator co = null;
void Start()
{
co = FunA();
Invoke("OnCancelButtonClick", 1f);
StartCoroutine(co);
}
private IEnumerator FunA()
{
Debug.Log("Enter FunA...");
yield return FunB();
Debug.Log("FunA end...");
}
private IEnumerator FunB()
{
Debug.Log("Enter FunB...");
yield return RepeatPrint();
Debug.Log("FunB end...");
}
private IEnumerator RepeatPrint()
{
for (int i = 0; i < 5; i++)
{
Debug.Log(i);
yield return new WaitForSeconds(1);
}
}
/// <summary>
/// Set this function to a button on UI Canvas
/// </summary>
public void OnCancelButtonClick()
{
if (co != null)
{
StopCoroutine(co);
Debug.Log("Stop Coroutine...");
co = null;
}
}
The expected output:
Enter FunA...
Enter FunB...
0
Stop Coroutine...
To address KYL3R's statement in his answer that stopping the main Coroutine won't affect any other coroutine, that has already been started. This is partially true but totally depends on how and where the coroutine was started.
There are two ways to start a coroutine function:
1. Start a coroutine with the StartCoroutine function from any function like a function with a void return type.
void Start()
{
StartCoroutine(RepeatPrint());
}
or
IEnumerator Start()
{
yield return StartCoroutine(RepeatPrint());
}
2. Start a coroutine function without the StartCoroutine function by yielding the coroutine function you want to start. This must be done inside a coroutine function or a function with IEnumerator return type. It can't be done in a normal function like a void function.
IEnumerator Start()
{
yield return RepeatPrint();
}
When you start a coroutine with StartCoroutine then start children coroutines after with StartCoroutine but then killed the parent coroutine, the children coroutine functions will and should continue to run untouched.
Now, when you start a coroutine with StartCoroutine then start children coroutines after with yield return YourCoroutine() without using the StartCoroutine function but then killed the parent coroutine, the children coroutine functions will and should terminate or stop immediately so as the parent one.
It's not surprising that most Unity users don't know because it is not documented but it's something that's very important to know when using coroutine in Unity.
Coroutines run independent from each other. So stopping the main Coroutine won't affect any other, that has already been started.
It seems you have 2 options:
A: Stop the nested Coroutine from inside the first level
Store a Reference to RepeatPrint and use that to stop it using StopCoroutine.
edit: actually "from inside the first level" is not necessary, just use the correct Reference.
B: Stop ALL Coroutines in your MonoBehaviour
May work if you have no other Coroutines running

Reset a repeating Coroutine delay

I'm using a Coroutine to set up a repeating delay as follows.
In my Awake I have
StartCoroutine(RandomMove());
And then further down
IEnumerator RandomMove()
{
while (true)
{
// print(Time.time);
yield return new WaitForSeconds(foo);
// print(Time.time);
}
}
where 'foo' is a random float value that I change with every iteration.
Lets say foo is 10 seconds and part way thru the delay I need to reset the delay so it starts 'counting down' from 10 again.
How would I accomplish this? Should I use a Timer instead?
I don't like either of the two existing answers. Here's what I'd do:
Kill and Restart the coroutine:
We'll start with this part of the killer_mech's answer:
Coroutine myCoroutine;
void Awake() {
myCoroutine = StartCoroutine(RandomMove());
}
But we're going to handle the rest differently. killer_mech never did anything with the reference, other than to keep ovewriting it.
Here's what we're doing instead:
public void resetRandomMove() {
StopCoroutine(myCoroutine);
myCoroutine = StartCoroutine(RandomMove());
}
Call this any time you need to reset it.
I would suggest you first store Coroutine in a variable.
Coroutine myCoroutine;
void Awake()
{
myCoroutine = StartCoroutine(RandomMove());
}
and change the coroutine function as
IEnumerator RandomMove()
{
// print(Time.time);
yield return new WaitForSeconds(foo);
// print(Time.time);
// Call your new coroutine here
myCoroutine = StartCoroutine(RandomMove());
}
this way you will have a coroutine variable for every iteration. If you need to stop the coroutine just say :
StopCoroutine(myCoroutine);
in your function at required time.This will allow you to stop a coroutine in middle before the end of countdown. Also at the end of coroutine it will start new coroutine with updated reference After finishing your task just call back again with
myCoroutine = StartCoroutine(RandomMove());
Hope this resolves your problem. Yes you can do it with timer also with a boolean flag the same thing but I think using coroutine is much simpler.
.
Hmmm it could something like this also . Just for my own .
void Start() {
StartCoroutine(RepeatingFunction());
}
IEnumerator RepeatingFunction () {
yield return new WaitForSeconds(repeatTime);
StartCoroutine( RepeatingFunction() );
}
As i understand the question. InvokeRepeating() is also a choice.
Maybe it is because you are each frame waiting for new assigned seconds?
Why don't you make the random before yielding the wait, and store the CustomYieldInstruction instead of yielding a new instance, since it disposes the one that was before, that creates memory problems. You won't notice that if you yield return a WaitForSeconds with a constant value, but maybe with a random one creates ambiguity and resets the timer (see this Unity's optimization page, on the Coroutine's section). A quick example:
public float foo;
public float min;
public float max;
void Awake()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
while(true)
{
foo = Random.Range(min, max);
WaitForSeconds wait = new WaitForSeconds(foo);
yield return wait;
}
}
Also, #ryeMoss's solution seems a good one, to stop and restart the coroutine if 'foo' changes.
Hope it helps.

Create infinite loop in Unity with delay

I need to create a infinite loop in Unity without using the main thread? I saw some example, but it's not usefull:
while(true){
var aa;
debug.log("print.");
}
I want to add some delay like e.g. 2 seconds. If anybody knows the solution please help.
First define a Coroutines:
private IEnumerator InfiniteLoop()
{
WaitForSeconds waitTime = new WaitForSeconds(2);
while (true)
{
//var aa;
Debug.Log("print.");
yield return waitTime;
}
}
Then call it like that:
StartCoroutine(InfiniteLoop());
Added note:
If you happen to change Time.timeScale and don't want that to affect delay time, use:
yield return new WaitForSecondsRealtime(2);
Use this to create the loop;
private IEnumerator LoopFunction(float waitTime)
{
while (true)
{
Debug.Log("print.");
yield return new WaitForSeconds(waitTime);
//Second Log show passed waitTime (waitTime is float type value )
Debug.Log("print1.");
}
}
For calling the function, don't use Update() or FixedUpdate(), use something like Start() so you don't create infinite instances of the loop;
void Start()
{
StartCoroutine(LoopFunction(1));
}
Use coroutines..
//Call in your Method
StartCoroutine(LateStart(2.0f));
Then write coroutine like..
private IEnumerator LateStart(float waitTime)
{
yield return new WaitForSeconds(waitTime);
//After waitTime, you can use InvokeRepeating() for infinite loop infinite loop or you use a while(true) loop here
InvokeRepeating("YourRepeatingMethod", 0.0f, 1.0f);
}
Here is the documentation for InvokeRepeating():
https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html

Why does this Coroutine only run once?

"Something" is only printed once...
IEnumerator printSomething;
void Start () {
printSomething = PrintSomething();
StartCoroutine (printSomething);
}
IEnumerator PrintSomething () {
print ("Something");
yield return null;
StartCoroutine (printSomething);
}
The misstake in your approach is that you save the enumerator. A enumerator is already "enumerating" therefore giving the enumerator to the StartCoroutine-method twice basically results in direct exit of the coroutine as the enumerator has been used before. Starting the coroutine again can be done by calling the function again.
StartCoroutine(PrintSomething());
But instead of starting the coroutine over and over again try to use a loop inside instead.
while (true)
{
print("something");
yield return null;
}
This is better as internal handling of the coroutine and its overhead is unknown.
Try co-routine's name instead of a pointer. Or co-routine itself.
IEnumerator PrintSomething ()
{
print ("Something");
yield return null;
StartCoroutine ("PrintSomething");
}
Or
IEnumerator PrintSomething ()
{
print ("Something");
yield return null;
StartCoroutine (this.PrintSomething());
}
I ran into this exact same issue, Felix K. is right in that it assumes the IEnumerator has already been run and just immediately returns. My solution was to pass the function itself so that we generate a new IEnumerator each time it's called. I hope this helps someone else!
public IEnumerator LoopAction(Func<IEnumerator> stateAction)
{
while(true)
{
yield return stateAction.Invoke();
}
}
public Coroutine PlayAction(Func<IEnumerator> stateAction, bool loop = false)
{
Coroutine action;
if(loop)
{
//If want to loop, pass function call
action = StartCoroutine(LoopAction(stateAction));
}
else
{
//if want to call normally, get IEnumerator from function
action = StartCoroutine(stateAction.Invoke());
}
return action;
}

Categories