🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Various ideas on event based systems in AngelScript

Started by
5 comments, last by kaysik 18 years, 2 months ago
Scripting systems can run a lot faster using event based architectures. I've thought of a few ways to implement this in an engine using AngelScript, and was wondering if anyone else had others. I would think effeciency should be the highest priority, and ease of use would be the second. This may also help people starting off with Angelscript. Method 1: My current engine does not generate many events, so I went with a simple, though seemingly slow way to generate events. When an event occurs in my engine, it constructs a function name by concatenating the name of the object with the name of the event, searches for it in angelscript. If it finds it, it then executes the script. Example: The engine has a door object called "WoodenDoor17." This door starts opening and the generic door code in my engine wants to raise a "OnDoorBeginOpen" function. It then calls a HandleEvent function in my script system. This function's prototype looks like: HandleEvent(basegameobject* object, const char* event_name, const char* parameters). The actual call in this case would look like: HandleEvent(object, "OnDoorBeginOpen", "()"). Note that the calling function must generate parameters on its own (in this case it's an empty list). The HandleEvent() function would then concat the string names, something like this: object.name + "_" + event_name + parameters. If it can find the function by name, it runs it with the given parameters. The as code might look like this:

void WoodenDoor17_OnDoorBeginOpen()
{
    // Do something
}

This method is slow, as it has to construct and search for strings for each event. However, it is somewhat handy as the user never needs to register event handlers. It also does not allow for overloading event handlers (but I don't find this necessary in most applications). Method 2: The second method involves the user calling some sort of "RegisterHandler" function to register each event handler. The handler can be called anything, as long as the parameter types match the engine. Each object would have a member storing an AS function ID for each handler (or -1 by default). At init time, these members would be set by the "RegisterHandler" calls. When an event occurs, the function could be called by index, though the parameters would still have to be constructed manually. This seems like it would be a lot faster than method 1 for systems that have many events occuring each frame. It would also give scripters the freedom to name handlers whatever they want. However, it would require the scripter to manually register each function. It also still lacks easy parameter construction (any ideas on how to streamline this?). Method 3: This method is a combination of 1 and 2, and seems to get the best of both worlds. If I need speed on my current engine or if I was going to write a new engine, I'd probably use this method (unless someone suggests something better :) ). Event handlers follow the same naming convention mentioned in method 1. Each object also stores a function ID for an event handler for each event, as in method 2. At build time, each object that exists in the engine searches the script for a handler for each of its possible events. For example, "WoodenDoor17" would like for a "WoodenDoor17_OnDoorBeginOpen" function for its "OnDoorBeginOpen" event. If it finds it, it stores the ID (otherwise -1). This is obviously slow, but no one ever said init time had to be fast. This method seems both fast and does not require for the user to manually register event handlers. It still has the downside of runtime parameter construction, though I doubt this can be avoided much. Let me know if anyone else has any other ideas or improvement.
Advertisement
I use something like method 2 and it works great for me. The advantage of method 2 is that you can bind multiple objects to the one scripting function even if their totally different. Say and door and a medkit both want the same onDestroy() function? If you auto generate the function names then your forced to have individual functions for each and every entity (type) which could mean alot of cut+pasting which would a nightmare to maintain!

For my stuff I have an xml file which can hold function names for each event (If none is listed then no script will be called). Then at load time I bind each event to its listed script function. I haven't tried it with many functions (ie 20 is about my limit), but even so with AS it still takes less than a second to build then create all the contexts and set it all up.

Doing it this way with specified event handlers does take more work initially because you have to manually list which events use what functions but in the end I think its worth it. Auto generated function names would require much less setup, but would bug me later when i couldn't use the same functions for different things!
2 is still going to be the most flexible and powerfull way. In your 3rd example, only WoodenDoor17 is ever going to be able to use that function. I actually just define these things in the world data file, as a scriptname : function name pair.
i also go all the time with the second one. it's fairly well optimized and really good to maintain, and in this way u only need to get the id for the event handler functions only once after the script gets compiled, then you can forget about getting ids for functions and methods. anyway i'm in a testing case that is more likely on application (not game) writing, and so i have scripts that create and register real application components, those call back on script implemented event handlers, executing the default c++ behaviour when the handlers are not present (in script). in this way i can create objects and their event handlers in the script, and have the c++ framework in background converting virtual method calls for every component type to the real script implementation of them.

         script                 application           |    [create component]           |                      v   [define behaviours]--> [create real component]                          [register script object owner with the component]                          [register implemented methods id with the component]                                  |                                  v                          [run message loop] --> [dispatch event to component]                                                              |          |---------------[get the method id to execute] <-----          v   [execute behaviour]


i actually use an array that maps every virtual method id that i should execute in the script, i do the fill of the map only once. the only slowdown could be if events handler are called frequently, you got some overhead in calling context->Prepare, but actually for short executing scripts isn't a real problem.
kunitoki, that is essentially the same method I use.
Oops, yeah I forgot that many/most implementations need handlers that will handle all types of a certain event. I'm used to events handling a specific instance in prescripted sequences (e.g., when door17 opens, have npc13 walk through it). In addition, method 1 and 3 don't let you wire multiple handlers to a single event. If I were still going to use method 3, I'd modify it to act as follows:

Events can also be registed for entire types. In the door example, "Door_OnDoorBeginOpen" or perhaps just "OnDoorBeginOpen" would run for any object of type Door. This could easily work with inheritance. For example, if Door is a subclass of Object, a moving door could trigger "Object_OnMove," "Door_OnMove," and "WoodenDoor17_OnMove." This would enable the scripter to handle events at the level they see fit. If they want something to react to any door moving, they could handle this in the second method. If they only want to handle it for door17, they could do this as well. Of course, if each event has a parameter for the sender, it could look like:

void OnMove(object@ sender){   if (@sender == @WoodenDoor17)   {     // Handle   }  if (sender.type == Door)  {     print("Is RTTI indicative of bad design?");  }}
Your still left with a situation where its impossible for a door and a player to share the same script functions because they have no common super classes (or if they do then it'll be so high up the chain that it will force every single entity type to that event rather than just the two you want). Personally I think you still need some way to manually specify which function to use. You could have it use your class name system if no special cases are given and it'd work quite well, but somewhere in there you need a way to link any function you like.

This topic is closed to new replies.

Advertisement