Event/Delegate system in GameMonkey

Published January 02, 2007
Advertisement
I was bored today, so I coded this example.



Event systems are useful in games as you can fire a single event and have many objects respond to it, regardless of their type. For the same reason, they're also useful for GUIs and other such systems.

This example shows you how to set up a simple event handling system from within GameMonkey script using no additional bindings.

In this example system, event handlers are registered to an event 'type'. A handler is merely a function which takes an event 'object' as a parameter and is called when an event is fired through the system. An event is again, just a GM table with some common attributes. Because it's a table, you can (and should) add your own additional properties to it for your handlers to consume.

/////////////////////////////////// eventsystem// ------------------// Namespace table that contains the event system//global eventsystem = table(    /////////////////////    // 'private' members, don't access these outside of the api    m_listeners = table(),    m_next_id = 0,    m_next_listener = 0,    m_typelisteners = table        (            m_next = 0,            m_listeners = table()        ),            ///////////// m_dispatchevents    // The main workhorse; iterates all handlers and fires the event    // passing the object as 'this' if one is present    // returns: integer to indicate the number of handlers that processed the event    m_dispatchevents = function( a_evt )    {        local c = 0;        t = .m_typelisteners[ a_evt.type ];        if (t == null)        {            return 0;        }        foreach( l in t )        {            obj = l[0];            func = l[1];            if (obj != null)            {                obj:func( a_evt );            }            else            {                func( a_evt );            }            c = c+1;        }        return c;    },    ///////////////////////    // delete_listener    // ---------------    // Removes an event listener from the system    //    delete_listener = function( a_type, a_id )    {        t = .m_typelisteners[ a_type ];        if (t != null)        {            t[ a_id ] = null;        }    },    ///////////////////////    // add_listener    // ---------------    // Add an event listener to the system    // Params:    //  a_type - The type of event (eg: "update", "destroy" )    //  a_func - A function to handle the event when called    //  a_obj  - Object to pass as 'this' when the event is fired (optional)    //  Returns:    //    id - Unique Id for that listener and event type    //    add_listener = function( a_type, a_func, a_obj )    {        t = .m_typelisteners[ a_type ];        if (t == null)        {            t = table();            .m_typelisteners[ a_type ] = t;        }        id = .m_typelisteners.m_next;        t[ id ] = { a_obj, a_func };        .m_typelisteners.m_next = id + 1;        return id;    },    ///////////////////////    // fire_event    // ---------------    // Sends an event to all subscribers    // Params:    //  a_evt - Event object to fire    //    fire_event = function( a_evt ) { return .m_dispatchevents( a_evt ); },        ///////////////////////    // create_event    // ---------------    // Creates an event object and allocates a unique id    // Params:    //  a_type - The type of event (eg: "update", "destroy" )    create_event = function( a_type )    {        evt = table();        evt.type = a_type;        evt.id = .m_next_id;        .m_next_id = .m_next_id+1;        return evt;    });



Here's some helper functions for using the system in a game-like way. In the demo script we create an 'entity' which is just a simple GM table object with some named attributes. There are a couple of helper functions which set up the entity; these aren't part of the core event system, but they're pretty useful to have around if you want a fully-integrated system. The helper functions will create a method on the entity corresponding to the name of the event - so if the event was "damage", the entity would then contain a function called "on_damage". This isn't needed, but it can be useful if you need to fire the event handler individually. When an entity 'dies', you must remove its handlers from the system (remove_entity_events) otherwise the GC will keep the entity alive.

The make_entity function takes a table as a second parameter, which in turn contains pairs of data (event type and the function).


/////////////////////////////////// add_entity_event// ------------------// Helper function to add an event handler to an entity//global add_entity_event = function( a_entity, a_eventtype, a_eventfunc ){    // register listener    id = eventsystem.add_listener( a_eventtype, a_eventfunc, a_entity );    // create a named function on the entity    a_entity[ "on_" + a_eventtype ] = a_eventfunc;    // store event hook info on the entity    a_entity.event_hooks[ a_eventtype ] = { id, a_eventtype, a_eventfunc };};/////////////////////////////////// remove_entity_event// ------------------// Helper function to remove an event handler from an entity//global remove_entity_event = function( a_entity, a_eventtype ){    id = a_entity.event_hooks[ a_eventtype ][0];    // register listener    eventsystem.delete_listener( a_eventtype, id );    // remove named function from the entity    a_entity[ "on_" + a_eventtype ] = null;    // remove event hook info from the entity    a_entity.event_hooks[ a_eventtype ] = null;};/////////////////////////////////// remove_entity_events// ------------------// Helper function to remove all event handlers from an entity//global remove_entity_events = function( a_entity ){    foreach ( e in a_entity.event_hooks )    {        remove_entity_event( a_entity, e[1] );    }};/////////////////////////////////// make_ent// ------------------// Helper function to make an event-enabled entity//global make_entity = function( a_name, a_event_table ){    t = table();    t.name = a_name;    t.event_hooks = table();    // register events    foreach ( e in a_event_table )    {        add_entity_event( t, e[0], e[1] );    }    return t;};



And finally, the demo code. I have created a single generic function to show the event that was fired. What you would do here is to create your own specific functions to handle damage events, update events or whatever you want to throw at it.

/////////////////////////////////// generic_event_func// ------------------// A simple function that shows the event being executed and who is handling it//global generic_event_func = function( a_evt ){    print( .name + " handled event id:", a_evt.id, "(" + a_evt.type + ")" );};// Create two test entities//// bob - Handles the "damage" and "update" events// steve - handles update and "recharge" eventsprint( "Creating entity 'bob'" );global bob = make_entity( "bob",     {        { "damage", generic_event_func },        { "update", generic_event_func },    });print( "Creating entity 'steve'" );global steve = make_entity( "steve",    {        { "recharge", generic_event_func },        { "update", generic_event_func },    });// create a damage eventevt = eventsystem.create_event( "damage" );print( "Firing a 'damage' event" );eventsystem.fire_event( evt );evt = eventsystem.create_event( "update" );print( "Firing an 'update' event" );eventsystem.fire_event( evt );evt = eventsystem.create_event( "recharge" );print( "Firing a 'recharge' event" );eventsystem.fire_event( evt );print( "Removing 'bob' from the system" );remove_entity_events( bob );evt = eventsystem.create_event( "update" );print( "Firing an 'update' event" );eventsystem.fire_event( evt );


And there you have it. Any comments or suggestions are welcome.
Previous Entry Linux
Next Entry gmBind2
0 likes 2 comments

Comments

LachlanL
Very nice work. I don't use GameMonkey (I'm most likely going to go with Lua in my current project), but it's still good to see an example of how these things work.
January 02, 2007 06:59 PM
Trapper Zoid
Nice - I'll read through this in a bit more detail later as I'm still fine tuning my own event system, although I'm using C++.

Do you find there's a benefit in using strings as the event type identifiers? At the moment I'm using a enumerated type in C++, but I've been considering other methods for a rework of my messaging/event system.

Plus I'm wondering whether it's better to call my system an "event system" or a "message system". I keep toggling between the two, and it's a pain to rename everything once I have a change of heart [smile].
January 02, 2007 08:59 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement