1

Ok so I have created a system that listens and handles events as shown below. I created an Event struct and several more specific structs to hold event data. I also created a typedef for a Listener which takes in event data. An internal event handler receives the events and runs all of the user defined listeners in the appropriate vector for that event. However the specific event data is sliced off since the Event class only contains the EventType. Is there any way around this so that I do not have to rewrite almost all of my code. I have tried passing the event by reference but that didn't solve it. Here is the code so far...

EDIT: I am aware that I can use a static_cast to use the other data in the event however since I am trying to create a simple API for developers I would like to not force the user to static_cast in every listener.

Structure

struct Event {
    EventType type;
};
struct MouseEvent : Event {
    int x;
    int y;
    bool down;
    uint8_t which;
};
typedef function<void(MYCLASS&, Event&)> Listener;
typedef vector<Listener*> Listeners;

Handler

void MYCLASS::eventHandler(Event& event, Listeners* listeners) {
  for(auto it = listeners->begin(); it != listeners->end(); ++it) {
    (**it)(*this,event);
  }
}

Example user defined listener

    instance.addListener(MOUSEMOVE, [](MYCLASS& a, Event& e) {
        cout << e.x << endl; //THIS LINE DOES NOT WORK
    });
Owen Kuhn
  • 85
  • 6
  • 3
    What you have already is the correct solution, without using a separate listener type for each event type. The user will just have to deal with typecasting the `Event*` pointer when needed. However, assuming the `EventType` can be used to determine the type of descendant being passed around, the user can and should use `static_cast` instead of `dynamic_cast` to avoid overhead of performing an RTTI lookup on each event, since `static_cast` is evaluated at compile-time. Also, your `eventHandler()` needs to do better filtering to call only the listeners that are interested in the current `Event*` – Remy Lebeau Aug 17 '19 at 02:44
  • 1
    You have a general design fault! No listener will register for all events or more precise, no listener function is able to handle all events. And it makes simply no sense. Why the "coffee ready" listener should get a mouse event. If you want to stay on your broken design, you can technically downcast with RTTI or use std::variant. But all of them is the wrong way to fix a general design problem! – Klaus Aug 17 '19 at 08:46
  • Possible duplicate of [What is object slicing?](https://stackoverflow.com/questions/274626/what-is-object-slicing) – L. F. Aug 17 '19 at 17:24
  • @L.F. Thank you for finding the name of my issue I edited my question. Do you know of any way to avoid the slicing? – Owen Kuhn Aug 18 '19 at 01:45
  • Is it possible to replace the problematic line with `static_cast( &e )->x` ? – javaLover Aug 18 '19 at 02:04
  • @javaLover Yes that works I used it earlier before I edited the question, I don't know why I removed that section however the purpose of my application is to provide a simple API for developers and I would much prefer if they didn't have to cast to the correct event type in every listener they create. – Owen Kuhn Aug 18 '19 at 02:11
  • If so, here is what I am doing in my game library : User should treat many types individually e.g. `instance.addMouseListener(...); instance.addKeyboardListener(...);` Java's JPanel also solves it like this. It might look uncool, but work very well for me. – javaLover Aug 18 '19 at 02:17
  • @javaLover Thank you very much this is what I will do – Owen Kuhn Aug 18 '19 at 03:42
  • Your problem isn't slicing, because you are using a reference and that supports polymorphism. The problem is covariance in callback parameters. – Ben Voigt Aug 18 '19 at 04:48
  • @Ben Voigt I 100% agree about that. The topic name "How do I avoid Object Slicing in C++" is currently misleading. "covariance in callback parameters" is a better one. – javaLover Aug 19 '19 at 10:33

1 Answers1

1

Encouraged by positive feedback, I post my answer here.

The main idea is to treat each callback type individually e.g. mouse and keyboard.

instance.addMouseListener([](MYCLASS& a, MouseEvent& e) {
    cout << e.x << endl;
});
instance.addKeyboardListener([](MYCLASS& a, KeyboardEvent& e) {
    cout << e.button << endl; 
});

It is a bit untidy, but it works fine (at least for my game).
Moreover, I believe this is a standard way to solve.
(see: addMouseListener for a JPanel , SDL Mouse Click )

There is another way that I prefer though. It may or may not suitable for you.
I create an instance of mouse/keyboard, and treat them like global variables.
Then user can query it like :-

//in game loop : e.g. System_UpdateRocket.h
MousePtr* mouse=getMouseGlobal();
KeyboardPtr* keyboard=getKeyboardGlobal();
if(mouse->getMouseMovementDelta().x < 0 &&        
  keyboard->justPressThisFrame(KEYBOARD::SPACE_BUTTON)){
     //do something e.g. myRocket->pewPew();
}

In this approach, there needs to be a library-level central system that updates every global-like instance (like MousePtr*) in every time-step.

javaLover
  • 6,347
  • 2
  • 22
  • 67