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.