piątek, 13 lipca 2012

Unity3D Yielding

To be honest the first time I find out about yield keyword in JavaScript was when I was learning Unity3D scripting. Did you know that yield is a built in mechanism of JavaScript? I didn't know that and Unity3D documentation was not very helpful on this ground either either:

The yield statement is a special kind of return, that ensures that the function will continue from the line after the yield statement next time it is called.

And

You can also pass special values to the yield statement to delay the execution of the Update function until a certain event has occurred.

Ok, so... yielding will break my function execution. This function will continue next time it is called. Pretty straightforward to me. So I wrote something like this:

private var explode : boolean;

function Update() {
    if (explode) {
        Explode();
    }
}

function Explode() {
    Flash();
    yield WaitForSeconds(1.0);
    Destroy();
    explode = false;
}

Guess what happened? Explosions appeared all over the screen like Explode() function was called over and over again. What the hell happened?! Let's look that explanation of yield by Mozilla fundation:

The function containing the yield keyword is a generator. When you call it, its formal parameters are bound to actual arguments, but its body isn't actually evaluated. Instead, a generator-iterator is returned. Each call to the generator-iterator's next() method performs another pass through the iterative algorithm. Each step's value is the value specified by the yield keyword. Think of yield as the generator-iterator version of return, indicating the boundary between each iteration of the algorithm. Each time you call next(), the generator code resumes from the statement following the yield.

Ok, so yield creates an iterator. We cannot assume that this is true for Unity3D JavaScript but this explanation may lighten up what is really happening.
After further digging I found example code of Daniel Rodriguez on his blog. He came with nice yield explanation and this example:

function MyCoroutine()
{
    print("This is printed second");
    yield;      //Return control to the Start function
    print("This is printed one fourth, exactly one frame after the third");
}
 
void Start()
{
    print("This is printed first");
    MyCoroutine();
    print("This is printed third");
}

Then let's go through this with keeping official yield explanation in mind:
  1. in Start() the This is printed first is printed
  2. diving into MyCoroutine()
  3. printing This is printed second
  4. yield will break execution od MyCoroutine() - returning to Start()
  5. printing This is printed third - so far so good
  6. wait, where's Update()? It's not important? Ok....
  7. When the fourth message will be printed then? How?
It will be printed. And MyCourotine() is not called in Update() I can assure you. Do you know why? This is my conclusion:
  • when yield is called it creates an iterator and puts it in (let's call it) yield queue to resume the execution later
  • right before the Update() call (or if there's no Update() let's think that it exists and it's empty) Unity is looking for each iterator in yield queue. If yield was called without parameters then execution will be resumed immeadletly.
  • calling method that contains yield over and over again doesn't resume the execution but will create more iterators in the yield queue. All these iterators will be executed (this is why I saw many explosions all over the screen).
What is the simplest way of thinking of yield (without parameters) in Unity3D? I belive it's To delay execution of further code and execute it before next Update() iteration. It's important to not to call function containing yield statement multiple times if you don't want to make it execute multiple times.


And I want to point here that this is only my assumption based on few experiments. I can be wrong here and please correct me if so. I believe that by time I will practice unity scripting something more and I will correct my above statements.

Sources:

1 komentarz: