My question was the first, the performance hit on having a bunch of extra threads sitting around in a listen loop waiting to be tossed a event, rather than just directly implementing normal function calls to efficiently move from event to event. Not only that, but the amount of code being written - it seems like a lot of work for very little extra functionality, not to mention extremely error prone (esp. for catching events "out of order"), though I could see the sense of it in large realtime games with many events happening simultaneously.
I don't have threads sat on listen loops for events. I use multithreading in my game, but so far only for very specific purposes (in my case, fluid rendering, which there's another thread about). The event system is completely inert until a part of the code decides to put together a packet of data and send an event, at which point everything that's registered as a listener has that data packet passed to a callback. As such, the coding required to create an entirely new type of event is pretty lightweight, just:
- Defining somewhere what the event is called, and what sort of data packet it sends (one line of code for basic data types)
- Having some part of the code send an event when it needs to (one line)
- Have another part of the code register as a listener (one line), store a handle to that registration, use the handle to de-register (one line), and provide a callback.
The "out of order" issue is a slightly bigger one in that depending on what sends the event, and when, callbacks can be called at any point during the game update loop. Generally this isn't an issue with sufficiently carefuly programming, but for the more complex cases my system also has an ability to "queue" events on a channel, which only get sent out when the channel is explicitly "flushed", so you can have much finer control over when that happens.
As for when I use the event system vs. when I just have stuff talk directly to each other, it's generally a case of common sense. Given that my primary reason for having the event system is to reduce dependencies as much as possible (to increase maintainability), I'll tend to use it to bridge the gap between parts of the code which I feel should be loosely-coupled anyway - so, the physics system, the rendering system, and the core gameplay code should be kept pretty seperate. Some systems, audio for instance, are entirely driven by events they receive, whereas some (input and some of the higher-level timing stuff) pretty much don't do anything except send events. But for parts of the code which are more tightly-coupled, say the animation system and the sprite manager, I tend to just have those work directly together. Similarly, I'm much more likely to use the events system when I don't know or care how many listeners there are, but where there could be anything between zero and many varied things that are interested than I am when I know that only one specific part of the game will need that information.
Furthermore, to use your g for grenade key example, it must be kind of treacherous to design context-sensitive interfaces(like mouse selection) - I imagine you have to broadcast a second flag to all the listeners to tell them who the event is for, based on a state.
My example wasn't quite how I actually do things with input events, although it might be closer to that in future if I find it's better for me to do it that way. Currently my input system sends events for "key down", "key up", and "key is down this frame" along with an integer identifying which key the event is talking about. There are also events for "mouse button down", "mouse button up" and "mouse move" which send a struct containing the screen position of the mouse and (optionally) the button in question. With something like mouse selection, rather than have every game entity or HUD item have a callback to test seperately if they got clicked on a "mouse button down" (because running all those individual tests could get time-consuming if done that way), I'd have the Entity Manager be the listener, work out which entity got selected, and then act appropriately by storing the handle of the currently-selected entity, or by calling some method in that one entity to say "you've been clicked".