1
\$\begingroup\$

Below is code for handling events in SFML. It works by reading a text file containing a sequence of functions to be called when a given event is detected:

EventManager.hpp

#pragma once

#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <vector>
#include <unordered_map>
#include <functional>
#include <fstream>
#include <sstream>
#include <iostream>

namespace AGE
{
    enum class EventType
    {
        KeyDown       = sf::Event::KeyPressed,
        KeyUp         = sf::Event::KeyReleased,
        MButtonDown   = sf::Event::MouseButtonPressed,
        MButtonUp     = sf::Event::MouseButtonReleased,
        MouseWheel    = sf::Event::MouseWheelMoved,
        WindowResized = sf::Event::Resized,
        GainedFocus   = sf::Event::GainedFocus,
        LostFocus     = sf::Event::LostFocus,
        MouseEntered  = sf::Event::MouseEntered,
        MouseLeft     = sf::Event::MouseLeft,
        Closed        = sf::Event::Closed,
        TextEntered   = sf::Event::TextEntered,
        Keyboard      = sf::Event::Count + 1, Mouse, Joystick
    };



    struct EventInfo
    {
        EventInfo()
        {
            this->code = 0;
        }

        EventInfo(int code)
        {
            this->code = code;
        }

        int code;
    };

    using Events = std::vector<std::pair<EventType, EventInfo>>;

    struct EventDetails
    {
        EventDetails(const std::string& binding_name)
        {
            this->binding_name = binding_name;
            Clear();
        }

        void Clear()
        {
            this->size              = { 0, 0 };
            this->text_entered      = { 0 };
            this->mouse             = { 0, 0 };
            this->mouse_wheel_delta = { 0 };
            this->key_code          = { -1 };
        }

        std::string binding_name;

        sf::Vector2i size;
        sf::Uint32 text_entered;
        sf::Vector2i mouse;
        int mouse_wheel_delta;
        int key_code;
    };



    struct Binding
    {
        Binding(const std::string& name) :
        details(name),
        name(name),
        event_count(0)
        {}

        void BindEvent(EventType type, EventInfo info = EventInfo())
        {
            this->events.emplace_back(type, info);
        }

        EventDetails details;
        Events       events;
        std::string  name;
        int          event_count;
    };

    using Bindings = std::unordered_map<std::string, Binding*>;
    using Callbacks = std::unordered_map<std::string, std::function<void(EventDetails*)>>;

    class EventManager
    {
    public:
         EventManager();
        ~EventManager();

        bool AddBinding(Binding* binding);
        bool RemoveBinding(const std::string& name);

        void HandleEvent(sf::Event& event);
        void Update();

        void SetFocus(const bool focus);

        template <class T>
        bool AddCallback(const std::string& name,
                         void(T::*function)(EventDetails*), 
                         T* class_instance)
        {
            auto func = std::bind(function, class_instance, std::placeholders::_1);
            return this->callbacks.emplace(name, func).second;
        }

        void RemoveCallback(const std::string& name)
        {
            this->callbacks.erase(name);
        }


    private:
        void LoadBindings();

    private:
        Callbacks callbacks;
        Bindings  bindings;
        bool has_focus;
    };
}

EventManager.cpp

#include "EventManager.hpp"

namespace AGE
{
    EventManager::EventManager() :
    has_focus(true)
    {
        LoadBindings();
    }



    EventManager::~EventManager()
    {
        for (auto& itr : this->bindings)
        {
            delete itr.second;
            itr.second = nullptr;
        }
    }



    bool EventManager::AddBinding(Binding* binding)
    {
        if (this->bindings.find(binding->name) != this->bindings.end())
        {
            return false;
        }

        return this->bindings.emplace(binding->name, binding).second;
    }



    bool EventManager::RemoveBinding(const std::string& name)
    {
        auto itr = this->bindings.find(name);
        if (itr != this->bindings.end())
        {
            delete itr->second;
            this->bindings.erase(itr);
            return true;
        }

        return false;
    }



    void EventManager::HandleEvent(sf::Event& event)
    {
        for (auto& b_itr : this->bindings)
        {
            Binding* binding = b_itr.second;

            for (auto& e_itr : binding->events)
            {
                EventType event_type = static_cast<EventType>(event.type);

                if (e_itr.first != event_type) { continue; };

                if ((event_type == EventType::KeyDown) || (event_type == EventType::KeyUp))
                {
                    if (e_itr.second.code == event.key.code)
                    {
                        ++(binding->event_count);
                        break;
                    }
                }
                else if ((event_type == EventType::MButtonDown) || (event_type == EventType::MButtonUp))
                {
                    if (e_itr.second.code == event.mouseButton.button)
                    {
                        binding->details.mouse.x = event.mouseButton.x;
                        binding->details.mouse.y = event.mouseButton.y;
                        ++(binding->event_count);
                        break;
                    }
                }
                else
                {
                    if (event_type == EventType::MouseWheel)
                    {
                        binding->details.mouse_wheel_delta = event.mouseWheel.delta;
                    }
                    else if (event_type == EventType::WindowResized)
                    {
                        binding->details.size.x = event.size.width;
                        binding->details.size.y = event.size.height;
                    }
                    else if (event_type == EventType::TextEntered)
                    {
                        binding->details.text_entered = event.text.unicode;
                    }
                    ++(binding->event_count);
                }
            }
        }
    }



    void EventManager::Update()
    {
        if (!this->has_focus) { return; };

        for (auto& b_itr : this->bindings)
        {
            Binding* binding = b_itr.second;

            for (auto& e_itr : binding->events)
            {
                switch (e_itr.first)
                {
                default:
                    break;

                case EventType::Keyboard:
                    if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key(e_itr.second.code)))
                    {
                        ++(binding->event_count);
                    }
                    break;

                case EventType::Mouse:
                    if (sf::Mouse::isButtonPressed(sf::Mouse::Button(e_itr.second.code)))
                    {
                        ++(binding->event_count);
                    }
                    break;
                }

                if (binding->events.size() == binding->event_count)
                {
                    auto callback_itr = this->callbacks.find(binding->name);
                    if (callback_itr != this->callbacks.end())
                    {
                        callback_itr->second(&binding->details);
                    }
                }
                binding->event_count = 0;
                binding->details.Clear();
            }
        }
    }



    void EventManager::SetFocus(const bool focus)
    {
        this->has_focus = focus;
    }



    void EventManager::LoadBindings()
    {
        const std::string deliminator = ":";
        std::ifstream     bindings;
        bindings.open("C:/dev/MyVSProjects/C++/Racer/Racer/Racer/src/Keys.cfg");

        if (!bindings.is_open())
        {
            std::cerr << "Failed to open key file!" << std::endl;
            return;
        }

        std::string line;
        while (std::getline(bindings, line))
        {
            std::stringstream key_stream(line);
            std::string callback_name;
            key_stream >> callback_name;
            Binding* binding = new Binding(callback_name);

            while (!key_stream.eof())
            {
                std::string key_value;
                key_stream >> key_value;

                int start = 0;
                int end = key_value.find(deliminator);
                if (end == std::string::npos)
                {
                    delete binding;
                    binding = nullptr;
                    break;
                }
                EventType type = EventType(std::stoi(key_value.substr(start, end - start)));
                int code = std::stoi(key_value.substr(end + deliminator.length(), key_value.find(deliminator, end + deliminator.length())));
                EventInfo event_info;
                event_info.code = code;
                binding->BindEvent(type, event_info);
            }

            if (!AddBinding(binding)) { delete binding; };
        }

        bindings.close();
    }
}

Short example: (Keys.cfg then tells the code to call function "cout" when keybutton 89 (f5) is pressed)

Keys.cfg

COUT 5:89

Main.cpp

#include <SFML/Graphics.hpp>

#include "EventManager.hpp"

#include <iostream>

class Test
{
public:
    Test() = default;

    void COUT(AGE::EventDetails* details)
    {
        std::cout << "Test" << std::endl;
    }
};

int main()
{
    Test test;

    sf::RenderWindow window(sf::VideoMode(800, 600), "Test");
    AGE::EventManager event_manager;
    event_manager.AddCallback("COUT", &Test::COUT, &test);

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::LostFocus)
            {
                event_manager.SetFocus(false);
            }
            else if (event.type == sf::Event::GainedFocus)
            {
                event_manager.SetFocus(true);
            }
            event_manager.HandleEvent(event);
        }
        event_manager.Update();

        window.clear();
        window.display();
    }
}

I would like any tips to improve the code in any way possible. Looking to learn!

\$\endgroup\$
1
\$\begingroup\$

Data modeling wise:

  • The name EventInfo is ambiguous, especially when there is another class called EventDetails. So I am thinking about how to make their purposes more differentiable. It seems like the purpose of EventInfo and EventType is to create a composite key, which is used in EventManager to fork business logic. I would recommend creating a structure EventClassifier which has both event type and event code as member variables. Binding can have an unordered set of EventClassifier.

Coding style-wise:

  • prefer to use initialization list rather than assignment operator in the constructor. For instance:
EventInfo(int a_code)
:  code(a_code)
{}
  • It is redundant to write this->size in function void Clear(). It can just be size.
  • you can use structured bindings when iterating thru a map: for (const auto& [k,v] : bindg->events). the code will be more readable as you don't need to write things like iter->second.

Some other things:

  • in EventManager destructor, the code should check if(itr.second) before freeing the memory.
  • EventManager::AddBinding takes a raw pointer of Binding. This is unsafe because 1) EventManager will free the memory in RemoveBinding. so it assumed the address is on heap however there is no guarantee. 2) Caller of EventManager::AddBinding has access to the binding object and could have freed the memory. Then when EventManager tries to access it, the program will segfault. I would recommend changing the interface of AddBinding(Binding* binding) to AddBinding(const std::string& callback_name, EventType type, int event_code). Then EventManager has the ownership of binding objects exclusively.
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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