Coroutines are a strange beast. Yield return causes the method to suspend execution until it is later stepped. Behind the scenes, it might look something like this:
class FireContinuouslyData {
int state;
bool shouldBreak;
}
object FireContinuously(FireContinuouslyData data) {
switch (data.state) {
case 0:
goto State_0;
}
while (true) {
GameObject laser = ...;
laser.GetComponent...
//the next three lines handle the yield return
data.state = 0;
return new WaitForSeconds(fireTime);
State_0:
}
}
And internal to Unity/C# (since yield return is a native c# feature), when you call StartCoroutine, it creates a FireContinuouslyData
object, and pass it in to the method. Based on the return value, it determines when to call it again later, simply storing the FireContinuouslyData object to pass it in next time.
If you ever did a yield break, it could internally just set data.shouldBreak = true
and then Unity would simply throw away the data and not schedule it again.
And if there were any data that needed to be saved between executions, it would also be stored in the data for later.
An example of how Unity/C# might implement the coroutine functionality:
//Internal to Unity/C#
class Coroutine {
Action<object> method;
object data;
}
Coroutine StartCoroutine(IEnumerator enumerator) {
object data = CreateDataForEnumerator(method); //Very internal to C#
Action<object> method = GetMethodForEnumerator(enumerator); //Also very internal to C#
Coroutine coroutine = new Coroutine(method, data);
RunCoroutine(coroutine);
return coroutine;
}
//Called whenever this coroutine is scheduled to run
void RunCoroutine(Coroutine coroutine) {
object yieldInstruction = coroutine.method(coroutine.data);
if (!data.shouldBreak) {
//Put this coroutine into a collection of coroutines to run later, by calling RunCoroutine on it again
ScheduleForLater(yieldInstruction, coroutine);
}
}