1
\$\begingroup\$

I have written a very simple algorithm vizualizer where all input is coming from the mouse and I needed an event manager for that. Since I never wrote one before, I wrote a 30 min implementation to act as a placeholder. Now my question is, whether or not I get the idea of an event manager right, does it look the right way? if not can some of you give me some tipps or links to tutorials.

Mouse event:

#ifndef B9E46EEC_476A_469D_9500_CFB8E28280E2
#define B9E46EEC_476A_469D_9500_CFB8E28280E2

#include <SFML/Window/Event.hpp>

namespace Pathfinding::Events
{
    struct MouseEvent
    {
        MouseEvent(bool eventOnly_, sf::Event::EventType event_, sf::Mouse::Button button_)
            : eventOnly(eventOnly_), event(event_), button(button_) {}
            
        bool eventOnly = false;
        sf::Event::EventType event;
        sf::Mouse::Button button;
    };
}

#endif /* B9E46EEC_476A_469D_9500_CFB8E28280E2 */

Mouse data:

#ifndef EF091C2C_702A_4E72_882C_6AB418436A17
#define EF091C2C_702A_4E72_882C_6AB418436A17

#include <SFML\System\Vector2.hpp>

namespace Pathfinding::Events
{
    struct MouseData
    {
        MouseData(sf::Vector2i cursorPosition_) : cursorPosition(cursorPosition_) {}
        
        MouseData(sf::Vector2i cursorPosition_, int32_t wheelDelta_) 
        
        : cursorPosition(cursorPosition_), wheelDelta(wheelDelta_) {}
        
        sf::Vector2i cursorPosition;
        int32_t wheelDelta;
    };
}

#endif /* EF091C2C_702A_4E72_882C_6AB418436A17 */

Event manager headaer

#ifndef D9DC23B1_9143_4917_B212_390EBA2EE1DF
#define D9DC23B1_9143_4917_B212_390EBA2EE1DF

#include <functional>
#include <deque>
#include <SFML/Window/Event.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <vector>

#include "MouseEvent.hpp"
#include "IEventManager.hpp"
#include "MouseData.hpp"

namespace Pathfinding::Events
{
    class EventManager final : public Pathfinding::Abstract::IEventManager
    {
    public:
        EventManager() = default;
        
        explicit EventManager(sf::RenderWindow *window);
        
        void addBinding(MouseEvent event, std::function<void(MouseData)> callbackFunc) override;
        
        void pushEvent(sf::Event event) override;
        
        void processEvents() override;
        

    private:
        sf::RenderWindow *windowPtr;
        std::deque<sf::Event> eventQueue;
        std::vector<std::pair<MouseEvent, std::function<void(MouseData)>>> callBacks;
    };
}

#endif /* D9DC23B1_9143_4917_B212_390EBA2EE1DF */

Event manager implementation:

#include "EventManager.hpp"

#include <SFML/Window/Event.hpp>

#include "MouseData.hpp"

namespace Pathfinding::Events
{
    EventManager::EventManager(sf::RenderWindow *window)
        : windowPtr(window)
    {
    }

    void EventManager::addBinding(MouseEvent event, std::function<void(MouseData)> callback)
    {
        callBacks.push_back({event, callback});
    }

    void EventManager::pushEvent(sf::Event event)
    {
        if (event.type == sf::Event::EventType::Closed)
        {
            windowPtr->close();
        }
        eventQueue.push_back(event);
    }

    void EventManager::processEvents()
    {
        while (!eventQueue.empty())
        {
            sf::Event &currentEvent = eventQueue[0];
            sf::Vector2i mousePos = sf::Mouse::getPosition(*windowPtr);
            for (auto &callBack : callBacks)
            {
                if (currentEvent.type == callBack.first.event)
                {
                    if (callBack.first.eventOnly || currentEvent.key.code == callBack.first.button)
                    {
                        callBack.second(MouseData(mousePos, currentEvent.mouseWheel.delta));
                    }
                }
            }
            eventQueue.pop_front();
        }
    }
}

Use case:

auto eventManagerUPtr = std::make_unique<EventManager>(&applicationUPtr->window);
eventManagerUPtr->addBinding({EVENT_AND_KEY, MouseButtonPressed, sf::Mouse::Left}, 
std::bind(&IGraphOperations::leftMouseButtonPressed, applicationUPtr->graphOpsUPtr.get(), _1));

eventManagerUPtr->addBinding({EVENT_AND_KEY, MouseButtonPressed, sf::Mouse::Right}, 
std::bind(&IGraphOperations::rightMouseButtonPressed, applicationUPtr->graphOpsUPtr.get(), _1));

eventManagerUPtr->addBinding({EVENT_ONLY, MouseButtonReleased, NO_MOUSE_BUTTON}, 
std::bind(&IGraphOperations::mouseButtonReleased, applicationUPtr->graphOpsUPtr.get(), _1));

eventManagerUPtr->addBinding({EVENT_ONLY, MouseMoved, NO_MOUSE_BUTTON}, 
std::bind(&IGraphOperations::mouseMoved, applicationUPtr->graphOpsUPtr.get(), _1));

eventManagerUPtr->addBinding({EVENT_ONLY, MouseMoved, NO_MOUSE_BUTTON}, 
std::bind(&IGraphOperations::nodeUnderCursor, applicationUPtr->graphOpsUPtr.get(), _1));

eventManagerUPtr->addBinding({EVENT_ONLY, MouseWheelMoved, NO_MOUSE_BUTTON}, 
std::bind(&IGraphOperations::mouseWheelMoved, applicationUPtr->graphOpsUPtr.get(), _1));

...
while (window.isOpen())
{
    sf::Event event;
    while (window.pollEvent(event))
    {
        eventManagerUPtr->pushEvent(event);
    }
    eventManagerUPtr->processEvents();
    draw();
}
...

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
   MouseEvent(bool eventOnly_, sf::Event::EventType event_, sf::Mouse::Button button_)
       : eventOnly(eventOnly_), event(event_), button(button_) {}
      

...

   MouseData(sf::Vector2i cursorPosition_) : cursorPosition(cursorPosition_) {}
   
   MouseData(sf::Vector2i cursorPosition_, int32_t wheelDelta_) 
   : cursorPosition(cursorPosition_), wheelDelta(wheelDelta_) {}

We don't need constructors like this in modern C++, as we can use aggregate initialization to initialize structs (including designators for specific members in C++20).


   EventManager() = default;

Is this necessary? An EventManager created like this could never be used (due to the invalid windowPtr).


void EventManager::pushEvent(sf::Event event)
{
    if (event.type == sf::Event::EventType::Closed)
    {
        windowPtr->close();
    }
    eventQueue.push_back(event);
}

Checking for the Closed event and closing the window here is a bit unexpected. It might be better to do it in the processEvents() loop instead.


We probably shouldn't store the callbacks / bindings in the EventManager class itself. At the moment, if we want to use a different set of bindings, we'd have to recreate the EventManager. If we have a separate class, we could pass the bindings into the processEvents() function:

struct EventCallbacks
{
    std::vector<std::pair<MouseEvent, std::function<void(MouseData)>>> mouseCallbacks;
    // ... keyboard, etc...
};

class EventManager
{
public:
    ...

    void processEvents(EventCallbacks const& callbacks);
};

That way, we can easily switch out the callbacks if we want to move to a different app state / input mode.


   while (window.isOpen())
   {
       sf::Event event;
       while (window.pollEvent(event))
       {
           eventManagerUPtr->pushEvent(event);
       }
       eventManagerUPtr->processEvents();
       draw();
   }

Right now there doesn't seem to be a reason to have a second queue to push events to before processing them, so we could just process events directly (either pass individual events into processEvents, or do the polling in processEvents).

\$\endgroup\$
3
  • \$\begingroup\$ thank you for your feedback, about your last point, what would be a good reason for a queue? I read somewhere that in case of a game loop, we need to separate the polling of events from the update function, thats why I poll first and then update. \$\endgroup\$
    – a a
    Mar 27, 2022 at 9:06
  • \$\begingroup\$ Forgot to mention, this while loop is not how my actual loop looks like. \$\endgroup\$
    – a a
    Mar 27, 2022 at 9:16
  • 1
    \$\begingroup\$ In a native Android game (just as an example), we need the UI thread to be as responsive as possible, so we can't do anything that takes a lot of time on the UI thread. So when we get an input event, we simply push it onto a separate queue to process on a different thread later. I don't actually know how SFML works internally, but I expect it maintains its own queue. When we call window.pollEvent(), it will grab an event off the internal queue for us to process. So there's already some separation there, and we can consider the "input polling" from SFML to simply be part of the "update" code. \$\endgroup\$
    – user673679
    Mar 27, 2022 at 9:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.