#pragma once #include #include #include #include #include #include #include #include #if __cplusplus < 201103L #error This library needs at least a C++11 compliant compiler #endif namespace Dexode { class EventBus { public: EventBus() = default; ~EventBus() { _callbacks.clear(); } EventBus(const EventBus&) = delete; EventBus(EventBus&&) = delete; EventBus& operator=(EventBus&&) = delete; EventBus& operator=(const EventBus&) = delete; /** * Register listener for event. Returns token used for unlisten. * * @tparam Event - type you want to listen for * @param callback - your callback to handle event * @return token used for unlisten */ template int listen(const std::function& callback) { const int token = ++_tokener; listen(token, callback); return token; } /** * @tparam Event - type you want to listen for * @param token - unique token for identification receiver. Simply pass token from @see EventBus::listen * @param callback - your callback to handle event */ template void listen(const int token, const std::function& callback) { using Vector = VectorImpl; assert(callback && "callback should be valid");//Check for valid object std::unique_ptr& vector = _callbacks[getTypeId()]; if (vector == nullptr) { vector.reset(new Vector{}); } assert(dynamic_cast(vector.get())); Vector* vectorImpl = static_cast(vector.get()); vectorImpl->add(token, callback); } /** * @param token - token from EventBus::listen */ void unlistenAll(const int token) { for (auto& element : _callbacks) { element.second->remove(token); } } /** * @tparam Event - type you want to unlisten. @see Notiier::listen * @param token - token from EventBus::listen */ template void unlisten(const int token) { auto found = _callbacks.find(getTypeId()); if (found != _callbacks.end()) { found->second->remove(token); } } /** * Notify all listeners for event * * @param event your event struct */ template void notify(const Event& event) { using Vector = VectorImpl; const auto typeId = getTypeId();//TODO think about constexpr auto found = _callbacks.find(typeId); if (found == _callbacks.end()) { return;// no such notifications } std::unique_ptr& vector = found->second; assert(dynamic_cast(vector.get())); Vector* vectorImpl = static_cast(vector.get()); vectorImpl->beginTransaction(); for (const auto& element : vectorImpl->container) { element.second(event); } vectorImpl->commitTransaction(); } private: struct VectorInterface { virtual ~VectorInterface() = default; virtual void remove(const int token) = 0; }; template struct VectorImpl : public VectorInterface { using CallbackType = std::function; using ContainerElement = std::pair; using ContainerType = std::vector; ContainerType container; ContainerType toAdd; std::vector toRemove; bool inTransaction = false; virtual void remove(const int token) override { if (inTransaction) { toRemove.push_back(token); return; } //Invalidation rules: https://stackoverflow.com/questions/6438086/iterator-invalidation-rules auto removeFrom = std::remove_if(container.begin(), container.end() , [token](const ContainerElement& element) { return element.first == token; }); if (removeFrom != container.end()) { container.erase(removeFrom, container.end()); } } void add(const int token, const CallbackType& callback) { if (inTransaction) { toAdd.emplace_back(token, callback); } else { container.emplace_back(token, callback); } } void beginTransaction() { inTransaction = true; } void commitTransaction() { inTransaction = false; if (toAdd.empty() == false) { container.insert(container.end(), toAdd.begin(), toAdd.end()); toAdd.clear(); } if (toRemove.empty() == false) { for (auto token : toRemove) { remove(token); } toRemove.clear(); } } }; int _tokener = 0; std::map> _callbacks; template static const std::size_t getTypeId() { //std::hash{}(typeid(T).name() is slower return typeid(T).hash_code(); } }; } /* namespace Dexode */