Polymorphism
Event system
Section titled “Event system”- Implement a simple event system. The interface should be as follows:
-
An
EventSystemstructure that stores a list of event handlers for each event type. -
An
EventHandlerstructure that stores a handler identifier (a number) and the handler function itself. -
Define the handler function type
EventHandlerFuncwithusing. It can be one of the following; choose whichever you prefer:-
A function pointer, but in that case be aware that you will not be able to preserve context with it.
-
std::function. In my opinion, this is the simplest yet still flexible option. -
EventHandlerAbstractBase*, whereEventHandlerAbstractBaseis an abstract base class that declares the virtual functionhandleEvent(EventInfo* event). This will let you preserve context in derived classes. In this case, do not forget to delete the handler when unsubscribing.class EventHandlerAbstractBase{public:virtual void handleEvent(EventInfo* event) = 0;};class MyHandler : public HandlerAbstractBase{MyContext* context;public:void handleEvent(EventInfo* event) override{// ...}};
-
-
An
EventHandlestructure that stores an event identifier (a number). -
An
EventInfostructure that will be passed to event handlers. You may decide exactly what data to pass and how:-
You can use type erasure by storing a
void*pointing to additional data and casting it to the required type in the handlers.struct EventInfo{void* context;// ...};// ...void handleEvent(EventInfo* event){auto context = static_cast<MyContext*>(event->context);// ...} -
You can simply use some fixed, generic data, but then you will probably not be able to pass more specific context when handling the event.
struct EventInfo{int someData1;int someData2;};void handleEvent(EventInfo* event){std::cout << event->someData1 << std::endl;} -
You can add a
templateparameter to this structure for the payload type. This is the most complex option, which will also require you to add templates toEventSystemitself, but it is also the most flexible.template<typename Payload>struct EventInfo{Payload payload;// ... some more generic info};template<typename Payload>void dispatchEvent(EventSystem& eventSystem,size_t eventId,EventInfo<Payload>* event){std::span<EventHandler<Payload>> handlers = getEventHandlersById<Payload>(eventSystem, eventId);for (const auto& handler : handlers){handler.func(event);}}
-
-
A function
EventHandle createEvent(EventSystem& eventSystem)orEventHandle<T> createEvent(EventSystem& eventSystem).It returns an event identifier that can be used to subscribe handlers later. Each subsequent identifier should be 1 greater than the previous one.
To simplify debugging, you can store the event name in
EventSystem. You can also use anstd::unordered_mapwith user-provided strings as event identifiers. -
A function
EventHandlerHandle subscribe(EventSystem& eventSystem, EventHandle eventId, EventHandlerFunc&& func);, which adds a handler to the specified event. It returns anEventHandlerHandlestructure containing a handler identifier, which can be used for unsubscribing. Each subsequent identifier should be 1 greater than the previous one. -
A function
bool unsubscribe(EventSystem& eventSystem, EventHandle eventId, EventHandlerHandle handle);, which removes the handler with the specified identifier from the specified event (you may assume that handlers cannot unsubscribe other handlers). It returnsfalseif the handler was not found. -
A function
void dispatch(EventSystem& eventSystem, EventHandle eventId, EventInfo* event);, which calls all handlers for the event with the specified identifier.
-
Create a menu that demonstrates its use. The menu must have the following options:
-
Create a new event, initially without subscriptions. If you chose the
templateoption, the user must be able to select aPayloadtype from known types (at least 2). -
Subscribe one of the predefined handlers to a specific event. The user must be able to choose one of several functions defined beforehand in the program (at least 6). If a function expects additional context, let the user enter it before binding the function (for example, the function “add N to the number in the payload” expects a number N).
-
Unsubscribe a handler by providing the event and handler identifiers.
-
Print the identifiers of all events and their handlers.
-
Invoke an event by providing its identifier and, if needed, data for the handlers.
-
-
Possible ideas for events and handlers:
-
An event for applying filters when selecting a color. Handlers that combine, override, or transform the color passed in the event context. Finally, print the selected color after processing.
-
A mathematical pipeline for numbers. Handlers that add to, multiply, or take the square root of the number passed in the event context.
-
Events in the context of a game. You can imagine that one event is invoked when a player presses a button. Its effect could be a monetary bonus awarded to the player at random, a dynamite explosion and the player’s death, an increase in score, etc.
Any ideas are welcome.
Do not use global variables!