(In)efficiency of Event Handlers in a flexible game

Started by
5 comments, last by Flyverse 8 years, 11 months ago

Hey guys!

As I said in this topic, I currently have an idea for an exremely modular, flexible game. The concept looks like this:

The game itself is not doing much. It has all the "basic stuff", but my focus would be on the "plugin/modding" system of the game: I want to be able to create some kinds of add-ons which basically don't do anything else than hooking into different parts of the games events - Thus requiring an event handler.

Since the game is a platformer, each entity-type, each platform, etcetera would be an add-on; But the question here is, can the performance keep up?

Let's say my event handler & game looks like this:

EventHandler {

registerEvent(type, callback)

callEvent(type, other arguments) -> loop through registered events of type "type" and execute the callback

}

Game {

While initiating: EventHandler.callEvent("init");

When entities are colliding: EventHandler.callEvent("collision", entity1, entity2);

etc

}

Example Addon {

Hook_Init(){do something}

etc

}

Not only Entities and Platforms will be addons though, but also systems like ParticleSystems and stuff. Thing is: Do I get much overhead in such an event handler system? I'd really like to do it, but if it can't keep up performance-wise, it would just be wasted time.

I hope I could make myself clear (My english isn't very good)

Thanks in advance,

Flyverse

Advertisement

There will be some overhead. That's about all anyone can say.

It entirely depends on where you want to use such flexible 'runtime' interfaces.

If you get issues regarding performance, just identify the problem area and get rid of the interface overhead there by promoting it to be a part of the core engine, or refactor the interface to do more work with less calls. Since the amount of low level calls tends to be much higher than high level calls, you probably wont notice a slowdown even if your event system took 100x longer to make a call compared to normal function calls.

So should be clear that using an interface like that (eg an indirection or two to call a function instead of a direct compile time optimized call) is not going to be that bad if the implementation is good (compare to virtual function calls).

Just avoid using 'stringly typed' code:

eg Instead of


while(true)
    eventHandler.callEvent("init");

do something like


int init=eventHandler.getEventId("init");
while(true)
    eventHandler.callEvent(init);

So if you use string identifiers, they would only be used at initialization and in user readable configuration files, and resolved to optimal indices/handles/ids before use.

I could see that being a problem if you make heavy use of the system.

Just dont exclusively expose your math library through the event system.

o3o

Instead of a centralized event object, you can do something like this:


class Application
{
	Run()
	{
		InitializeSomeStuff();
		CreateWindow();
		
		TriggerEvent( this->onStartup );
		
		StartTheLoop();
	}
	
	// public variables (events)
	
	Event< bool, void> onStartup;
	Event< bool, int reason > onQuit;
	// ... etc
};


class SoundEngine
{
	void PlaySound( string sname );
	void PlayMusic( string sname );
	
	Event< void, Sound& > onStartPlaying;
	Event< void, Sound& > onFinishedPlaying;
	// ... etc
};

This way, an event is a variable, and you don't have to use enumerators or strings to identify events, and the different modules remain completely independent from one another. You can add callbacks to an event (free functions or member variables). But as you mentioned in your previous thread, you have to be careful not to go overboard with this by making an event variable for every possible event that you can imagine another module may be interested in. Also, for callbacks that are object methods, you have to make sure that the object "overlasts" the event, or properly remove the callback when the object dies.

You can find such an implementation here: http://code-section.com/entry/2/c-a-simple-event-system

The performance of this will probably not be an issue since the callbacks are typically stored in a list, and triggering an event is a matter of iterating over the list elements.


As I said in this topic, I currently have an idea for an exremely modular, flexible game. ... my focus would be on the "plugin/modding" system of the game ... Since the game is a platformer, each entity-type, each platform, etcetera would be an add-on; But the question here is, can the performance keep up?

No. No no no no.

First things first. Your game "as a whole" might be the add-on. In the sense you might have no game-specific logic in (C++, or your lang of choice) code. Your game is not an assembly of add-ons. It is a cohesive blend.

Now that we got out the ideological part, let's get to your question. Will perf keep up?

Yes.

Be explorative, don't worry too much! Your priority is getting the job done!

More involved answer is: callbacks by themselves won't kill your perf. What it matters is how often they are called and how much they cause you to deviate from the "optimal" path.

In your example, you want to check if "entity1" collides with "entity2".

As far as this is what goes, you might just have the physics processed as usual and then iterate on all resulting contact points to extract the set involving entity1, then checking if it contains entity2. This is doable for a small amount of checks. As the interesting entities grow, it's probably better to go on a "sensors" approach - they are physics rigid bodies which automatically keep track of their contact points so you can just check their internally collected list. Bullet calls them "ghost objects" if memory serves.

In general, if the check to call is trivial (an n-m test like above is not, albeit it might be viable) the cost of the callback depends on how much often it gets called. As long as it's event-based, I've found this to be very viable.

Keep in mind there could be ways to emulate what you need while still being hi-perf. Here's a real world example on a game I used to work a couple of years ago.

I had those things. We'll call them "rollys". Rollys just roll around. BUT they don't fall in the water, turning back to avoid falling into the void/water. There can be 1-8 rollys each "lair" and there are usually 1-4 lairs.

The initial solution involved a per-frame, per-rolly callback. It involved sweeps and was fairly complicated. Performance was viable albeit far from perfect on the lowest-end devices (Atom).

Solution 2: rollys were updated to include a special "hit descriptor" and the level was annotated with special "rolly only" colliders. Now rollys can just bound off the special colliders - they are the same to them. They still behave the same, but this solution requires way less tests.

Long story short: before going into scripting, ensure you have a decent data-driven design. While you might want to keep game-specific code out of your application (or not) there are features you're going to need and those should be just built-in.

Previously "Krohm"

the performance hit of a callback vs a regular subroutine call should only be one de-reference to get the callback address.

the performance hit of a callback vs an in-lined subroutine or regular inline code should be one de-reference to get the callback address, a gosub / ret instruction pair to make the actual call, and any call setup overhead such as setting up stack frame pointers and pushing parameters on the stack, or copying parameters to registers. this will depend on calling convention, with fastcall (register passing, no stack frame) being the fastest.

the only difference between a callback and a regular subroutine call is that a callback (usually) gets called from code you didn't write, and the single de-reference to get the address of your callback subroutine.

I use procedural variables (basically callbacks) in Caveman 3.0 for action handlers (think: The SIMs - make and eat dinner action handler code), no problem.

to keep them fast, you want to code them like interrupt handlers. do only what you must, as quickly as possible, then pass on control flow to the next routine in the chain.

from the overhead of callback vs inline code as described above, you can see the advantage of moving performance critical common callback code into the engine itself, as mentioned in previous replies.

use fastcall when possible, and inline any appropriate. use ID numbers, they are faster than strings, as mentioned in a previous reply.

since its all about plug-ins you want to make it as data driven as possible - also mentioned in a previous reply.

following the advise given in the replies, there's no reason why its shouldn't work just fine. only the callbacks you call a billion times will need to be rolled into the engine.

to see callback based code in action, check out the directx multi-animation sample. its ALL callbacks! the whole darn program! punch up 100+ skinned meshes on a dual core laptop? no problemo!

.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Thanks for the replies!

I guess I'll just try to implement it, then.

(By the way, if you have nothing else to do feel free to correct my english :D)

This topic is closed to new replies.

Advertisement