Background Job BasicsEdit
It is possible to fire off a task that runs 'in the background,' continuously taking care of some responsibility in the game. The utility of this is best demonstrated with an example.
Suppose that a particular game contains two characters, Alice and Bob. Bob is supposed to rotate continuously to face Alice, no matter where Alice goes. Fortunately, there is already a command that rotates Bob:
If we use this command inside 'onSceneStart', Bob will rotate only once, at the start of the game. That's not what we want: we need him to rotate every frame. To make this occur, we can launch a background job whose responsibility is to keep rotating him:
function onSceneStart() Coroutines.schedule(watch, bob, alice end function watch(person, target) while true do person:setLookAt(target) Timer.sleep(0) end end
The word 'Coroutines' is a very academic word for "Background Job." Coroutines.schedule launches a new background job that calls the function watch(bob,alice). The watch-routine causes the person to look at the target, then it sleeps for one frame, and repeats. The routine is coded so that you can use it to cause any object to watch any other object.
Timer.later can be used to do anything that background jobs can do. However, background jobs often produce much more readable code. Consider this example:
function printOneTwoThree() while true do print("One") Timer.sleep(1) print("Two") Timer.sleep(1) print("Three") Timer.sleep(1) end end
If launched as a background job, this function will print "One," "Two," "Three," "One," "Two," "Three," and so forth, with a one-second pause between each print. You can accomplish the same thing using Timer.later:
function printOne() print("One") Timer.later(printTwo, 1) end function printTwo() print("Two") Timer.later(printThree, 1) end function printThree() print("Three") Timer.later(printOne, 1) end
As you can see, doing it this way requires us to chop the function up into lots of little pieces, and makes the code a bit harder to understand.
As Soon As PossibleEdit
When a background job is scheduled, it starts running "as soon as possible." That basically means, as soon as the engine is done with whatever other thing it is doing. For example, if the background job is created during scene initialization, the job will run as soon as scene initialization is done. If the background job is created while handling a mouse click, the background job will run as soon as the mouse handler is done.
This is implemented as follows: Coroutines.schedule pushes the coroutine onto the front of the timer queue, which means it will be the next thing executed after the timer is done with the current task. As a result, if you schedule two background jobs, the most-recently scheduled runs first.
Killing Background JobsEdit
There are two ways to kill a background job. One is to design the subroutine that runs in the background so that it exits on some condition. For example, you could rewrite the 'watch' routine like this:
function watch(person, target) while (person:getProperty("stoplooking") == nil) do person:setLookAt(target) Timer.sleep(0) end end
If you start this routine in the background, and then later set the "stoplooking" property of the person, the while loop will terminate, the subroutine will return, and the background job will be done.
The other way to kill a background job is forcefully. To do this, you need the background job's "handle." In order to obtain that handle, you need to use slightly different code to start the background job:
local jobhandle = Coroutines.schedule(func, arg1, arg2...)
Now that you have a job handle, you can use it to kill the background job:
The subroutine will then stop running. You can also obtain job handles using 'Coroutines.running.'
Background Jobs Must SleepEdit
This needs to be repeated: when a background job is running, it locks out all other engine functions - including other background jobs, and other foreground jobs. It is only when the background job calls Timer.sleep that other aspects of the engine have a chance to run.
This is important for two reasons. First, background jobs seem a lot like threads in other engines, but because they automatically lock out everyone else, there is no need to manually write locking code. You do not really need critical sections when every block of code not containing Timer.sleep is implicitly atomic.
Second, it is important to realize that a background job really can freeze the whole engine by going into an infinite loop. It really is necessary for a background job to sleep on a frequent basis, and in fact, not to run any more than it really needs to.
There are two primary ways for a background job to sleep. One is to call Timer.sleep. The other is to call Coroutines.yield, which causes the background job to sleep indefinitely until explicitly awakened. If a background job uses yield, then the only way to get it to wake back up is to use Coroutines.schedule(jobhandle). The jobhandle can be obtained from Coroutines.schedule, as shown earlier, or by using the method Coroutines.running.
Event Handlers are Background JobsEdit
When Wild Pockets calls one of your event handlers, it automatically creates a background job to run the event handler. To put it differently, all event handlers are executed by background jobs. Because of this, it is legal for an event handler to call Timer.sleep.
There are actually several cases the engine automatically creates a background job to run your code. These include:
- Event handlers
- Collision Callbacks
- GUI Callbacks
Any of these can use Timer.sleep.
Be careful - many things are not included. For example, object constructors are not run by background jobs. Therefore, object constructors cannot use Timer.sleep. Any attempt to do so will result in this strange Lua error:
attempt to yield across metamethod/C-call boundary