Event Id
An event has two parts
- Type (or Name, or Id)
- Arguments
“EventId” like this? What are the pros and cons?
Yes! Only use scoped enums.
struct EventArg{};
enum class EventId {
LEVEL_STARTED,
BOMB_EXPLODED,
PLAYER_DIED,
};
struct Event {
const EventId id;
static const uint8_t MAX_ARGS = 8;
uint8_t nbArgs;
EventArg args[MAX_ARGS];
explicit Event(EventId _id) : id{_id} {}
}
Event e(EventId::PLAYER_DIED);
if(e.id == EventId::PLAYER_DIED) {
}
Achievement from Team Fortress 2
Not very portable, right? Ask for alternatives. Let’s see what they come up with.
Challenges: What about 100 kills. What about 20 kills with a flamethrower, etc...
Achievement from world of warcraft
You need to keep track of the height when you jumped, you compare it with the height when you land, you cannot die of it + it needs to be on a hard surface...
This decouples code. The Physics engine can issue an event that an Entity fell, but whether or not there should be an achievement for that, or lives should be subtracted, or a level should be restarted is totally not up to the Physics engine.
Note: the observer interface does not have to be written like this, the Notify method can have whatever parameters you want and you can have multiple Notify methods too. Often you have an event and eventargs.
Keep in mind: the parameters of the Notify function don’t have to be the ones as we present here. That’s why it is a pattern – adapt it to your needs.
In GC languages, if you remove an observer, you’ll need some Destroy() method that unregisters them from the subject, you don’t have a destructor to do that. = lapsed listener problem
Check the book for extra examples, even something in there with a ring buffer which is good for memory and data locality.
2nd bullet: By the time an event gets processed, it might not be necessary anymore, how do you handle that?
3rd bullet: If an eventhandler invokes an event that triggers the same handler you’re stuck. In a single threaded environment that’s fine, you’ll get a stackoverflow, but in a multithreaded context you’ll be keeping your CPU’s busy.
5th bullet: Pass ownership: caller to queue to receiver, or share ownership (with shared ptr) or the queue owns.
enums/ints are easy to compare, but list needs to be known in advance
no scoped enums :)
Downside is that this needs to be calculated at runtime.
The last method I've added myself: In C++, string literals like "QuitEvent" are of type const char[N], where N includes the null terminator ('\0'). When a function template takes an array by reference (e.g., const char (&text)[N]), the compiler deduces the size of the array (N) automatically.