How can I prevent my code from yielding every frame? - c#

I am calling a method that call itself to crawl through terrain and create zones. However when the zones become to large the process ends in a stack overflow. By forcing the code to yield and take its time it finishes to completion successfuly and crawls the 3 zones in my map. However the method I am using is yielding EVERY single frame and I don't know how to make it yield just every 100 frames, causing it to be extremely slow to finish. Here is the pseudo code of what I am doing for readability:
public int timer = 0;
void Awake(){
StartCoroutine(crA);
}
public IEnumerator crA(){
//stuff
yield return StartCoroutine(crB());
//stuff that needs to happen only after crB finishes
}
public IEnumerator crB(){
timer = 0;
yield return StartCoroutine(crC());
}
public IEnumerator crC(){
//Crawiling code, crawls terrain to create a zone
if(x){ yield break;}
timer++;
//vv I WANTED IT TO YIELD ONLY IN HERE
if (timer ==100){
timer = 0;
yield return null;
}
//^^
yield return StartCoroutine(crC());
yield return StartCoroutine(crC());
yield return StartCoroutine(crC());
yield return StartCoroutine(crC());
yield return StartCoroutine(crC());
}
it seems yield return startcoroutine is causing a yield but I don't know what to use instead. Any help would be appreciated.

As said in general as long as you use yield it will at least yield for one frame like
yield return null;
does.
What you could do instead is use the IEnumerator.MoveNext "manually" without yielding it. This is basically what the Unity coroutine is calling once per frame if you used StartCoroutine.
And then only yield when you want to do so. Given your pseudo code something like e.g.
private void Awake()
{
StartCoroutine(crA);
}
public IEnumerator crA()
{
//stuff
yield return crB();
//stuff that needs to happen only after crB finishes
}
public IEnumerator crB()
{
timer = 0;
// "Manually" run the routine instead of using Unity Coroutine interface
var crc = crC();
while(crc.MoveNext())
{
if(timer >= 100)
{
yield return null;
timer = 0;
}
}
}
public IEnumerator crC()
{
//Crawiling code, crawls terrain to create a zone
if(x) yield break;
timer++;
yield return crC();
yield return crC();
yield return crC();
yield return crC();
yield return crC();
}
And then as said you could do it time based instead of using a frame/call counter rather using e.g. a StopWatch
private const float TARGET_FRAMERATE = 60;
public IEnumerator crB()
{
var targetMilliseconds = 1000f / TARGET_FRAMERATE;
var sw = new StopWatch();
sw.Start();
// "Manually" run the routine instead of using Unity Coroutine interface
var crc = crC();
while(crc.MoveNext())
{
if(sw.ElapsedMilliseconds >= targetMilliseconds)
{
yield return null;
sw.Restart();
}
}
}
So whenever the last execution exceeded the target frame-rate it will yield one frame. You'll have to play a bit with the value .. probably something like 30 or even 24 might already be enough depending on your usecase.
It basically is a tradeoff between frame-rate and actual realtime duration.
Note: Typed on smartphone but I hope the idea gets clear

Related

Trouble understanding Delays and Coroutines

void start()
StartCoroutine(Text());
IEnumerator Text()
{
Debug.Log("Hello")
yield return new WaitForSeconds(3)
Debug.Log("ByeBye")
}
I understand the basic concept that this does but I don't get what anything means such as yield return new WaitforSeconds(3) and what StartCoroutine is and What an IEnumerator is.
Can anyone explain to me what they mean?
When you call a function, it runs to completion before returning. This effectively means that any action taking place in a function must happen within a single frame update; a function call can’t be used to contain a procedural animation or a sequence of events over time. As an example, consider the task of gradually reducing an object’s alpha (opacity) value until it becomes completely invisible.
void Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
}
}
As it stands, the Fade function will not have the effect you might expect. In order for the fading to be visible, the alpha must be reduced over a sequence of frames to show the intermediate values being rendered. However, the function will execute in its entirety within a single frame update. The intermediate values will never be seen and the object will disappear instantly.
It is possible to handle situations like this by adding code to the Update function that executes the fade on a frame-by-frame basis. However, it is often more convenient to use a coroutine for this kind of task.
A coroutine is like a function that has the ability to pause execution and return control to Unity but then to continue where it left off on the following frame. In C#, a coroutine is declared like this:
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return null;
}
}
It is essentially a function declared with a return type of IEnumerator and with the yield return statement included somewhere in the body. The yield return null line is the point at which execution will pause and be resumed the following frame. To set a coroutine running, you need to use the StartCoroutine function:
void Update()
{
if (Input.GetKeyDown("f"))
{
StartCoroutine("Fade");
}
}
You will notice that the loop counter in the Fade function maintains its correct value over the lifetime of the coroutine. In fact any variable or parameter will be correctly preserved between yields.
By default, a coroutine is resumed on the frame after it yields but it is also possible to introduce a time delay using WaitForSeconds:
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}
This can be used as a way to spread an effect over a period of time, but it is also a useful optimization. Many tasks in a game need to be carried out periodically and the most obvious way to do this is to include them in the Update function. However, this function will typically be called many times per second. When a task doesn’t need to be repeated quite so frequently, you can put it in a coroutine to get an update regularly but not every single frame. An example of this might be an alarm that warns the player if an enemy is nearby. The code might look something like this:
bool ProximityCheck()
{
for (int i = 0; i < enemies.Length; i++)
{
if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
return true;
}
}
return false;
}
If there are a lot of enemies then calling this function every frame might introduce a significant overhead. However, you could use a coroutine to call it every tenth of a second:
IEnumerator DoCheck()
{
for(;;)
{
ProximityCheck();
yield return new WaitForSeconds(.1f);
}
}
This would greatly reduce the number of checks carried out without any noticeable effect on gameplay.
Note: You can stop a Coroutine with StopCoroutine and StopAllCoroutines. A coroutines also stops when the GameObject it is attached to is disabled with SetActive(false). Calling Destroy(example) (where example is a MonoBehaviour instance) immediately triggers OnDisable and the coroutine is processed, effectively stopping it. Finally, OnDestroy is invoked at the end of the frame.
Coroutines are not stopped when disabling a MonoBehaviour by setting enabled to false on a MonoBehaviour instance.
Reference: https://docs.unity3d.com/Manual/Coroutines.html
Unity (ab)uses enumerators to build C# CoRoutines, because async / await didn't exist. When you write;
IEnumerator Text()
{
Debug.Log("Hello")
yield return new WaitForSeconds(3)
Debug.Log("ByeBye")
}
The compiler turns that into something like;
IEnumerator Text() => new StateMachine();
public class StateMachine : IEnumerable{
private int state = 0;
// plus any local variables moved to fields.
StateMachine(){}
public object Current { get; set; }
public bool MoveNext(){
switch(state){
case 0:
Debug.Log("Hello");
Current = new WaitForSeconds(3);
state = 1;
return true;
case 1:
Debug.Log("ByeBye");
return false;
}
}
}
Since the state of your function is now stored in fields on an object, your method can pause before it finishes. Unity will then look at the object you yield to decide when to call MoveNext().
Now that C# has async methods, which also cause your methods to be translated into state machines. It would be possible for a new version of unity to support them instead, like;
async Task Text()
{
Debug.Log("Hello")
await Something.WaitForSeconds(3)
Debug.Log("ByeBye")
}
But they would still have to support the old way of building CoRoutines.

Why isn't my "yield" working?

i'm very new to programming and i have a feeling that there is a very stupid mistake here. But can anyone explain me, why instead of 4 messages with a delay of 2 seconds between, i instantaniously get the last message shown only.
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using UnityEngine.UI;
public class Wait : MonoBehaviour {
private int i = 0;
public string[] message;
[SerializeField]
private Text toText;
public IEnumerator Message(float waitTime)
{
toText.text = message[i];
i++;
yield return new WaitForSeconds(waitTime = 2f);
}
void Start()
{
StartCoroutine(Message(i));
StartCoroutine(Message(i));
StartCoroutine(Message(i));
StartCoroutine(Message(i));
}
}
void Start()
{
StartCoroutine(Message(i));
StartCoroutine(Message(i));
StartCoroutine(Message(i));
StartCoroutine(Message(i));
}
I don't think that is doing what you think it should. This will not wait for each StartCoroutine to finish and will call the next StartCoroutine.
This is what happens:
The first StartCoroutine(Message(i)); call will start the Message function.
Once it meets the yield return new WaitForSeconds(waitTime = 2f); line of code, it will then jump back into the Start() function.
The next StartCoroutine(Message(i)); will be called then the-same thing will happen again.
When calling a coroutine function from a non coroutine function, as long as you have yield return new WaitForSeconds, yield return null;, or yield return what-ever YieldInstruction is implemented, the execution will return to that non coroutine function in-which the StartCoroutine function was called from and continue to execute other code.
To make coroutine wait for another one to finish, make the StartCoroutine(Message(i)); function call from another coroutine function. This will allow you to yield each coroutine function call. This is called chaining coroutine.
To chain or yield a coroutine function call, simply put yield return in front of the StartCoroutine function. yield return StartCoroutine(Message(i));
public class Wait : MonoBehaviour {
private int i = 0;
public string[] message;
[SerializeField]
private Text toText;
public IEnumerator Message(float waitTime)
{
// toText.text = message[i];
i++;
yield return new WaitForSeconds(waitTime = 2f);
}
void Start()
{
StartCoroutine(startMessage());
}
IEnumerator startMessage()
{
yield return StartCoroutine(Message(i));//Wait until this coroutine function retuns
yield return StartCoroutine(Message(i));//Wait until this coroutine function retuns
yield return StartCoroutine(Message(i));//Wait until this coroutine function retuns
yield return StartCoroutine(Message(i));//Wait until this coroutine function retuns
}
}
Now, each StartCoroutine(Message(i)); call will wait until the first one finishes. You can always use a boolean variable to do this but it is much better to yield the StartCoroutine call.
The reason the text is set instantly is because StartCoroutine will execute the enumerator from Message.
The first two things that happen is setting the text and incrementing i. Only after that is done, you will yield the WaitForSeconds. It is at that point StartCoroutine will pause further execution of Message.
If you had had a line after the yield return you would have seen the effects of that after the 2 seconds.
in the example in the docs you can also see the behavior after the yield return Wait
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
I would suggest running the next test to become more familiar with how yield return works:
IEnumerator MessageOuter() {
Console.WriteLine("outer 1");
var inner = MessageInner();
Console.WriteLine("outer 2");
return inner;
}
IEnumerator MessageInner() {
Console.WriteLine("inner 1");
yield return new WaitForSeconds(1);
Console.WriteLine("inner 2");
yield return new WaitForSeconds(1);
Console.WriteLine("inner 3");
}
void Start() {
Console.WriteLine("start 1");
var outer = MessageOuter();
Console.WriteLine("start 2");
StartCoroutine(outer);
Console.WriteLine("start 3");
}

My coroutine is not really a routine. Why is this function called just once?

I wrote a script to spawn game object in my unity game. It uses coroutine. Why my spawnWaves() is called just once? Here is my script:
using UnityEngine;
using System.Collections;
public class GameController : MonoBehaviour {
public GameObject[] hazards;
public Vector3 spawnValues;
public float spawnInterval;
// Use this for initialization
void Start ()
{
StartCoroutine(SpawnWaves(spawnInterval));
}
IEnumerator SpawnWaves(float interval)
{
Vector3 spawnPosition = new Vector3(Random.Range(-spawnValues.x, spawnValues.x), spawnValues.y, spawnValues.z);
Quaternion spawnRotation = new Quaternion(Random.Range(-Mathf.PI, Mathf.PI), Random.Range(-Mathf.PI, Mathf.PI), Random.Range(-Mathf.PI, Mathf.PI), Random.Range(-1f, +1f));
Instantiate(hazards[Random.Range(0, 3)], spawnPosition, spawnRotation);
yield return new WaitForSeconds(interval);
}
}
All public values inside of Unity Editor is set correctly.
What's wrong?
Thanks!
A couroutine is nothing else than a normal yield-method.
For having a loop use while.
while (...)
{
// Do work here
yield return new WaitForSeconds(interval);
}
As alternative, which i do not recommend, you can start the coroutine again at the end.
....
yield return new WaitForSeconds(interval);
StartCoroutine(SpawnWaves(spawnInterval));
Further explaination
A coroutine uses a enumerator for running the above code. Basically the compiler creates a background class which implements the IEnumerator interface. Jon Skeet explains it very well here.
The problem is that your method has only one occurrence of yield return and it's not in a loop. This means that it always returns an IEnumerable object that contains (yields) a single item from start to end.
The question you have to be able to answer is: how many items do you expect the coroutine to yield?
If you expect it to yield only one item, then it's fine as it is.
If you expect it to yield (for example) 3 items, it would look something like this:
yield return new WaitForSeconds(interval);
yield return new WaitForSeconds(interval);
yield return new WaitForSeconds(interval);
If you expect it to yield n items, it would look something like:
for(var i = 0; i < n; i++)
{
yield return new WaitForSeconds(interval);
}
If you expect it to yield until a condition is met, then you probably want something like this:
while(!conditionIsMet)
{
yield return new WaitForSeconds(interval);
}
And if you expect it to yield a potentially infinite amount of items:
while(true)
{
yield return new WaitForSeconds(interval);
}

How to stop co-routine?

When two co-routines are running, how do you stop the first co-routine?
GLOBALS.stableTime = 5;
IEnumerator StableWaittingTime ()
{
yield return new WaitForSeconds (1f);
if (GLOBALS.stableTime == 0) {
GameManager.instance.LevelFaildMethod ();
} else {
GameManager.instance.stableWaittingTime.text = GLOBALS.stableTime.ToString ();
GLOBALS.stableTime--;
StartCoroutine ("StableWaittingTime");
}
}
There are three ways to stop coroutines.
The first is to call StopAllCoroutines(), which will obviously stop all running coroutines.
The second is to call StopCoroutine(coroutine), where coroutine is a variable name given to your IEnumerator.
And the third is to do a yield break from within the coroutine.
Worth noting is that both StopAllCoroutines and StopCoroutine can only stop a coroutine when the coroutine reaches a yield return *.
So if you have two coroutines with the same name and you want to stop the one you are executing in you do yield break.
Interestingly, if you want to stop every other coroutine besides the one you are executing in, you call StopCoroutines() from within that coroutine.
#Imapler answer is almost all you need. I would just add that StopCoroutine method of MonoBehaviour is overloaded and has 3 types of parameters, so it is possible to stop many coroutines of same name.
For your need here, just use yield break; like this:
void Start ()
{
StartCoroutine (StableWaittingTime ());
}
IEnumerator StableWaittingTime ()
{
yield return new WaitForSeconds (1f);
if (false)
{
// do something
}
else
{
// do something
StartCoroutine (StableWaittingTime ());
yield break;
}
}

Loops using coroutines

I have a question about coroutine behaviour in case of loops, see following code extract as example done on a Unity C# script:
void Start() {
StartCoroutine(FSM());
}
IEnumerator FSM() {
state="State1";
while (true) {
Debug.Log("State "+state);
yield return StartCoroutine(state);
}
}
IEnumerator State1() {
while (true) {
if (stateTransitionCond) {
state = "NextState";
yield break;
}
yield return null;
}
}
The status machine works fine, but while the current status is Status1 (stateTransitionCond==false), due to the yield return null inside the loop of State1() routine, I was expecting that loop inside FMS() also performs another iteration generating debug log 'Debug.Log("State "+state);'.
In other words I was expecting a lot of debug log (one for each iteration of State1() routine, when status is Status1) but in the reality only 1 execution is performed while status is Status1.
So I suppose I miss something about yield functionality, is there anybody that can explain me this behaviour?
Your issue stems from the fact that your code does not break out of the State1() method until stateTransitionCond == true.
The method starting the coroutine, FSM(), is not returned to until State1 is finished. In other words, control flow does not return to the calling method until the coroutine is complete. I believe this is due to the fact that you are yield-ing State1 inside FSM (yielding another coroutine). Obviously, "normal" methods do not wait for the coroutine to finish before continuing execution.
Please see the code sample below for an illustrative example:
using UnityEngine;
using System.Collections;
public class CoroutineTest : MonoBehaviour {
// current FSM state
public string state = "";
void Start()
{
StartCoroutine(FSM());
}
IEnumerator FSM()
{
state = "State1";
while (true)
{
Debug.Log("State: " + state);
// ExecuteOnce will execute exactly once before returning to the outer function
yield return StartCoroutine(ExecuteOnce());
// ExecuteIndefinitely will execute indefinitely until its while() loop is broken
// uncomment to test
//yield return StartCoroutine(ExecuteIndefinitely());
}
}
IEnumerator ExecuteOnce()
{
Debug.Log("Calling ExecuteOnce()");
yield return new WaitForSeconds(1f);
}
IEnumerator ExecuteIndefinitely()
{
Debug.Log("Calling ExecuteIndefinitely()");
while (true)
{
Debug.Log("Inside ExecuteIndefinitely()");
yield return new WaitForSeconds(1f);
}
}
}

Categories

Resources