Asynchronious scripting

Started by
4 comments, last by ToohrVyk 17 years, 1 month ago
In an utopian world, game logic is written like this:

Enemy *myEnemy;
while (!dead)
{
    myEnemy = FindNearestEnemy();
    MoveWithinRange(myEnemy->location, 10);
    FireAt(myEnemy);
    if (dead)
    {
        Say("Did j00 killed teh mighty AI!");
        Respawn(locations[rand()%10]);
    }
}


As you probably all know, in reality it doesn't work like this. The problem is that the game state is evaluated every frame, so you'd have to make a state machine to make this kind of behaviour. edit: this counts especially for the FindNearestEnemy() and FireAt() commands, which take at least several frames to execute. It seems like scripting is a viable option, but what kind of scripting languages would supply this behaviour? (Ie, a command that takes multiple frames). And how is this implemented? Thanks for any help, and apologies if my description is a bit unclear, ask me and I'll try to elaborate.
Advertisement
'co-routines' are the general solution; kind of like threads in that they maintain their own state, but you have to explicately yield from them.

In this case, for example, the 'MoveWithinRange' function would do a step, then yield control back, then when this script is called again it picks up from where it left off in the MoveWithinRange' function, which does the next step, and then repeat.
I understand exactly your problem. We did use Lua in our last project and it certainly didn't offer such a mechanism. We ended up writing our own scripting language which had delays in and the special kind of game control your talking of. I'm not aware of a scripting language that has async delays in that your after.
I have used angelscript as my scripting language to do this. HOWEVER, it does not directly support what you wish to do. But there are examples that come with writing coroutines. So what I did is use their scripting language and wrote the additional code to support the execution of the script over several frames...

There are examples with angelscript on how this is done...

I hope this helps you.
Game Monkey Script is built entirely on asynchronous support and I use it extensively in my project. You wouldn't be able to use the exact syntax in your example, but it has a built in functionality that allows you to block a script thread on an event, so for example your script might look like this.

Enemy *myEnemy;while (!dead){    block(EVENT.ENEMYSEEN); // native code would signal the scripting system with an event whenever new enemies are seen    myEnemy = FindNearestEnemy();    MoveWithinRange(myEnemy->location, 10);    FireAt(myEnemy);    block(EVENT.WEAPON_FIRED); // same here    if (dead)    {        Say("Did j00 killed teh mighty AI!");        Respawn(locations[rand()%10]);    }}


In GM, you can hide the above calls to block by doing the block inside your FireAt function, MoveWithinRange, FindNearestEnemy function.

Game Monkey is ideal for such scripting. Every script run with GM is run in a script thread. They are not real threads. They are similar to Microthreads or Fibers, so there are no issues common to real threads, and no need for protection mechanisms like mutex and critical sections and such.

Stackless Python works similarly, and with some work yourself you could get Lua coroutines to work similarly. Quake4/Doom3 engine implements a custom scripting language that runs the same sort of 'threads'. Particularly for AI, it makes scripting game entities much much easier than using polling or other methods.
Any language with lambdas should be able to handle this more or less elegantly, when combined with a scheduler.

class enemy (pos) = object(self)  inherit entity  var position = pos  mutable var activity = Action.idle  method spawn =    position # set_random;    self # act  method act =     let enemy = World.findNearestEnemy (position) in    let shooting =      new shootAt        ~actor:self        ~target:enemy    in    activity <-       new doWithinRange         ~actor:self        ~target:enemy        ~distance:10        ~action:shooting      method die =    activity # cancel;    self # say ("Omgnoes");    self # spawnend


Internally, the shootAt and doWithinRange actions schedule actions and triggers, which are then executed by a scheduler. All of them work as coroutines, which execute a small action (start moving, schedule a trigger for when the object reaches the destination to execute the intended action, and a trigger for when the object leaves the distance to start moving again).

This topic is closed to new replies.

Advertisement