-
Notifications
You must be signed in to change notification settings - Fork 90
Event handling 3.0 (with steroids) #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
… template metaprogramming
f6bee51
to
31946f3
Compare
31946f3
to
154eed8
Compare
Considering your comfort with template metaprogramming, have you considered using CRTP instead of / in addition to lambda-based handling? It allows compile-time resolution of the 'most derived' definition of methods and functions, without the overhead of dynamic polymorphism. The resulting syntax would be very similar, except the lambda would be replaced by a method override, and composite handlers could be formed via mixins. The CRTP interfaces are effectively promises of an implementation of specific event handling methods, satisfied by the derived class or at least one of its ancestors, with a compile time error resulting from a missing implementation. (It's easy enough to mix in a default implementation in later derivations.) |
My main focus was removing the need for inheritance so user can just write their classes without the need to override methods or the like. About the overhead of EDIT: I've been thinking about the overhead
|
bump? |
Sorry, my work is going well but I'm being extra-cautious. I can easily show the general difference between the performance of calls with For now, if developers have concern about performance, but need an inheritance model, they can use the non- |
@AMDmi3, ping? |
So after a lot of work I managed to finish the implementation of an idea I had when I started working on this event handler logic. This is basically a generalization of the
PollEvent()
andPollAllEvents()
functions I had previously published.How does it work?
Definitions
First let us define what an event handler is, it can come in two flavors:
object(event)
where event is of any valid event type. This event handler definition includes free functions, lambdas and classes withoperator()
overloaded.object.HandleEvent(event)
, no inheritance is needed. A single object can handle more than one event type.Both flavors must return void.
The valid event types are defined by the data fields of SDL_Event rather than the SDL_EventType enum. The actual
SDL_Type
is also considered a valid event type and if an event handler accepts this type then all the events will be passed to it.Usage
The user only has to define a list of event handlers and pass it to one of the two event polling functions defined in EventPolling.hh, ex:
In the previous example user defines three event handlers:
This was my objective, the usage is extremely simple!
The guts
This makes heavy use of template metaprogramming, so the language might be a little odd. I will be describing the logic in a bottom-up fashion:
Three utility metaprogramming operations (these can be found in the Private/Utility.hh file):
Or<...>
Or operation for template metaprogramming, expands tostd::true_type
if any of the values passed to the template are true, otherwise expands tostd::false_type
.And<...>
And operation for template metaprogramming, expands tostd::true_type
if all of the values passed to the template are true, otherwise expands tostd::false_type
.TupleHasType<T, std::tuple<...>>
Expands tostd::true_type
ifT
is a value defined in the given tuple, otherwise expands tostd::false_type
.ValidEventTypes
is defined as a tuple of valid event types in Private/EventHandler.hhThree different "template functions" to identify if a type can handle an event (also defined in Private/EventHandler.hh):
IsEventHandlerFunctor<EventHandlerType, EventType>
expands tostd::true_type
ifEventHandlerType
adheres to the definition of an event handler functor described above of the givenEventType
, otherwise expands tostd::false_type
. The same is true for any event handler object withIsEventHandlerObject<EventHandlerType, EventType>
.IsEventHandler<EventHandlerType, EventType>
expands to the equivalent of (using the previous definitions):(IsEventHandlerFunctor<EventHandlerType, EventType> OR IsEventHandlerObject<EventHandlerType, EventType>) AND TupleHasType<EventType, ValidEventTypes>
EventTypeFilter
is defined (in Private/EventTypeFilters.hh) to map an instance ofSDL_Event
to any of the types defined inValidEventTypes
. Basically this "template function" can detect if a given instance ofSDL_Event
is of a givenEventType
and retrieve theEventType
value from it. This must be defined for all the types present inValidEventTypes
.The function
DispatchEventHandlerFunctor(const EventType&, EventHandlerType&&)
has two definitions (in Private/EventDispatching.hh), one to apply the givenEventType
to the givenEventHandlerType
in case the later is an event handler functor forEventType
and other that is no-op. The same is true for any event handler object withDispatchEventHandlerObject(const EventType&, EventHandlerType&&)
.The
EventDispatcher<ValidEventHandler, EventHandlerType, EventTypes...>
"template function" is a class defined (in EventDispatching.hh) to dispatch the givenSDL_Event
to the given instance ofEventHandlerType
(using bothDispatchEventHandlerFunctor
andDispatchEventHandlerObject
) for each event type inEventTypes
.ValidEventHandler
is a helper value to identify if an valid/invalid event handler has been passed (in other words ifIsEventHandler<EventHandlerType, EventType>
), the final expansion ofEventDispatcher
will do a static assert on that value to signal an error if an invalid event handler was passed.The function
DispatchEvent(const SDL_Event &, EventHandlerTypes&&...)
is defined to call theEventDispatcher
"template function" with each of the given event handlers and each of the event types defined inValidEventTypes
.Finally
PollEvent(EventHandlerTypes&&...)
use the standard SDLSDL_PollEvent()
to retrieve an event and if any event is retrieved it is dispatched to all event handlers usingDispatchEvent
.PollAllEvents(EventHandlerTypes&&...)
makes use ofPollEvent(EventHandlerTypes&&...)
to do the same until there is no events left to poll. Both defined in EventPolling.hh.Again, this makes heavy usage of template metaprogramming, but a rough translation to C++-like code would be:
Concerns
PollEvent
andPollAllEvents
. Hence I created a Private folder and placed such code in the SDL2pp::Private namespace, if you think it should be moved/renamed please let me know.operator()
andHandleEvent()
functions for the same type, I'm not sure what's more intuitive. Rise an error in case both are defined and mention this is ambiguous, execute both or just execute one. Currently the code executes both one after another.EventType
,const EventType
andconst EventType &
. If the argument is defined asEventType &
then it will be marked as an invalid event handler. If a custom type define both it will silently ignore the invalid one, this is conceptually and technically correct but I think it may cause some headaches:HandleKeyDown
) but this would require an enormous amount of code given the amount of possible events and on top of that how would know what exact event the user wants to handle for functors (ex:void foo(SDL_KeyboardEvent)
wants to handle key down, key up or both events?). I think the current approach is a healthy compromise between readability and code behind the scenes.