Coroutines

From OrbEdit Wiki

Jump to: navigation, search

Contents

Introduction to Coroutines

Coroutines are a special type of Lua function that can be paused and resumed. Coroutines, in combination with Events, are the heart of Orb's scripting system. They allow you to perform one operation and pause until that operation completes, then resume and perform another operation. Pausing returns control to the caller, just as if the function had exited. Resuming begins again at the statement immediately following the pause. This gives you a huge amount of flexibility to do things like:

  • Design paths for characters to walk along
  • Create particle emitters
  • Chain together spell effects
  • Make a "listener" that checks for certain conditions every frame
  • Write an AI script that polls for new input at certain intervals


Starting Coroutines

The two most important functions to know when working with Orb coroutines are:

  • orb_run(func, id, ...) Starts a new coroutine. As discussed in the Events section, this function associates the function func with the Event ID id and immediately calls func with an arbitrary set of parameters. However, unlike calling func directly, it is called as a coroutine, meaning it can be paused and resumed at any time.
  • pause(n) Pauses the execution of a coroutine for n seconds, when called from within a coroutine (that is, from within a function started by orb_run()). When called with no parameter, the coroutine will be paused until the Event ID associated with it is fired. Calling pause() with a value of 0 will pause for exactly 1 frame.

An now, a simple example:

-- Create a coroutine that will print 2 pieces of text with a pause between them
orb_run(function()
          print("But enough talk");  -- Print some text
          pause(2);                  -- Pause for 2 seconds
          print("Have at you!");     -- Print some more text
          return -1;                 -- Return -1 to indicate that the coroutine is done
        end, orb.gueid());


A more complicated example:

-- Create a new Sprite
local mySprite = Sprite:new();
 
-- Obtain an Event ID
local id = orb.gueid();
 
-- Create a coroutine that will slowly move the Sprite back and forth forever
orb_run(function()
 
          -- Create an infinite loop
          while(true) do
            mySprite:move(100, 0, 20, id);    -- Move 100 pixels to the right at 20 pixels/second and fire id when done
            pause();                          -- Pause until id is fired
            mySprite:move(-100, 0, 20, id);   -- Move 100 pixels to the left at 20 pixels/second and fire id when done
            pause();                          -- Pause until id is fired
          end
 
          return -1;                          -- Unreachable code, since the above loop goes forever
        end, id);

Note that the same Event ID is used throughout the whole coroutine and is passed to each of the move() methods to fire when they complete. Each call to pause() causes the coroutine to wait until the Event ID is fired. Without the ability to pause, the above example would crash your game; the script would go into an infinite loop and the engine would grind to a halt. Pausing, however, gives control back to the engine to continue with its other tasks until it's time to resume.


Stopping Coroutines

Coroutines can end in one of two ways:

  • Normally: When the code-path of a coroutine comes to an end, it is cleaned up just like a normal function. Coroutines should always return -1 to indicate that they are done.
  • Forced: You can force a coroutine to terminate through the use of orb_cancel(id), passing in the Event ID of the coroutine to be cancelled. For example, you might want to terminate an animation loop early in order to play another animation in its place. Canceling a coroutine will not stop it immediately, it will only cause it to not be resumed after its next pause. This is only a problem if a coroutine cancels itself via orb_cancel(), but in that case, it should just return.

To illustrate this point, an example, building on the previous example:

-- <code from previous example>
 
-- Create a second coroutine that will wait 10 seconds and then cancel the coroutine in the above example
orb_run(function()
          pause(10);
          orb_cancel(id);
          return -1;
        end, orb.gueid());

Although a rather contrived example, this illustrates how to easily cancel a coroutine, simply by calling orb_cancel(id) with the Event ID of the coroutine to be cancelled.


Nested Coroutines

Coroutines can be nested without a problem. However, nested coroutines have no effect on their outer coroutine, as seen in the example below:

-- Create an outside coroutine
orb_run(function()
          -- Loop 10 times
          for i = 0, 10 do
            pause(2);                   -- Pause for 2 seconds
            print("Outside");           -- Print "Outside"
 
            -- Create another coroutine
            orb_fun(function()
                      pause(1);         -- Pause for 1 second
                      print("Inside");  -- Print "Inside"
                      return -1;        -- Finished
                    end, orb.gueid());
          end
 
          return -1;
        end, orb.gueid());

In this example, the call to pause() made from the inside coroutine has no effect on the outer coroutine. Each call to orb_run() creates its own separate thread that is unaffected by pauses from other threads. Each time through the outer loop creates a new coroutine which pauses for one second from the time it was created, and then prints "Inside". Nesting is used frequently in the Tutorials.

Personal tools