/******************************************************************************** * Copyright (C) 2014-2017 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH * * * * This software is distributed under the terms of the * * GNU Lesser General Public Licence (LGPL) version 3, * * copied verbatim in the file "LICENSE" * ********************************************************************************/ #ifndef FAIR_MQ_EVENTMANAGER_H #define FAIR_MQ_EVENTMANAGER_H #include #include #include #include #include #include #include #include #include #include #include #include namespace fair { namespace mq { // Inherit from this base event type to create custom event types template struct Event { using KeyType = const K; }; /** * @class EventManager EventManager.h * @brief Manages event callbacks from different subscribers * * The event manager stores a set of callbacks and associates them with * events depending on the callback signature. The first callback * argument must be of a special key type determined by the event type. * * Callbacks can be subscribed/unsubscribed based on a subscriber id, * the event type, and the callback signature. * * Events can be emitted based on event type and callback signature. * * The event manager is thread-safe. */ class EventManager { public: template using Callback = std::function; template using Signal = boost::signals2::signal; template auto Subscribe(const std::string& subscriber, Callback callback) -> void { const std::type_index event_type_index{typeid(E)}; const std::type_index callback_type_index{typeid(Callback)}; const auto signalsKey = std::make_pair(event_type_index, callback_type_index); const auto connectionsKey = std::make_pair(subscriber, signalsKey); const auto connection = GetSignal(signalsKey)->connect(callback); { std::lock_guard lock{fMutex}; if (fConnections.find(connectionsKey) != fConnections.end()) { fConnections.at(connectionsKey).disconnect(); fConnections.erase(connectionsKey); } fConnections.insert({connectionsKey, connection}); } } template auto Unsubscribe(const std::string& subscriber) -> void { const std::type_index event_type_index{typeid(E)}; const std::type_index callback_type_index{typeid(Callback)}; const auto signalsKey = std::make_pair(event_type_index, callback_type_index); const auto connectionsKey = std::make_pair(subscriber, signalsKey); std::lock_guard lock{fMutex}; fConnections.at(connectionsKey).disconnect(); fConnections.erase(connectionsKey); } template auto Emit(typename E::KeyType& key, Args&&... args) const -> void { const std::type_index event_type_index{typeid(E)}; const std::type_index callback_type_index{typeid(Callback)}; const auto signalsKey = std::make_pair(event_type_index, callback_type_index); (*GetSignal(signalsKey))(key, std::forward(args)...); } private: using SignalsKey = std::pair; // event , callback using SignalsValue = boost::any; using SignalsMap = std::unordered_map>; mutable SignalsMap fSignals; using ConnectionsKey = std::pair; // subscriber , event/callback using ConnectionsValue = boost::signals2::connection; using ConnectionsMap = std::unordered_map>; ConnectionsMap fConnections; mutable std::mutex fMutex; template auto GetSignal(const SignalsKey& key) const -> std::shared_ptr> { std::lock_guard lock{fMutex}; if (fSignals.find(key) == fSignals.end()) { // wrapper is needed because boost::signals2::signal is neither copyable nor movable // and I don't know how else to insert it into the map auto signal = std::make_shared>(); fSignals.insert(std::make_pair(key, signal)); } return boost::any_cast>>(fSignals.at(key)); } }; /* class EventManager */ } /* namespace mq */ } /* namespace fair */ #endif /* FAIR_MQ_EVENTMANAGER_H */