diff --git a/include/engine/core/engine_bus.hpp b/include/engine/core/engine_bus.hpp new file mode 100644 index 0000000..2818789 --- /dev/null +++ b/include/engine/core/engine_bus.hpp @@ -0,0 +1,73 @@ +#ifndef __MSC_ENGINE_BUS_HPP__ +#define __MSC_ENGINE_BUS_HPP__ + +#include +#include +#include +#include +#include +#include + +/// @brief Event base class +class MscEvent +{ +public: + virtual ~MscEvent() = default; + + /// @brief Get event name + virtual std::string getName() const = 0; + + /// @brief Is current event is canceled + virtual bool isCanceled() const = 0; + + /// @brief Cancel current event + virtual void cancel() = 0; +}; + +/// @brief Engine event bus +class MscEventBus +{ +public: + /// @brief Event handler alias + using Listener = std::function; + + /// @brief Subscription identifier alias + using SubscriptionId = std::uint64_t; + + /// @brief Emit event + /// @param event event context + void publish(MscEvent &event); + + /// @brief Subscribe to specified event + /// @param name event name + /// @param listener event handler + /// @returns subscription identifier to make it possitble to remove listener + SubscriptionId subscribe(const std::string &name, Listener listener); + + /// @brief Subscribe to specified event and execute once on publish + /// @param name event name + /// @param listener event handler + void subscribeOnce(const std::string &name, Listener listener); + + /// @brief Unsubscribe listener by its own id + /// @param id subscription id + /// @return `true` if listener has been deleted, otherwise `false` + bool unsubscribe(SubscriptionId id); + +private: + using ListenerWithId = struct + { + SubscriptionId id; + Listener listener; + bool once; + }; + + std::unordered_map> _listeners; + std::unordered_map _listenersPaths; + std::uint64_t _lastId = 0; + std::mutex _listenersMutex; + + SubscriptionId _subscribe(const std::string &name, Listener listener, bool once); +}; + +#endif // __MSC_ENGINE_BUS_HPP__ diff --git a/src/engine/core/event_bus.cpp b/src/engine/core/event_bus.cpp new file mode 100644 index 0000000..7d6420a --- /dev/null +++ b/src/engine/core/event_bus.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +void MscEventBus::publish(MscEvent &event) +{ + std::vector listeners; + + { + std::lock_guard lock(_listenersMutex); + auto it = _listeners.find(event.getName()); + if (it == _listeners.end()) + return; + + for (const auto &entry : it->second) + listeners.push_back(entry); + } + + for (const auto &entry : listeners) + { + if (event.isCanceled()) + break; + + entry.listener(event); + if (entry.once) + unsubscribe(entry.id); + } +} + +MscEventBus::SubscriptionId MscEventBus::_subscribe(const std::string &name, MscEventBus::Listener listener, bool once) +{ + std::lock_guard lock(_listenersMutex); + + const SubscriptionId id = ++_lastId; + auto listenerWithId = ListenerWithId{ + .id = id, + .listener = std::move(listener), + .once = once}; + + _listeners[name].push_back(std::move(listenerWithId)); + _listenersPaths[id] = name; + + return id; +} + +MscEventBus::SubscriptionId MscEventBus::subscribe(const std::string &name, Listener listener) +{ + return _subscribe(name, std::move(listener), false); +} + +void MscEventBus::subscribeOnce(const std::string &name, Listener listener) +{ + _subscribe(name, std::move(listener), true); +} + +bool MscEventBus::unsubscribe(SubscriptionId id) +{ + std::lock_guard lock(_listenersMutex); + + auto it = _listenersPaths.find(id); + if (it == _listenersPaths.end()) + return false; + + auto &listeners = _listeners.at(it->second); + for (std::size_t i = 0; i < listeners.size(); i++) + { + if (listeners.at(i).id != id) + continue; + + listeners.erase(listeners.begin() + i); + _listenersPaths.erase(id); + + if (listeners.empty()) + _listeners.erase(it->second); + + return true; + } + + return false; +} \ No newline at end of file