continuing-gamemonkey.md 71 KB

Continuting GameMonkey Script: Advanced Use

Foreword

This article is the revised version of that originally published in 2009 on GameDev.net. There are several aspects that are out of date, however the article has been left in its original form for preservation and posterity.

Introduction

By reading my Introduction to GameMonkey Script articles you have been introduced to the basic features of the GameMonkey Script (GM Script or simply 'GM' herein) language and how to embed it into your game or application. This article will teach you some of the more advanced aspects of the GM language and the virtual machine API. I will begin by describing the functionality and provide examples in GameMonkey Script code itself as this allows simple and quick demonstration of the features. I will then continue this discussion with examples in C++ code to demonstrate how to access this functionality from within your game engine itself.

In order to follow this article you are assumed to have read, understood and implemented the ideas and examples presented in the introductory articles. It is also expected that you are familiar with concepts such as simple messaging systems and event handlers so that you can follow some of the sample code without issue.

This article covers the following topics:

  • Cooperative Threads
  • Blocking / Signalling
  • Threads and 'this'
  • Thread States
  • The Structure of a Thread
  • Creating Script-Extensible Entities
  • Best practices for using GameMonkey

Scripting Cooperative Threads

Every script running within the GM virtual machine is executed within its own "thread". A "thread" is standalone in that it has its own executable bytecode and stack but it runs within the overall context of the GameMonkey machine so can access data and functions from other threads. Unlike your operating system's definition of a "thread", the GM virtual machine is based around a co-operative threading model, so that upon every execution of the gmMachine::Execute() method each thread must either complete (terminate) or yield to the machine to allow other threads a chance to execute.

When a thread runs, it executes a function, a sequence of bytecode that is contained within its own stackframe. Whenever you execute a script in GameMonkey the bytecode is compiled into a function and is actually created within a thread to be executed. As a result, any explicit creation of your own threads will need a function to actually execute. This concept will be covered in more detail in a later section of this article.

Creating Threads from Script

There are two ways of creating a thread in GM; the first is via the scripting language itself -- there is an in-built function called thread() that takes the thread function and the values to be passed to this function as arguments. The following example demonstrates how to create a new thread from script:

  global thread_1 = function( a_count ) {
    print( "[1] Starting..." );
    for (i = 0; i < a_count; i = i + 1)
    {
      print( "[1] iteration: " + i );
    };

    print( "[1] Finishing..." );
  };

  print( "[0] Ready to execute..." );
  thread( thread_1, 100 );
  sleep(1);
  print( "[0] Thread created..." );

Example: threads_01.gm

  [0] Ready to execute...
  [1] Starting...
  [1] Iteration 0
  ...
  [1] Iteration 99
  [1] Finishing...
  [0] Thread Created...

Output: threads_01.gm

In this example, a new thread is created within the machine which executes a function to count from 0 to 99. It will continue until the function is completed, hogging the machine's runtime until it is done. In this example, the sleep() function is called to yield control from the main thread and into the new thread we create. Use of sleep will be discussed later on in this article.

Yielding Thread Execution

Now that you are able to spawn a new scripted thread you can begin to run processes co-operatively. As mentioned before, GM is not based upon a pre-emptive threading environment so each thread needs to yield to others after it has finished a portion of work; it is up to the script writer to define what a portion of 'work' for a thread and should be.

In the following example we create two threads, each counting from 0 to a specified number. This example does not use yield() -- what do you think will happen when you run it?

  global thread_func = function( a_name, a_count ) {
    print( "["+a_name+"] Starting..." );
    for (i = 0; i < a_count; i = i + 1)
    {
      print( "["+a_name+"] iteration: " + i );
    };

    print( "["+a_name+"] Finishing..." );
  };

  print( "[0] Ready to execute..." );
  thread( thread_func, 1, 100 );
  thread( thread_func, 2, 100 );
  sleep(1);
  print( "[0] Thread created..." );

Example: threads_02.gm

  [0] Ready to execute...
  [1] Starting...
  [1] Iteration 0
  ...
  [1] Iteration 99
  [1] Finishing...
  [2] Starting...
  [2] Iteration 0
  ...
  [2] Iteration 99
  [2] Finishing...
  [0] Thread Created...

Output: threads_02.gm

As you see from the output of the script, the two threads ran consecutively and not concurrently as you might have expected. The reason for this is that although two threads were created, the first was executed which blocked the second thread until it had completed. Once complete, the second thread was free to run until completion. If you intended to run a single cycle of each thread in turn you need to tell the GM machine when to yield execution to other threads. In order to tell a thread to yield, you simply call the scripted function yield() with no parameters.

The following example is the same script but with a yield() in the loop of each function.

  global thread_func = function( a_name, a_count ) {
    print( "["+a_name+"] Starting..." );

    for (i = 0; i < a_count; i = i + 1)
    {
      print( "["+a_name+"] iteration: " + i );
      yield();
    };

    print( "["+a_name+"] Finishing..." );
  };

  print( "[0] Ready to execute..." );
  thread( thread_func, 1, 100 );
  thread( thread_func, 2, 100 );
  sleep(1);
  print( "[0] Thread created..." );

Example: threads_03.gm

  [0] Ready to execute...
  [1] Starting...
  [1] Iteration 0
  [2] Starting...
  [2] Iteration 0
  ...
  [1] Iteration 99
  [2] Iteration 99
  [1] Finishing...
  [2] Finishing...
  [0] Thread Created...

Output: threads_03.gm

After running the above script, you will see that instead of running consecutively as witnessed in the first example the two threads appeared to run concurrently. Internally, the GM machine ran only one thread at a time but the yield() command instructed the virtual machine to switch contexts and execute the next thread.

Sometimes you may want to pause a thread for a specific length of time, for example if you had an NPC that needed to wait for 10 seconds at a waypoint before moving on. This can be achieved using the script command sleep(), which takes a numeric parameter of how many seconds the thread needs to sleep for. A sleeping thread yields until the sleep duration has passed, after which it resumes execution of the next command. Try experimenting with the samples above and replace a yield() with sleep to see the effects it has on execution.

Blocking and Signalling

In the real world context of a game you will not be using long loops and yield() very often; instead thread execution and context switching can be controlled by the more powerful blocking and signalling mechanism provided by GameMonkey. In games you would use a scripted thread to act as the 'brain' of an entity which would 'think' on every game cycle and perform an action based on an event or stimulus; the majority of the time it may be sitting waiting for that trigger, potentially by checking a loop. The following example demonstrates the traditional and extremely inefficient way of doing this:

  global WakeUp = false;

  global thread_1 = function( a_count ) {

    while (WakeUp == false)
    {
      // Snooze
      print("zzz");
      yield();
    }
    print("I just woke up, boy was I tired!");
  };

  // Launch thread
  thread( thread_1 );

  // Sleep for 1 secs then set the wakeup variable
  sleep(1);
  WakeUp = true;

Example: blocking_01.gm

When running the script, you will see a screen full of "zzz"'s before the thread 'wakes up'. The thread is actually still running here, taking up CPU cycles and waiting for the WakeUp call. GM presents a much more efficient way of handling this scenario via its blocking and signalling mechanism.

A thread can block itself until a specific signal (or one of several signals) is received. Blocking effectively takes a thread out of the active thread pool, yielding until a signal wakes it up. A simple example is demonstrated below:

  global thread_1 = function() {
    block("WAKEUP");
    print("I just woke up, boy was I tired!");
  };

  // Launch thread
  thread( thread_1 );

  // Sleep for 1 secs then set the wakeup variable
  sleep(1);
  signal("WAKEUP");

Example: blocking_02.gm

The thread will block until it receives a signal string of "WAKEUP", which is sent to it after 1 second. This is more efficient as it's not taking up interpreted script cycles checking for a scripted variable, we're literally waiting upon a message from the GameMonkey machine to continue. It is important to remember that a thread can block or signal on any gmVariable and not specifically strings, which I have used here as the most intuitive way to demonstrate the functionality.

Let's take a look a more complicated example involving two threads. Each thread will be created consecutively and will block on a different signal. The first signal will be thrown after the sleep command, which in turn will signal the other thread.

  global thread_1 = function() {
    block("WAKEUP");
    print("I just woke up, boy was I tired! You should wake up too!");
    signal("YOUTOO");
  };

  global thread_2 = function() {
    block("YOUTOO");
    print("What did you wake me up for?");
  };

  // Launch thread
  thread( thread_2 );
  thread( thread_1 );

  // Sleep for 1 secs then set the wakeup variable
  sleep(1);
  signal("WAKEUP");

Example: blocking_03.gm

  I just woke up, boy as I tired! You should wake up too!
  What did you wake me up for?

Output: blocking_03.gm

Often it is desirable for a thread to be able to block until it receives one of a selection signals - for example, you may want your scripted entity to walk to an area and then wait there until it receives a command to move to another point, attack an entity or indeed simply defend itself. The block and signal mechanism offers support for this by allowing you to block on multiple signals. The signal which resumes the thread is returned from the block command allowing you to act in appropriate manner.

  global blockfunc = function() {
    print( "Waiting for instruction, sir!" );
    signal_received = block("attack", "move", "defend");
    if(signal_received == "attack")
    {
      print("Attacking!");
    }
    else if (signal_received == "move")
    {
      print("Moving to position!");
    }
    else if (signal_received == "defend")
    {
      print("Defending til the death!");
    }
  };

  thread( blockfunc );
  sleep(1);
  signal("attack");

Example: blocking_04.gm

  Waiting for instruction, sir!
  Attacking!

Output: blocking_04.gm

In the example above, the thread will block upon 3 signals, attack, move or defend. The signal received will determine what the thread then proceeds to do -- in this case the attack signal is received so the entity attacks.

Each of the signalling examples presented until now have relied upon the signal being global, meaning that if two threads were waiting on the same signal, they would both be activated together. In games this would mean that all of your game units waiting for a wakeup call will spring into action at once. You will be relieved to know that it is possible to send a signal to a single thread rather than globally. To achieve this you must know the Thread Id of the thread you're signalling, this is returned by the normal thread function when you first create the thread. The following example is adapted to demonstrate the same block function being used with multiple threads.

  global blockfunc = function(name) {
    print( name + ", waiting for instruction, sir!" );
    signal_received = block("attack", "move", "defend");

    if (signal_received == "attack")
    {
      print(name + " is attacking!");
    }
    else if (signal_received == "move")
    {
      print(name + " is moving to position!");
    }
    else if (signal_received == "defend")
    {
      print(name + " is defending til the death!");
    }
  };

  thread_1 = thread( blockfunc, "woob" );
  thread_2 = thread( blockfunc, "foo" );

  sleep(1);

  signal("attack", thread_1);
  signal("defend", thread_2);

Example: blocking_05.gm

  woob, waiting for instruction, sir!
  foo, waiting for instruction, sir!
  woob is attacking!
  foo is defending til the death!

Output: blocking_05.gm

As you have seen, the blocking and signalling mechanism in GameMonkey Script allows you to create multiple threads within the virtual machine and have them remain dormant and taking up no execution cycles until a signal is received to call it back into action. This allows you to design complex behaviours within your entities that respond to the signals that are sent back and forth between objects.

Script Threads and 'this'

Now that you've explored the topics of GameMonkey's threading model, using block to pause them and signal to resume execution, it's time to look at how we can use the concept of this with threads to open up a lot of power for scripting in your game.

Each thread has a special gmVariable associated with it - the this variable. The concept of this allows you to effectively run a thread against an object and always have that object in scope. If you recall in the introductory article, I demonstrated how you could use this to reference specific objects in function calls. In GameMonkey's threading system, this can be used in exactly the same way, except that it can be accessed in every function. See the example that follows:

  global my_entity = {
    x = 50, y = 100, name = "test"
  };

  global move_ent_left = function() {
    print( "Starting thread - this.name = " + .name );

    while( this.x > 0 )
    {
      this.x = this.x - 10;
      print( this.name + " - x = " + this.x );
      yield();
    }

    print( "Ending thread - this.name = " + .name );
  };

  my_entity:thread( move_ent_left );
  sleep(1);

Example: threads_this_01.gm

  Starting thread -- this.name = test
  test -- x = 40
  test -- x = 30
  ...
  Ending thread -- this.name = test

Output: threads_this_01.gm

The code demonstrated above creates a simple table called my_entity, which has members x, y and name. The function move_ent_left will simply decrement the x position of this by 10 units, is created in the global scope and accepts no parameters, so we can't 'cheat' by passing an object instance to the function.

The thread itself is created as normal using the thread() function, but with one key difference -- the my_entity table is passed as this via the syntax my_entity:thread( func );

The next example will show the move_ent_left function being used for multiple objects and on multiple threads.

  global robot = {
    x = 50, y = 100, name = "robot"
  };

  global player = {
    x = 150, y = 200, name = "player"
  };

  global move_ent_left = function() {
    print( "Starting thread - this.name = " + .name );

    while( this.x > 0 )
    {
      this.x = this.x - 10;
      print( this.name + " - x = " + this.x );
      yield();
    }

    print( "Ending thread - this.name = " + .name );
  };

  robot:thread( move_ent_left );
  player:thread( move_ent_left );
  sleep(1);

Example: threads_this_02.gm

  Starting thread -- this.name = robot
  robot -- x = 40
  Starting thread -- this.name = player
  player -- x = 140
  ...
  Ending thread -- this.name = robot
  player -- x = 90
  player -- x = 80
  ...
  Ending thread -- this.name = player

Output: threads_this_02.gm

Clever use of threads and this is an extremely useful way of giving different entities different behaviours, even if they have the same properties; all you would need to do is pass different functions on the thread. In the example that follows, we define 5 "robots" that each posses a different behaviour -- this behaviour is executed at runtime.

  global create_robot = function(x, y, name) {
    return { x = x, y = y, name = name, id, class="robot" };
  };

  global behaviour_stupid = function() {
    print( .name + " is acting stupidly" );
  };

  global behaviour_seek = function() {
    print( .name + " is seeking resources" );
  };

  global behaviour_rest = function() {
    print( .name + " is resting" );
  };

  global robot_def = {
    {"tom", behaviour_seek},
    {"mike", behaviour_rest},
    {"jane", behaviour_stupid},
    {"bob", behaviour_stupid},
    {"sarah", behaviour_seek}
  };

  for(id = 0; id < 5; id = id + 1)
  {
    robot = create_robot(1 * id, 10 * id, robot_def[id][0]);
    robot:thread(robot_def[id][1]);
  }

  sleep(1);

Example: threads_this_03.gm

  tom is seeking resources
  mike is resting
  jane is acting stupidly
  bob is acting stupidly
  sarah is seeking resources

Output: threads_this_03.gm

If the robot were your normal game entity that is displayed on screen, I'm sure you could appreciate how being able to script them in this way is extremely powerful.

Thread States

Games often utilise finite state machines to control the behaviour and state of an object. State machines are often used in game entity artificial intelligence scripts which may want to act upon a specific stimulus and then return to their previous state. Consider the PacMan ghosts who change into to a 'panic' state when the PacMan eats a power-up and then revert back to 'roaming' once the power-up has worn off; or the patrolling unit in an RTS game that encounters an enemy, intercepts to attack it and then resumes its patrol once the threat has passed.

I have demonstrated how you can effectively use different functions to interact with a scripted object through the use of threads and this. As you have seen, GameMonkey Script threads execute a function - the state binding allows you to change the function that is running on a thread. The thread will keep the same Thread Id, but will lose any locally defined variables in the change of execution context. The following code will demonstrate how to initiate a state and change to another:

  global awake = function() {
    print("I'm awake!");
  };

  global resting = function() {
    print("I am resting");
    sig = block("wakeup");
    if (sig == "wakeup")
    {
      stateSet( awake );
    }
  };

  global init_state = function() {
    // Set a state on the thread
    // we're now using states
    stateSet( resting );
  };

  thread( init_state );
  sleep(1);
  signal("wakeup");

Example: states_01.gm

I am resting
I'm awake!

Output: states_01.gm

The state of a thread is set by the first call to the in-built global function stateSet() that accepts the new state function and any additionally required parameters. You will notice that this behaves almost exactly how you created the thread in the first place, except this time you are still using the current thread and just changing the function that is being executed. If you want to pass this to the new state, you must explicitly do so when calling stateSet().Once you have set a thread state you can transition to a new state at any time by a subsequent call to stateSet().

Any subsequent changes in state allow you to query the current state function by calling stateGet() or the previous state by calling stateGetLast(). This is useful as GameMonkey allows you to do comparisons on functions, letting you react according to the previous state or simply just resume the previous behaviour by switching the state back to what it was before. If stateGet() returns null, the thread isn't engaged in state behaviour; likewise if stateGetLast() returns null the thread hasn't had a previous state.

The example that follows will demonstrate an object that is created, blocks until a condition is met, performs an action and then resumes the previous action. In one object, it will start as asleep, stir and then go back to sleep -- another will start asleep, get panicked at a loud noise and then cause all sorts of chaos for the player.

  global ent_state_panic = function() {
    print(.name + " is panicking, firing off alarms and attracting attention to you");
  };

  global ent_state_awake = function() {
    print(.name + " is waking up");
    this:stateSet( stateGetLast() ); // revert to previous state
  };

  global ent_state_sleeping = function() {
    print(.name + " is sleeping");
    sig = block("quiet_noise", "loud_bang", "kill");

    if (sig == "quiet_noise")
    {
      this:stateSet( ent_state_awake );
    }
    else if (sig == "loud_bang")
    {
      this:stateSet( ent_state_panic );
    }
    else
    {
      print( .name + " killed" );
    }
  };

  /// Initialise the state on the entity
  global ent_state_init = function(func) {
    print( .name, " state initialised");
    this:stateSet(func);
  };

  global ent_1 = { name = "roboguard 1000" };
  global ent_2 = { name = "old ticker" };

  // Create two threads, one for each entity and initialise them in the sleeping state
  ent_1.threadid = ent_1:thread( ent_state_init, ent_state_sleeping );
  ent_2.threadid = ent_2:thread( ent_state_init, ent_state_sleeping );

  sleep(1);
  print("You stand on a twig");
  signal("quiet_noise");
  sleep(1);

  print("You fire a gun at " + ent_1.name + " causing a loud noise");
  signal("loud_bang", ent_1.threadid);

  // Tell the entity to die
  signal("kill", ent_2.threadid);

Example: states_02.gm

  roboguard 1000 state initialised
  roboguard 1000 is sleeping
  old ticker state initialised
  old ticker is sleeping
  You stand on a twig
  old ticker is waking up
  old ticker is sleeping
  roboguard 1000 is waking up
  roboguard 1000 is sleeping
  You fire a gun at roboguard 1000 causing a loud noise
  roboguard 1000 is panicking, firing off alarms and attracting attention to you
  old ticker killed

Output: states_02.gm

The state transitions involved above are described in this diagram:

blocksigstates

Image: blocksigstates.gif

Sometimes it's useful to know about a state change before it happens to allow you to run cleanup code or trigger another behaviour. To achieve this you can call the function stateSetExitFunction() and pass a function object. The function you pass will be called just before the state of the thread changes, allowing you to run whatever code you need to. When the function completes the state will transition as expected; you could use this to play a sound effect before the real event happens, for example.

  global awake = function() {
    print("I'm awake!");
  };

  global waking = function() {
    print("I am stirring...");
  };

  global resting = function() {
    print("I am resting");

    sig = block("wakeup");
    if (sig == "wakeup")
    {
      stateSetExitFunction( waking );
      stateSet( awake );
    }
  };

  global init_func = function() {
    // set state on thread
    stateSet( resting );
  };

  thread( init_func );
  sleep(1);
  signal("wakeup");

Example: states_03.gm

  I am resting
  I am stirring...
  I'm awake!

Output: states_03.gm

All state changes happen on the currently operating thread, but sometimes it would be useful to change the state of another thread in the machine. Imagine you have a thread blocking on a signal for the player to move out of cover; if the cover explodes, you may want to force the players taking cover into a different state, such as the one that decides the next action. The stateSetOnThread() function allows you to do just this and requires a thread id and a state function.

  global state_advance = function() {
    print("Leaving cover and advancing");
  };

  global state_decide_action = function() {
    print("I have to decide on a next action");
  };

  global state_hiding = function() {
    print("Behind cover, waiting to advance");
    sig = block("advance");
    if (sig == "advance")
    {
      stateSet( awake );
    }
  };

  global init_state = function() {
    stateSet( state_hiding );
  };

  tid = thread( init_state );
  sleep(1);

  // Signal isn't thrown, tell this thread to change state
  print("Cover explodes!");

  stateSetOnThread( tid, state_decide_action );

Example: states_04.gm

  Behind cover, waiting to advance
  Cover explodes!
  I have to decide on a next action

Example: states_04.gm

As you have seen, the threading functionality built into GameMonkey Script provides a useful toolset to control behaviours of your entities and offers flexible solutions to achieve your scripting needs.

Integrating GameMonkey with your Game

GameMonkey Script offers a lot of advanced functionality to script your game entities which can become quite complex for your script writers. In a real-world usage scenario you may wish to simplify your scripting interface and use many of GameMonkey Script's threading features invisibly. For example, if you create an NPC entity that has an update routine it is often more intuitive for a script writer to simply write a script such as:

  npcupdatefunc = function() { ... do stuff... };
  npc = createNPC(npcupdatefunction);

Rather than:

  npcupdatefunc = function() { ... do stuff... };
  npc = createNPC();
  npc:thread(npcupdatefunction);

Your game may also feature asynchronous events such as dialog boxes or entity movement instructions that use the blocking functionality.

This section will describe how to work with the GameMonkey Script API to take advantage of the threading functionality and offer some simple methods by which you can simplify the interface you provide to your script writers without compromising on the flexibility offered by the scripting language.

Creation of gmThreads

A new thread can be created at any time in the virtual machine by calling the gmMachine::CreateThread() method. The result of this action is that a new gmThread object is created and a unique thread id is returned. You can call this method passing a gmVariable to act as this and importantly, a gmVariable containing the gmFunctionObject that the thread needs to execute. The following code demonstrates the creation of a simple thread via the API that calls an existing function within script -- for simplicity's sake this script is embedded in the application.

  #include <iostream>
  #include "gmThread.h"

  int main(int argc, char* argv[])
  {

    // Create gm virtual machine
    gmMachine gm;

    // A test script which creates the function we're testing
    const char *testscript = "global threadfunc = function() { print(\"Hello, threads!\"); };";

    // Execute the script to create the function in the VM
    if (gm.ExecuteString( testscript, NULL, true ))
    {
      bool first = true;
      std::cout << gm.GetLog().GetEntry(first);
      return 1;
    }

    int new_threadId = 0;

    // Allocate a thread within the machine
    gm.CreateThread( gmVariable::s_null, gm.GetGlobals()->Get(&gm, "threadfunc"), &new_threadId );

    // Execute the machine
    gm.Execute(1);
    return 0;
  }

Example: threads_01a.cpp

The code is simple; a script is executed to create the scripted function (a global named threadfunc) and a new thread is created using the reference to the script function obtained by reading from the global table. One thing to note is that as we've created a scripted function using script, we need to call gmMachine::Execute() to run the thread because the original ExecuteString() call actually created a thread to run in and it is still seen as the active thread in the machine.

To demonstrate the passing of this to a thread upon creation we simply pass an actual variable installed of gmVariable::s_null (the static null value variable). An example of this follows, you will notice that the string I pass as this is available in the script function we call.

  #include <iostream>
  #include "gmThread.h"

  int main(int argc, char* argv[])
  {
    // Create gm virtual machine
    gmMachine gm;

    // A test script which creates the function we're testing
    const char *testscript = "global threadfunc = function() { print(\"'this' passed as - \", this); };";

    // Execute the script to create the function in the VM
    if (gm.ExecuteString( testscript, NULL, true ))
    {
      bool first = true;
      std::cout << gm.GetLog().GetEntry(first);
      return 1;
    }

    int new_threadId = 0;

    // Allocate a thread within the machine
    gm.CreateThread( gmVariable(gm.AllocStringObject("Hello, this!")), gm.GetGlobals()->Get(&gm, "threadfunc"), &new_threadId );

    // Execute the machine
    gm.Execute(1);

    return 0;
  }

Example: threads_01b.cpp

As you expect, it's entirely possible to execute a native function as the thread function, you simply bind the function as normal and use the gmFunctionObject pointer wrapped up by a gmVariable. The following code demonstrates the same result as the first, this time binding a function and calling that.

  int GM_CDECL gmMyThreadFunc(gmThread *a_thread)
  {
    std::cout << "Hello, threads!" << std::endl;
    return GM_OK;
  }

  int main(int argc, char* argv[])
  {
    // Create gm virtual machine
    gmMachine gm;

    // Bind the function to use by creating a gmFunctionObject
    gmFunctionObject *threadfunc = gm.AllocFunctionObject( gmMyThreadFunc );

    int new_threadId = 0;

    // Allocate a thread within the machine
    gm.CreateThread( gmVariable::s_null, gmVariable(threadfunc), &new_threadId );

    return 0;
  }

Example: threads_02.cpp

Any object can be passed to the thread as this if it has been registered within the gmMachine as a gmType and wrapped in a gmVariable. The following example shows how you can access this in the callback via the gmThread::GetThis() method. In this case I expect it to be a string type, but in your code you'd most likely use your own entity types.

  int GM_CDECL gmMyThreadFunc(gmThread *a_thread)
  {
    GM_ASSERT( a_thread->GetThis()->m_type == GM_STRING );

    gmStringObject *thisstr = reinterpret_cast<gmStringObject*>(a_thread->GetThis()->m_value.m_ref);

    std::cout << "'this' passed as " << thisstr->GetString() <<
    std::endl;
    return GM_OK;
  }

  int main(int argc, char* argv[])
  {
    // Create gm virtual machine
    gmMachine gm;

    // Bind the function to use by creating a gmFunctionObject
    gmFunctionObject *threadfunc = gm.AllocFunctionObject( gmMyThreadFunc );

    // Add function to the global table so scripts can access it
    gm.GetGlobals()->Set(&gm, "threadfunc", gmVariable(threadfunc) );

    // Call script to make callback, pass a variable containing "hello" as this
    const char *script = "text = \"hello\"; text:thread( threadfunc );";

    gm.ExecuteString( script );
    return 0;
  }

Example: threads_03.cpp

Working with 'this'

So far, most of the C++ binding examples we've been using have relied on us passing the objects to work on as a parameter to the callback functions. However you're more likely to want to pass this to functions explicitly or implicitly by binding them to an instance of a type -- this is how we've been doing it in many of the actual scripts you've seen. This next section will demonstrate how to access and use this in your native callbacks and continue to provide a simple example of some code to extend your bound types.

As you have seen previously, every gmThread has a stack slot allocated for the this variable. This can be accessed in raw gmVariable form by calling gmThread::GetThis(). The main difference between GetThis() and Param() is that GetThis() returns the gmVariable via a const pointer and not by value like the Param() functions. In the previous example (threads_03.cpp), you were accessing a string variable passed as this. It is often required that you validate the type of this variable -- the gmThread.h file defines a series of useful macros for checking function parameters, there are also a few for checking the type of this, such as GM_CHECK_THIS_STRING, which will return a thread exception if this is passed as anything other than a string type.

Like the Param() functions, there are several helpers for automatically and safely converting this back to the various native types associated with the GameMonkey type. So, for example ThisString() returns as a const char* and ThisInt() returns an int.

  int GM_CDECL gmThisTest(gmThread *a_thread)
  {

    GM_CHECK_THIS_STRING;

    std::cout << "'this' passed as " << a_thread->ThisString() <<
    std::endl;
    return GM_OK;
  }

Example: this_01.cpp

There are three methods for returning your user types, ThisUser() returns a void* and the gmType of the object it holds; ThisUserCheckType() will only return a non-NULL pointer to the object if the type you pass in matches the object type and finally ThisUser_NoChecks() passes you just the pointer to the data held in the user object. Of course, these are simply common conversions -- you are encouraged to create your own routines should you require anything else, such as converting back and forth to your entity type automatically.

Script-Extensible Objects

The use of this becomes incredibly important when it comes to working with your own user types and binding functions and data to them. The rest of this section will demonstrate a highly simplified game entity class that allows you to extend it with scripted properties.

The first thing we do is define our game entity. In this example we declare it to have the following attributes:

Variable Read/Write? Description
Id Read Guid of the entity
X Read, Write X position of the entity
Y Read, Write Y position of the entity

If you think back to the simple Vector binding I demonstrated in the original articles, you will remember that we used the raw Vector* pointer as the data tied to GameMonkey and bound the Dot/Ind operators to allow modification of the underlying data. This time along we want to take advantage of the flexibility of GameMonkey and effectively allow additional properties to be added to the object -- for example, allowing the script writer to add a "name" property to the object.

  struct Entity
  {
    Entity() : Id(0), X(0), Y(0) { }
    Entity(int id) : Id(id), X(0), Y(0) { }
    int Id, X, Y;
  };

Example: ext_01.cpp

The first step to achieving this goal is to create a proxy object that holds both the raw Entity data and a gmTableObject that will be used to hold our additional properties; this proxy is represented by a ScriptEntity class. We have a choice to make at this stage; do we want to be invasive to the Entity class and allow it to hold a pointer to the ScriptEntity or do we want the ScriptEntity to wrap the Entity object? I have chosen to add a pointer to the ScriptEntity into the Entity class; this enables any other consumers of the Entity class to be aware of the additional properties and create their own or read any existing ones -- this allows for some nice data-driven entities to be created as you will see shortly.

  struct ScriptEntity
  {
    ScriptEntity(Entity &ent) : EntityObject(ent), ScriptProperties(NULL) {}
    Entity &EntityObject;
    gmTableObject *ScriptProperties;
  };

Example: ext_01.cpp

Once the basic objects have been defined the type is bound as normal. I have bound a global function createEntity() to the script environment which will allocate a new Entity from the EntityManager and also allocate a new ScriptObject and gmTableObject for it.

  int GM_CDECL gmCreateEntity(gmThread *a_thread)
  {
    // Create an entity from the manager
    int entityId = 0;

    Entity &ent = s_EntityManager.CreateEntity(entityId);

    // Allocate a script entity & construct it
    ScriptEntity *sent = reinterpret_cast<ScriptEntity*>(s_scriptents.Alloc());

    GM_PLACEMENT_NEW( ScriptEntity(ent), sent );

    // Tie back to entity
    ent.ScriptObject = sent;

    // Create a property table
    sent->ScriptProperties = a_thread->GetMachine()->AllocTableObject();

    // Tell GM how much memory we're using
    int memadjust = sizeof(gmTableObject) + sizeof(Entity) + sizeof(ScriptEntity);
    a_thread->GetMachine()->AdjustKnownMemoryUsed(memadjust);

    // return to client
    a_thread->PushNewUser( sent, s_entitytype );
    return GM_OK;
  }

Example: ext_01.cpp

I've chosen to use some of the in-built GameMonkey memory allocation routines for the ScriptEntity creation (gmMemFixed); this is a fairly simple fixed memory allocator which uses a freelist for reallocations. You are free to use the normal new and delete operators or to rely on memory allocation routines from your own engine.

Once this function is bound, you can create entities in script -- but they're not much use until you bind the operators. In this case I will bind the GetDot and SetDot operators only. They will both work in similar ways; the object is retrieved from the relevant operand and the text of the property is resolved from the gmStringObject it is passed as. If the property is resolved to either X, Y or Id (the properties of the Entity class), we simply access the relevant property on the Entity. Any other property accesses will go straight to the gmTableObject we created on the ScriptEntity, thus allowing us to get and set properties that didn't exist on the base class. The SetDot operator code is shown below as an example.

  void GM_CDECL gmOpSetDot(gmThread *a_thread, gmVariable *a_operands)
  {
    if (a_operands[0].m_type != s_entitytype)
    {
      a_thread->GetMachine()->GetLog().LogEntry( "gmEntity:OpSetDot invalid type passed" );
      a_operands[0] = gmVariable::s_null;
      return;
    }

    // Get scriptentity back
    ScriptEntity *sent = reinterpret_cast<ScriptEntity*>(a_operands[0].GetUserSafe(s_entitytype));

    // Get name of 'get' prop
    std::string propname = a_operands[2].GetCStringSafe();

    // Test for our known properties and return their values back immediately

    if (propname == "X")
    {
      sent->EntityObject.X = a_operands[1].GetIntSafe();
    }
    else if (propname == "Y")
    {
      sent->EntityObject.Y = a_operands[1].GetIntSafe();
    }
    else if (propname == "Id")
    {
      a_thread->GetMachine()->GetLog().LogEntry( "gmEntity:OpSetDot cannot set Id" );
      a_operands[0] = gmVariable::s_null;
    }
    else
    {
      // Otherwise, store a value in the table
      sent->ScriptProperties->Set(a_thread->GetMachine(), propname.c_str(), a_operands[1]);
    }
  }

Example: ext_01.cpp

When you bear in mind that GameMonkey Script functions can also be stored as variables, you will see that you can start adding scripted functions to the base entity class with ease -- be it from the C++ API or from script itself. All functions invoked on the object have this implicitly passed, so code such as the following is possible:

  ent = createEntity();
  ent.name = "somebody";
  ent.sayname = function() {
    print( "My name is: ", this.name );
  };

  ent.sayname();

By using the C++ API and accessing this, you can begin to add some script-bound functions to the ScriptEntity's ScriptProperties table with ease. The following example shows a simple function that displays the entity's position, id and name -- with name being a property added within script.

  // The entity is passed as this
  int GM_CDECL gmEntityInfo(gmThread *a_thread)
  {
    ScriptEntity *sent = reinterpret_cast<ScriptEntity*>(a_thread->ThisUserCheckType(s_entitytype));

    if (sent == NULL)
    {
      a_thread->GetMachine()->GetLog().LogEntry("gmEntityInfo: Expected entity type as this");
      return GM_EXCEPTION;
    }

    std::stringstream infotext;
    infotext << "EntityId: ";
    infotext << sent->EntityObject.Id;
    infotext << " - Postition("; infotext << sent->EntityObject.X;
    infotext << ","; infotext << sent->EntityObject.Y; infotext << ") - Name: ";

    // Get name from table and pass to object
    gmVariable namevar = sent->ScriptProperties->Get(a_thread->GetMachine(), "name");

    if (namevar.IsNull() || namevar.m_type != GM_STRING)
    {
      infotext << " [No name]";
    }
    else
    {
      infotext << namevar.GetStringObjectSafe()->GetString();
    }

    std::cout << infotext.str() << std::endl;
    return GM_OK;
  }

Example: ext_01.cpp

In the full example code ext_01.cpp (not shown), I added an entry to the ScriptProperties table to automatically hold this function for each entity created by the script engine:

  // Bind an additional method to the entity's prop table
  sent->ScriptProperties->Set(a_thread->GetMachine(), "ShowInfo", gmVariable( a_thread->GetMachine()->AllocFunctionObject(gmEntityInfo)) );

The script we run can be modified as follows to use the new function:

  ent = createEntity();
  ent.name = "somebody";
  ent.ShowInfo();

And the output?

  EntityId: 1 -- Position(100,0) -- Name: somebody

In this section you've learned how to use GameMonkey Script to extend an existing native class and attach new data and functions to it. Armed with this information you are free to create powerful entity types and allow your script writers to extend them without any changes to the engine code.

Threads, The Stack and Function Calls

As threads are an important part of GameMonkey Script, I will use this section to cover how they work in more detail. I will also discuss how a function is called within the GameMonkey Virtual Machine (VM) and how this is tied to threads.

A thread on the VMis composed of the following components:

  • A unique identifier
  • A stack
  • A function to execute
  • A pointer to the current instruction (if any)
  • A list of blocks and signals pending on the thread
  • State information about the thread

The thread stack is a effectively a list of gmVariable objects that is used to hold the function being executed, the variable for this, any function parameters passed to the thread and then finally any working variables, including return values from functions.

The thread function is a gmFunctionObject which can be either a native function callback or a scripted function. A scripted function is a series of bytecode instructions that are interpreted by the GameMonkey VM to create the behaviours you see in script. The execution of the VM bytecode is dependent on the structures in the thread and the VM cannot execute a function without it.

When a function is called in GameMonkey, what actually happens? The first thing pushed to the stack is a variable containing this, it will contain null if there is no value for this. A variable holding the function object is next to be pushed to the stack, followed by any function parameters in order. Finally, a stackframe is pushed to complete the setup and initialise all internal data and structures to ready the thread for execution on the VM. The stackframe is important as it allows the VM to maintain the correct pointers to instructions and data to, for example, allow nested function calls to be made within the system. The function is then executed by the virtual machine, either by calling a native function or by interpreting the bytecode involved with the scripted function. Any return value is pushed back to the stack and the stack frame is popped, allowing the script to continue where it left off before the function call.

We can see this in action by manually creating a thread and calling a native function. The first code we write is a simple function that accepts two integer parameters and writes them to cout.

  int GM_CDECL gmFunctionTest(gmThread *a_thread)
  {
    GM_CHECK_INT_PARAM( a_param1, 0 );
    GM_CHECK_INT_PARAM( a_param2, 1 );
    std::cout << "Param 1: " << a_param1 << std::endl;
    std::cout << "Param 2: " << a_param2 << std::endl;
    return GM_OK;
    }

Next, we follow the steps detailed above to call the function via the GameMonkey API.

  gmMachine gm;
  int threadid = 0;

  gmThread *thread = gm.CreateThread(&threadid);
  thread->PushNull(); // Push 'this'
  thread->PushFunction( gm.AllocFunctionObject(gmFunctionTest) ); // push function to call
  thread->PushInt(50);
  thread->PushInt(100);
  thread->PushStackFrame(2);

Example: functioncall_01.cpp

As you can see, we create a thread, push the required information to it and then finally execute it by pushing the stack frame using gmThread::PushStackFrame. If the function were a scripted function, you would need to run it by executing the thread using gmThread::Sys_Execute or by using gmMachine::Execute() to execute the whole machine.

  gmMachine gm;

  // Create a global scripted function
  gm.ExecuteString(
    "global func = function(a, b) { print(\"Param 1:\", a); print(\"Param 2:\", b); };"
  );

  gmFunctionObject *func = gm.GetGlobals()->Get(&gm, "func").GetFunctionObjectSafe();

  int threadid = 0;

  gmThread *thread = gm.CreateThread(&threadid);
  thread->PushNull(); // Push 'this'
  thread->PushFunction( func ); // push function to call
  thread->PushInt(50);
  thread->PushInt(100);
  thread->PushStackFrame(2);
  thread->Sys_Execute();

Example: functioncall_02.cpp

If we'd have wanted to return a value from the function it's a simple case of pushing it to the stack before we return and retrieving it either by getting the top of the stack from the thread, or if it was a scripted function the call to Sys_Execute accepts an optional pointer to a gmVariable allowing you to capture this if you need to.

This section has shown you how to manually call functions and how they interact with the threads and stack. It is recommended that you use the gmCall helper for most of your script calls as it wraps all of this functionality for you already and reduces the chance of errors occurring.

Callback Return Values

So far, all of the examples of bound functions you've seen have returned GM_OK or GM_EXCEPTION. This section will describe how these are used by the virtual machine and discuss other values you can use.

When a function call is made or an operator callback is invoked, you have a chance to indicate success or failure to the VM execution engine. If the call is successful (GM_OK), everything continues as normal -- but what happens if there is an error? An error could be that you received the wrong parameter type to a function callback, or maybe even an internal game error that requires your script environment to be notified. In such cases, you would return a value of GM_EXCEPTION to the thread, which has the effect of causing it to terminate.

Usable values are as follows:

Value Meaning
GM_OK Success
GM_EXCEPTION An error occurred
GM_SYS_YIELD Causes a thread to immediately yield execution to another thread
GM_SYS_BLOCK A block has been set on the thread, see later for more detail
GM_SYS_SLEEP Force the thread to sleep after setting the Sys_SetTimeStamp and Sys_SetStartTime values accordingly.

There are other values available but they are internal and shouldn't be used for risk of corrupting the execution of the VM.

Signalling via the API

Like using signals in the script language you can signal threads from the API. This allows you to implement event-based systems that trigger game events as signals to the entire virtual machine as or to specific threads that are running on particular game entities. One use may be an RPG game that pops up a dialog box for a quest; in script you'd tell the engine to open the quest dialog with a specific text and wait for the user to accept or decline it. An example script would look like:

  ShowQuestDialog( Quests.SaveThePrincess );

  response = block( QuestDialog.Accept, QuestDialog.Decline );
  if (response == QuestDialog.Accept)
  {
    // do something
  }
  else
  {
    // do something else
  }

In this example, the ShowQuestDialog() function would be a native function that pops up a non-modal dialog that itself waits for a button response event from your GUI subsystem. As the dialog is non-modal, you need your script to respond from the user's input in some way, so you block on two signals to indicate accept or decline. Because the thread is waiting for a block it becomes dormant in the GameMonkey machine and therefore takes up no execution time until it's woken by a signal. The signal itself would be fired by your script subsystem in response to a GUI button press event for that specific dialog.

Let's look at some sample code:

  enum QuestDialog
  {
    QD_DECLINE,
    QD_ACCEPT
  };

  // This function simulates a modal quest dialog
  int GM_CDECL gmShowQuestDialog(gmThread *a_thread)
  {
    std::cout << "A princess is in danger, do you wish to save her?" << std::endl << "[Accept | Decline]" << std::endl;
    return GM_OK;
  }

  void HandleUserAcceptQuest(gmMachine &gm, int threadid, QuestDialog response)
  {
    // Fire signal to thread to indicate user's choice
    gm.Signal( gmVariable( (int)response ), threadid, 0 );
  }

  int main(int argc, char* argv[])
  {
    gmMachine gm;

    // Bind ShowQuestDialog function to script
    gm.GetGlobals()->Set( &gm, "ShowQuestDialog", gmVariable(gm.AllocFunctionObject( gmShowQuestDialog )) );

    // Create a global table to hold Ids for QuestAccept/Decline
    gmTableObject *dialogResponses = gm.AllocTableObject();
    dialogResponses->Set( &gm, "Accept", gmVariable(QD_ACCEPT) ); // Accept = 0
    dialogResponses->Set( &gm, "Decline", gmVariable(QD_DECLINE) ); // Decline = 0
    gm.GetGlobals()->Set( &gm, "QuestDialog", gmVariable( dialogResponses ) );

    // Run an example script that calls dialog and blocks on response
    const char *script = "ShowQuestDialog(); \n"
      "response = block( QuestDialog.Accept, QuestDialog.Decline ); \n"
      "if (response == QuestDialog.Accept) { print(\"[Quest accepted]\"); } else { print(\"[Quest declined]\"); }";

    int threadid = 0;

    // Run script and capture thread it's running on
    gm.ExecuteString( script, &threadid );
    Sleep(5000); // Wait for 5 seconds to simulate user deciding to accept
    HandleUserAcceptQuest(gm, threadid, QD_ACCEPT);

    // Tick the machine along
    gm.Execute(0);

    return 0;
  }

Example: signals_01.cpp

The majority of the code is actually setting up our global script variables and binding the demonstration quest dialog function. The sample script is run on the machine which immediately calls the quest dialog function (in your implementation you'd open a real GUI window) and then immediately blocks for the response. The signal itself is sent via a call to gmMachine::Signal, which accepts a gmVariable and an optional thread Id -- you will see that this is identical to calling signal from script.

A second example is that of a door created in script. The door itself will remain closed until the game instructs it to open, perhaps by a person using a switch or firing some kind of trigger. The script for the door is simple; the door is created and immediately blocks for the signal to open. When this trigger is received, an animation game event is played asynchronously while the door script sleeps for 5 seconds. After it resumes, the close animation is played and the door goes back to blocking -- this behaviour will repeat forever or until the thread is terminated by the game, perhaps in response to the door becoming impassible or destroyed.

  global DoorFunction = function() {
    while(true)
    {
      print(this, "waiting for use...");
      block("usedoor");
      this:startAnimation("dooropen");
      sleep(5);
      this:startAnimation("doorclose");
    }
  };

  createDoor(DoorFunction);

Example: signals_02.cpp -- Door Script

The native callback createDoor() will create a new door instance and run the function you pass it. Note that in this example I just demonstrate the object using a text string, your game will most likely feature an actual door entity being created and returned as a user object.

  int GM_CDECL gmCreateDoor(gmThread *a_thread)  {

    GM_CHECK_FUNCTION_PARAM(a_func, 0);

    // Simple example, return "TESTDOOR" as the user object
    gmStringObject *door = a_thread->GetMachine()->AllocStringObject("TESTDOOR");

    // Call passed in function as a new thread (gmCall does this under the hood)
    gmCall gmcall;
      gmcall.BeginFunction(a_thread->GetMachine(), a_func, gmVariable(door), false);
    gmcall.End();

    a_thread->PushString(door);
    return GM_OK;
  }

Example: signals_02.cpp

To open the door at any point in the game, you fire the signal. I'm executing the next frame of the VM manually here, but your game loop will be doing this each frame automatically.

  // Open the door... fire global signal for now
  gm.Signal(gmVariable( gm.AllocStringObject("usedoor") ), GM_INVALID_THREAD, 0);

Example: signals_02.cpp

The output of this whole example:

  TESTDOOR waiting for use...
  TESTDOOR starting animation: dooropen
  TESTDOOR starting animation: doorclose
  TESTDOOR waiting for use...

Output: signals_02.cpp

This example is an excellent way of showing how the thread that controls the door can be created within the game code itself, removing the need for a user to explicitly create them to run the door script. To the user, they are simply creating a door that will play out their behaviour, they don't even have to know about the signal being fired by the game.

Blocking via the API

It is possible to set a thread to block using the API and it allows more flexibility than blocking in script. When you block from within the GM Scripting language itself the currently executing thread is suspended immediately and is set to the status of "blocked". Blocking a thread from the API allows you to continue processing as normal; the blocked state of the thread is only registered upon the next execution cycle of the virtual machine. To block a thread, you need to use the gmMachine::Sys_Block function; this function takes a thread Id and a list of gmVariables to block on. Although it is possible to block on a thread that isn't the current thread, it is not recommended as it may cause undesirable or unpredictable behaviour in the scripts running on the machine (such as a thread blocking for a signal that will never occur).

A quick example will show you how to set up a block via script:

  enum
  {
    SIG_ZERO,
    SIG_ONE
  };

  int GM_CDECL gmBlockTest(gmThread *a_thread)
  {
    gmVariable blocks[] = { gmVariable(SIG_ZERO), gmVariable(SIG_ONE) };

    int ret = a_thread->GetMachine()->Sys_Block( a_thread, 2, blocks );
    if (ret == -1)
    {
      return GM_SYS_BLOCK;
    }
    a_thread->Push(blocks[ret]);
    return GM_OK;
  }

Example: blocks_01.cpp

In this callback function I have set up two variables containing the values 0 and 1 and have instructed the GameMonkey machine to block the currently running thread on them. Under the hood, the gmMachine::Sys_Block function will attempt to use up any signals already pending on the thread; if a matching signal is found for one of the blocks, it will return immediately with the index of the block variable you supplied. When this happens, I push the variable back to the thread stack as a return value so that it returns the block immediately to the user to allow them to check which block was signalled. If no corresponding signal is found, Sys_Block returns a value of -1 -- when this happens you return a value of GM_SYS_BLOCK to indicate to the VM that this thread should be suspended until a signal is received to wake it. When you bind the function above to the machine, it lets you run a script such as:

  r = blocktest();

  print( "signal fired:", r);

This will block until you signal the thread either using gmMachine::Signal or the signal() command from script.

Games often feature asynchronous actions, such as playing an animation or sound, actor pathfinding/movement and so on. In the real world, many of these actions will be kicked off with an event dropped onto a message queue which is picked by the relevant system. Imagine the case of an NPC being instructed to move across town; the entity management system will pick up the movement message and move the entity a little bit each frame until the NPC arrives at its destination many frames later. At this point the entity manager it will signal back to the game that the movement has completed.

Your scripts will often want to trigger asynchronous behaviours but will need to treat them as if they are synchronous by waiting for the result of the task before deciding how to proceed. If the machine-bound function returned immediately after queuing the message, the script would continue as normal without waiting for completion. In a previous section you achieved this in script by using GameMonkey's blocking and signalling functionality; in this case you could fire your event and then immediately block for the signal back from the game engine to wake up the thread.

  doasyncbehaviour();
  ret = block( "ok", "failed" );
  // continue as normal

Although this works, it can be a little clunky in practice and requires that your script writers always remember to block after an asynchronous behaviour. In this next section I will demonstrate an example of working with asynchronous game events elegantly with GameMonkey Script.

For this example I will allow you to imagine a town guard in an RPG game that wanders back and forth between two towers. When he gets to a tower, he'll inspect the area and move on if nothing is amiss. The guard's behaviour is described as the following series of states:

guardstates

Image: guardstates.gif

In the game engine this will be achieved by initiating the movement using a goto message to the entity manager. Upon arrival at the destination a goto_success message is sent back to the game system -- should anything happen such as the destination being unreachable, the entity being attacked or so on, a goto_failed message is sent back to the game, along with other messages for the next action (if any).

The sequence of events in this asynchronous call are as follows:

  1. Script calls NPCWalkTo( dest )
  2. Game pushes goto message to message queue
  3. Script call blocks thread and returns
  4. Game frame advances, entity manager picks up movement message
  5. Entity manager moves NPC each frame until destination is reached
  6. Message is sent back to script manager to inform destination reached
  7. Script manager signals thread of completion, script progresses as
  8. normal

Look at the example code blocking_02.cpp for a simple yet realistic implementation of the above behaviour. In the code, we bind two functions to the GameMonkey machine, CreateNPC() which is tied into the game's entity manager and NPCGoto() which accepts an entity and a location as a parameter. In this simple example, the location is either TOWER_ONE or TOWER_TWO in the global Location table. In a real game you'd most likely specify a vector, or would bookmark real locations with names like TOWER_ONE. The first thing we do in the script is create the guard NPC and start a thread for it to run in.

  global GuardMove = function() {
    this:stateSet(GuardGotoTowerOne);
  };

  npc = CreateNPC();
  npc:thread(GuardMove);

The guard's behaviour function immediately jumps into the state that tells it to go to the first tower.

  global GuardGotoTowerOne = function() {

    print("Going to Tower One...");

    res = NPCGoto(this, Location.TOWER_ONE);

    if (res == Event.SUCCESS)
    {
      print("Arrived at tower One");
      this:stateSet(GuardWait);
    }
    else
    {
      print("Couldn't get there");
    }
  };

This script function calls the NPCGoto() function and passes TOWER_ONE as a location for this entity to travel to. The native code behind this function fires off a message to the game system that indicates we want to move the entity to the specified location.

  GM_CHECK_INT_PARAM(a_entity, 0);
  GM_CHECK_INT_PARAM(a_location, 1);

  // Push message to Game Message queue
  NPCGotoMessage *msg = new NPCGotoMessage( a_entity, a_thread->GetId(), a_location );

  s_Game.GetMessageSystem().PushMessage( msg );

The bound function then immediately sets up a block on the scripted thread that suspends it until we've received the success or failure result back from the game.

  gmVariable blocks[] = { gmVariable(SM_SUCCESS), gmVariable(SM_FAILED) };

  int res = a_thread->GetMachine()->Sys_Block( a_thread, 2, blocks );

  if (res == -1)
  {
    return GM_SYS_BLOCK;
  }

The game continues as normal, the entity manager picks up the NPCGotoMessage we posted and processes the movement of the entity each frame. In this example we're using a time based trigger that fires after a few seconds, but in a real game you would move an entity's co-ordinates in the world.

  if (_Dur >= 2500.0f) // Simulate time passing
  {

    NPCArrivalMessage *msg = new NPCArrivalMessage(ent._Id, ent._ScriptThreadId, ent._Destination);

    s_Game.GetMessageSystem().PushMessage(msg);
    ent._State = Entity::ES_WAITING; // Go back to waiting state

    _Dur = 0;
  }

The script manager system is set up to listen for an NPCArrivalMessage; when it recieves one it will post the result of the movement request back on the entity's thread in the form of a signal.

if (a_msg->MessageType == MT_GOTO_SUCCESS)
{
  NPCArrivalMessage *msg = reinterpret_cast<NPCArrivalMessage*>(a_msg);

  // Fire signal on thread
  _Machine.Signal( gmVariable(SM_SUCCESS), msg->ScriptThreadId, 0 );
}

The GameMonkey Script machine processes the signal on the next update and the thread resumes operation, in this case the script is written to change state to the waiting state and then progress from there to TOWER_TWO.

As you can appreciate, this set up allows you to write scripts for entities as if they are synchronous and not worry about the complex message handling systems your engine may implement. By treating the NPCGoto function as a normal function call and hiding the blocking behaviour, you enable your script writers to write simple logic and not worry about anything that may happen internally in your game.

Best Practices

Now that you've explored GameMonkey Script in detail you will be aware that is a highly flexible language; like many other programming languages there are a series of best-practices to follow for both scripting and embedding the language in your games.

Avoid inline script

Although some of the examples here have scripts embedded within the C++ code, this should be avoided in a production environment. The single biggest benefit of a scripted language is that it helps avoid recompiles of your game engine should game logic need changing -- by inlining your script you will lose out on this benefit. It is advised that you load your scripts at runtime using your own load routines; you should also bind a function similar to Lua's dofile function that allows you to load and execute scripts from files at runtime. A non-portable example is available via the gmSystemLib binding that ships with GM.

Minimise Globals

Like many other programming languages, the use of global variables and functions should be kept to a minimum. Although convenient, a game will feature tens, hundreds, if not thousands of scripted functions to create its behaviours. When you rely entirely on globals you are forced to use warts to prefix each function name to allow you to differentiate between behaviours for different entity types. If specific behaviours and functions are to be shared across multiple entity types, it is worth considering, but for many cases it is not necessary. The best solution is to bind specific functions to your entity type libraries and to investigate some of the extensibility methods I discussed earlier to allow you to attach functions to a hidden table on the entity. This way, all the functions will implicitly be called with this as your entity type and you can code in a very object-oriented manner.

You may then choose to 'wrap' the base entity creation routines in specialised versions that attach functions and properties automatically. For example:

  global createGuardNPC() = function(name) {

    ent = createEntity();
    ent.name = name;
    ent.type = "GuardNPC";
    ent.stateWander = function () { ... };
    return ent;
  }

Your users can then create specialised entities that come pre-made with behaviours and you have a central place for the definition.

Separate Logic across Files

Like many scripting languages, GameMonkey doesn't have a default IDE -- therefore you are responsible for organising and tracking the files involved in your project. Your game will likely feature the behaviours of multiple entity types in GameMonkey script so for ease of navigation throughout your project and debugging any issues it is advised that you store the logic of each entity in separate files.

For example if you have "GuardNPC" and "MonsterNPC" as separate entities, it is recommended that you store all functions, data and code in a file for each and load them together using a master file.

  global createGuardNPC() = function(name) {
    ent = createEntity();
    ent.name = name;
    ent.type = "GuardNPC";
    ent.stateWander = function () { ... };
    return ent;
  };

NPC_Guard.gm

  global createMonsterNPC() = function(name) {
    ent = createEntity();
    ent.name = name;
    ent.type = "Monster";
    ent.stateWander = function () { ... };
    return ent;
  };

NPC_Monster.gm

  Game:LoadScript("NPC_Guard.gm");
  Game:LoadScript("NPC_Monster.gm");

NPCs.gm

This tip is linked heavily to the notion that you minimise globals; by defining and creating your entities in one file you can change scripts very quickly by accessing a single location that is well-named rather than hunting for specific references throughout a file.

Record Memory Use

The GameMonkey virtual machine records the memory use of all internal objects and uses this information to determine when to run the garbage collector or allocate new memory; it is also useful for profiling to see how much memory is being consumed by your scripting system. It is a good practice to use the gmMachine::AdjustKnownMemoryUse method to indicate how much memory you're using or freeing every time you allocate or release an object.

Use the Log for Errors

Whenever an error occurs in script callback, be it a parameter fault or another problem you should always try and write a meaningful error message to the log -- accessible by the gmMachine::GetLog() method. If your game is hooked into the output handlers of the GameMonkey Script machine, you can then feed these errors back to your users, possibly on a debug script console or otherwise. You could also choose to record the error in your game's main error log for later analysis or problems or issues.

Hook into the Machine

The GameMonkey virtual machine has a global machine callback method for several internal events. This is achieved by setting the function gmMachine::s_machineCallback which then lets you hook the following commands:

Machine Callback Command Description
MC_COLLECT_GARBAGE When a Garbage Collection cycle begins
MC_THREAD_EXCEPTION Whenever a thread causes an exception.
MC_THREAD_CREATE Called whenever a thread is created on the machine
MC_THREAD_DESTROY Called whenever a thread is about to be destroyed in the machine

The most useful command callback to most people is the MC_THREAD_EXCEPTION command as it allows you to handle any error cleanly in your game and potentially reset or terminate the entity that caused the exception. The gmPrintCallback allows you to hook into the in-built print() function and redirect any output to your script debug windows. There are several other hooks into the machine that are intended for debug purposes, all of which allow you to greater insight into what's happening in the script engine to allow you to minimise bugs and problems during development of your game.

Understand Garbage Collection

As discussed in a previous article, the GameMonkey Script garbage collector is an incremental mark & sweep collector; it will perform a little work on each execution cycle of the machine to check for objects that no longer have any references to the root of the machine. When binding and exposing your own objects to the machine they will become liable for garbage collection if you've set your callbacks up correctly. There will be certain occasions in which you don't want an object to be garbage collected, for example when compiling a local function to be tied to any new entities or if you want to expose a single C++ owned object as multiple variables in the machine. For cases like this you can call gmMachine::AddCPPOwnedGMObject() which tells the garbage collector to persist the object, effectively ignoring it in its cycle. To remove an object from this state, you call gmMachine:: RemoveCPPOwnedGMObject(). There is a useful binding in the src/binds directory of the main distribution called gmGCRoot which allows you to wrap these objects in a smart pointer, protecting them from garbage collection whilst there are still outstanding references to them in your native code.

Fast String Lookups

In the examples used in this article, I have used string comparisons to test which properties are being referred to in the GetDot/SetDot lookups. GameMonkey Script allows you to allocate a permanent string in the machine that isn't susceptible to garbage collection. As GameMonkey is optimised to reuse strings where possible it is possible to compare such strings by their gmStringObject* pointer alone. To do this, you would call gmMachine::AllocatePermanantStringObject and retain the gmStringObject* that is returned. In your operator callback code (or otherwise), you can now use this to compare any values you receive back from the machine. If the pointers are equal, you can guarantee that the string is the same. Look at ext_02.cpp for an example of how the extended entity code has been modified to handle this optimisation.

Tracking Threads

It is desirable to track the creation of threads in the machine, particularly those that are created with your user type as this. By doing so, you can ensure that any pending threads are cleaned up when an object that 'owns' them is destroyed. For example, if you have a thread that is running to control the opening of a door and playing the relevant animation it makes very little sense having this logic around if the door is destroyed or becomes permanently impassable. By tracking threads that are associated with the door, you can then kill them all once the door is destroyed. The same would apply to game entities which could have many different threads running for them to control a variety of complex behaviours. An ideal way of controlling threads within the machine is to make their use implicit via your own API. The signals_02.cpp example demonstrates how a thread is created and run for a user object without the script writer having to specify it. By simplifying the thread creation in this way, you can track their creation as you are controlling it directly.

Summary

This article has demonstrated how to use some of the more advanced features of GameMonkey Script, including the powerful threading functionality that is the key to providing complex scripted behaviours in your games.

Acknowledgements

I would like to extend my gratitude to Greg Douglas for his help in verifying the technical information in this article and to Jeremy Swigart for his expert-user guidance and real-world examples provided by the Omni-Bot project.

References

GameMonkey Script Homepage - http://www.somedude.net/gamemonkey/ GameMonkect Script Forums - http://www.somedude.net/gamemonkey/forum/ Omni-Bot - AI bots for multiple FPS games (powered by GameMonkey Script) -http://www.omni-bot.de/