Firstly - I understand why this behaviour happens, but as it is quite nuanced I do not think it is made especially clear in the documentation. I am not sure what, if anything, can be done to resolve it, but I want to start a discussion about whether this is something that could be dealt with.
The problem is writing “yield return anything” will always wait a frame.
I began this by needing to loop over a collection of objects in a coroutine, each of which have their own IEnumerator method that will optionally run some logic over the course of several frames, but if the condition is not met, will break out immediately with “yield break”.
Every write up I’ve since read about nested coroutines describes the following two means of nesting them as equivalent - this isn’t strictly true.
//First method of nesting
yield return NestedRoutine();
//Second method of nesting
IEnumerator nested = NestedRoutine();
while(nested.MoveNext())
yield return nested.Current;
For the most part, these are indeed equivalent. They differ, however, in how they handle ‘yield break’.
If in the above code, NestedRoutine is implemented as follows:
private IEnumerator NestedRoutine()
{
if(!ShouldRun) yield break;
//Run logic over some frames
}
Then the first means of nesting will wait a single frame before continuing the Coroutine, whereas the second will continue running the parent routine immediately.
When you “yield return” anything other than a YieldInstruction - e.g. null, true, false, 1 etc - Unity will wait a frame before continuing the Coroutine. This remains true even if you return another IEnumerator that itself actually broke rather than returned null.
This is subtly mentioned in the documentation:
By wrapping the yield return statement within a while loop and checking MoveNext(), the second means of nesting works as expected, as when it reaches a break statement, MoveNext() will return false and skip the return entirely.
This is a workaround, but becomes ugly very quickly - every time you need to yield another coroutine, you need to set a local variable and wrap its MoveNext() in a while loop before actually yielding it. This turns a single line of easy to read code into three, and moreover, three lines that can’t be wrapped in a method, because as soon as you ‘yield return’ that method, you will wait a frame.
The first, most commonly used means of nesting, results in any IEnumerator that could ‘break’ if some condition isn’t met, consuming a frame. This isn’t immediately apparent nor intuitive, because writing yield break infers different intent to yield return null, but this intent is lost in the parent loop, where they are both returned and thus evaluated as null.
As such, my only proposition, and I’m unsure whether it’s feasible, would be a YieldInstruction we can return that tells Unity to resume the parent coroutine immediately. Perhaps something like 'YieldResumeImmediate’. I am aware we can implement CustomYieldInstructions, but so far I have been unable to find a way of implementing one such that it never waits - at a minimum, it will always wait a single frame.
Consider the following CustomYieldInstruction:
public class ContinueImmediately : CustomYieldInstruction
{
public override bool keepWaiting => false;
}
If you were to return this a few times within an IEnumerator and record the frameCount before and after, you will find each return statement waits a frame. With this being the case, I can’t see how a CustomYieldInstruction would be able to continue immediately, unless there’s something I need to override that I’ve been unable to find. If somebody knows how to implement a CustomYieldInstruction such that it will resume the parent method immediately, that would be fantastic. Any thoughts on the matter would be appreciated
Thanks!