diff --git a/fairmq/FairMQDevice.cxx b/fairmq/FairMQDevice.cxx index 6fe96734..a4a56401 100644 --- a/fairmq/FairMQDevice.cxx +++ b/fairmq/FairMQDevice.cxx @@ -227,7 +227,6 @@ void FairMQDevice::InitWrapper() } } } - if (!fStateChangeCallback.empty()) { fStateChangeCallback(INITIALIZING_DEVICE); diff --git a/fairmq/FairMQStateMachine.cxx b/fairmq/FairMQStateMachine.cxx index a4c9c656..c9ca76db 100644 --- a/fairmq/FairMQStateMachine.cxx +++ b/fairmq/FairMQStateMachine.cxx @@ -226,8 +226,13 @@ bool FairMQStateMachine::WaitForEndOfStateForMs(std::string event, int durationI return WaitForEndOfStateForMs(GetEventNumber(event), durationInMs); } -void FairMQStateMachine::OnStateChange(std::function callback) +void FairMQStateMachine::OnStateChange(const std::string& key, std::function callback) { - fStateChangeCallback.connect(callback); + fStateChangeCallbacksMap.insert({key, fStateChangeCallback.connect(callback)}); } +void FairMQStateMachine::UnsubscribeFromStateChange(const std::string& key) +{ + fStateChangeCallbacksMap.at(key).disconnect(); + //fStateChangeCallbacksMap.erase(key); +} diff --git a/fairmq/FairMQStateMachine.h b/fairmq/FairMQStateMachine.h index 2905e457..9aee47d0 100644 --- a/fairmq/FairMQStateMachine.h +++ b/fairmq/FairMQStateMachine.h @@ -23,6 +23,7 @@ #include #include #include +#include // Increase maximum number of boost::msm states (default is 10) // This #define has to be before any msm header includes @@ -85,6 +86,7 @@ struct FairMQFSM_ : public msmf::state_machine_def , fState() , fChangeStateMutex() , fStateChangeCallback() + , fStateChangeCallbacksMap() {} // Destructor @@ -568,6 +570,7 @@ struct FairMQFSM_ : public msmf::state_machine_def std::mutex fChangeStateMutex; boost::signals2::signal fStateChangeCallback; + std::unordered_map fStateChangeCallbacksMap; }; // reactivate the warning for non-virtual destructor @@ -616,7 +619,8 @@ class FairMQStateMachine : public FairMQFSM::FairMQFSM bool WaitForEndOfStateForMs(int state, int durationInMs); bool WaitForEndOfStateForMs(std::string state, int durationInMs); - void OnStateChange(std::function callback); + void OnStateChange(const std::string&, std::function callback); + void UnsubscribeFromStateChange(const std::string&); }; #endif /* FAIRMQSTATEMACHINE_H_ */ diff --git a/fairmq/Plugin.h b/fairmq/Plugin.h index 1a3a1072..9519653d 100644 --- a/fairmq/Plugin.h +++ b/fairmq/Plugin.h @@ -69,6 +69,16 @@ class Plugin } static auto NoProgramOptions() -> const boost::optional { return boost::none; } + // device control API + using DeviceState = fair::mq::PluginServices::DeviceState; + using DeviceStateTransition = fair::mq::PluginServices::DeviceStateTransition; + auto ToDeviceState(const std::string& state) const -> fair::mq::PluginServices::DeviceState { return fPluginServices.ToDeviceState(state); } + auto ToStr(fair::mq::PluginServices::DeviceState state) const -> std::string { return fPluginServices.ToStr(state); } + auto GetCurrentDeviceState() const -> fair::mq::PluginServices::DeviceState { return fPluginServices.GetCurrentDeviceState(); } + auto ChangeDeviceState(const fair::mq::PluginServices::DeviceStateTransition next) -> void { fPluginServices.ChangeDeviceState(next); } + auto SubscribeToDeviceStateChange(std::function callback) -> void { fPluginServices.SubscribeToDeviceStateChange(fkName, callback); } + auto UnsubscribeFromDeviceStateChange() -> void { fPluginServices.UnsubscribeFromDeviceStateChange(fkName); } + private: const std::string fkName; diff --git a/fairmq/PluginServices.h b/fairmq/PluginServices.h index c99a4e6e..07d0fd73 100644 --- a/fairmq/PluginServices.h +++ b/fairmq/PluginServices.h @@ -11,8 +11,10 @@ #include #include +#include #include #include +#include namespace fair { @@ -30,56 +32,175 @@ class PluginServices { public: + /// See https://github.com/FairRootGroup/FairRoot/blob/dev/fairmq/docs/Device.md#13-state-machine + enum class DeviceState + { + Ok, + Error, + Idle, + InitializingDevice, + DeviceReady, + InitializingTask, + Ready, + Running, + Paused, + ResettingTask, + ResettingDevice, + Exiting + }; + enum class DeviceStateTransition // transition event between DeviceStates + { + InitDevice, + InitTask, + Run, + Pause, + Stop, + ResetTask, + ResetDevice, + End, + ErrorFound + }; + PluginServices() = delete; PluginServices(FairMQProgOptions& config, FairMQDevice& device) : fDevice{device} , fConfig{config} - {} + , fConfigEnabled{false} + , fkDeviceStateStrMap{ + {"Ok", DeviceState::Ok}, + {"Error", DeviceState::Error}, + {"Idle", DeviceState::Idle}, + {"InitializingDevice", DeviceState::InitializingDevice}, + {"DeviceReady", DeviceState::DeviceReady}, + {"InitializingTask", DeviceState::InitializingTask}, + {"Ready", DeviceState::Ready}, + {"Running", DeviceState::Running}, + {"Paused", DeviceState::Paused}, + {"ResettingTask", DeviceState::ResettingTask}, + {"ResettingDevice", DeviceState::ResettingDevice}, + {"Exiting", DeviceState::Exiting} + } + , fkStrDeviceStateMap{ + {DeviceState::Ok, "Ok"}, + {DeviceState::Error, "Error"}, + {DeviceState::Idle, "Idle"}, + {DeviceState::InitializingDevice, "InitializingDevice"}, + {DeviceState::DeviceReady, "DeviceReady"}, + {DeviceState::InitializingTask, "InitializingTask"}, + {DeviceState::Ready, "Ready"}, + {DeviceState::Running, "Running"}, + {DeviceState::Paused, "Paused"}, + {DeviceState::ResettingTask, "ResettingTask"}, + {DeviceState::ResettingDevice, "ResettingDevice"}, + {DeviceState::Exiting, "Exiting"} + } + , fkDeviceStateMap{ + {FairMQDevice::OK, DeviceState::Ok}, + {FairMQDevice::ERROR, DeviceState::Error}, + {FairMQDevice::IDLE, DeviceState::Idle}, + {FairMQDevice::INITIALIZING_DEVICE, DeviceState::InitializingDevice}, + {FairMQDevice::DEVICE_READY, DeviceState::DeviceReady}, + {FairMQDevice::INITIALIZING_TASK, DeviceState::InitializingTask}, + {FairMQDevice::READY, DeviceState::Ready}, + {FairMQDevice::RUNNING, DeviceState::Running}, + {FairMQDevice::PAUSED, DeviceState::Paused}, + {FairMQDevice::RESETTING_TASK, DeviceState::ResettingTask}, + {FairMQDevice::RESETTING_DEVICE, DeviceState::ResettingDevice}, + {FairMQDevice::EXITING, DeviceState::Exiting} + } + , fkDeviceStateTransitionMap{ + {DeviceStateTransition::InitDevice, FairMQDevice::INIT_DEVICE}, + {DeviceStateTransition::InitTask, FairMQDevice::INIT_TASK}, + {DeviceStateTransition::Run, FairMQDevice::RUN}, + {DeviceStateTransition::Pause, FairMQDevice::PAUSE}, + {DeviceStateTransition::Stop, FairMQDevice::STOP}, + {DeviceStateTransition::ResetTask, FairMQDevice::RESET_TASK}, + {DeviceStateTransition::ResetDevice, FairMQDevice::RESET_DEVICE}, + {DeviceStateTransition::End, FairMQDevice::END}, + {DeviceStateTransition::ErrorFound, FairMQDevice::ERROR_FOUND} + } + { + } // Control - //enum class DeviceState - //{ - //Error, - //Idle, - //Initializing_device, - //Device_ready, - //Initializing_task, - //Ready, - //Running, - //Paused, - //Resetting_task, - //Resetting_device, - //Exiting - //} - //auto ToDeviceState(std::string state) const -> DeviceState; + /// @brief Convert string to DeviceState + /// @param state to convert + /// @return DeviceState enum entry + /// @throw std::out_of_range if a string cannot be resolved to a DeviceState + auto ToDeviceState(const std::string& state) const -> DeviceState + { + return fkDeviceStateStrMap.at(state); + } - //auto ChangeDeviceState(DeviceState next) -> void; + /// @brief Convert DeviceState to string + /// @param string to convert + /// @return string representation of DeviceState enum entry + auto ToStr(DeviceState state) const -> std::string + { + return fkStrDeviceStateMap.at(state); + } - //auto SubscribeToDeviceStateChange(std::functionnew<])> callback) -> void; - //auto UnsubscribeFromDeviceChange() -> void; + /// @return current device state + auto GetCurrentDeviceState() const -> DeviceState + { + return fkDeviceStateMap.at(static_cast(fDevice.GetCurrentState())); + } - //// Configuration + /// @brief Trigger a device state transition + /// @param next state transition + /// + /// The state transition may not happen immediately, but when the current state evaluates the + /// pending transition event and terminates. In other words, the device states are scheduled cooperatively. + auto ChangeDeviceState(const DeviceStateTransition next) -> void + { + fDevice.ChangeState(fkDeviceStateTransitionMap.at(next)); + } - //// Writing only works during Initializing_device state - //template - //auto SetProperty(const std::string& key, T val) -> void; + /// @brief Subscribe with a callback to device state changes + /// @param InputMsgCallback + /// + /// The callback will be called at the beginning of a new state. The callback is called from the thread + /// the state is running in. + auto SubscribeToDeviceStateChange(const std::string& key, std::function callback) -> void + { + fDevice.OnStateChange(key, [&,callback](FairMQDevice::State newState){ + callback(fkDeviceStateMap.at(newState)); + }); + } - //template - //auto GetProperty(const std::string& key) const -> T; - //auto GetPropertyAsString(const std::string& key) const -> std::string; + auto UnsubscribeFromDeviceStateChange(const std::string& key) -> void + { + fDevice.UnsubscribeFromStateChange(key); + } - //template - //auto SubscribeToPropertyChange( - //const std::string& key, - //std::functionkey*/, const T /*newValue<])> callback - //) const -> void; - //auto UnsubscribeFromPropertyChange(const std::string& key) -> void; + + //// Configuration + + //// Writing only works during Initializing_device state + //template + //auto SetProperty(const std::string& key, T val) -> void; + + //template + //auto GetProperty(const std::string& key) const -> T; + //auto GetPropertyAsString(const std::string& key) const -> std::string; + + //template + //auto SubscribeToPropertyChange( + //const std::string& key, + //std::functionkey*/, const T /*newValue<])> callback + //) const -> void; + //auto UnsubscribeFromPropertyChange(const std::string& key) -> void; private: - FairMQDevice& fDevice; FairMQProgOptions& fConfig; + FairMQDevice& fDevice; + std::atomic fConfigEnabled; + const std::unordered_map fkDeviceStateStrMap; + const std::unordered_map fkStrDeviceStateMap; + const std::unordered_map fkDeviceStateMap; + const std::unordered_map fkDeviceStateTransitionMap; }; /* class PluginServices */ } /* namespace mq */ diff --git a/fairmq/test/helper/plugins/dummy.h.in b/fairmq/test/helper/plugins/dummy.h.in index c52b1b03..335c87a6 100644 --- a/fairmq/test/helper/plugins/dummy.h.in +++ b/fairmq/test/helper/plugins/dummy.h.in @@ -26,22 +26,33 @@ class DummyPlugin : public fair::mq::Plugin { public: - DummyPlugin(const std::string name, const Version version, const std::string maintainer, const std::string homepage, PluginServices& pluginServices) + DummyPlugin( + const std::string name, + const Version version, + const std::string maintainer, + const std::string homepage, + PluginServices& pluginServices) : Plugin(name, version, maintainer, homepage, pluginServices) { + SubscribeToDeviceStateChange( + [&](DeviceState newState){ + switch (newState) + { + case DeviceState::Exiting: + UnsubscribeFromDeviceStateChange(); + break; + } + } + ); } - }; /* class DummyPlugin */ auto DummyPluginProgramOptions() -> const boost::optional { - using namespace boost::program_options; - using std::string; - - auto plugin_options = options_description{"Dummy Plugin"}; + auto plugin_options = boost::program_options::options_description{"Dummy Plugin"}; plugin_options.add_options() - ("custom-dummy-option", value(), "Cool custom option."); - ("custom-dummy-option2", value(), "Another cool custom option."); + ("custom-dummy-option", value(), "Cool custom option."); + ("custom-dummy-option2", value(), "Another cool custom option."); return plugin_options; } diff --git a/fairmq/test/plugins/_plugin.cxx b/fairmq/test/plugins/_plugin.cxx index f4fd3f91..b4342519 100644 --- a/fairmq/test/plugins/_plugin.cxx +++ b/fairmq/test/plugins/_plugin.cxx @@ -36,9 +36,9 @@ auto control(FairMQDevice& device) -> void TEST(Plugin, Operators) { - auto config = FairMQProgOptions{}; + FairMQProgOptions config{}; FairMQDevice device{}; - auto services = PluginServices{config, device}; + PluginServices services{config, device}; auto p1 = Plugin{"dds", {1, 0, 0}, "Foo Bar ", "https://git.test.net/dds.git", services}; auto p2 = Plugin{"dds", {1, 0, 0}, "Foo Bar ", "https://git.test.net/dds.git", services}; auto p3 = Plugin{"file", {1, 0, 0}, "Foo Bar ", "https://git.test.net/file.git", services}; @@ -49,9 +49,9 @@ TEST(Plugin, Operators) TEST(Plugin, OstreamOperators) { - auto config = FairMQProgOptions{}; + FairMQProgOptions config{}; FairMQDevice device{}; - auto services = PluginServices{config, device}; + PluginServices services{config, device}; auto p1 = Plugin{"dds", {1, 0, 0}, "Foo Bar ", "https://git.test.net/dds.git", services}; stringstream ss; ss << p1; diff --git a/fairmq/test/plugins/_plugin_manager.cxx b/fairmq/test/plugins/_plugin_manager.cxx index 29a94c13..9988ea07 100644 --- a/fairmq/test/plugins/_plugin_manager.cxx +++ b/fairmq/test/plugins/_plugin_manager.cxx @@ -38,7 +38,7 @@ auto control(FairMQDevice& device) -> void TEST(PluginManager, LoadPlugin) { - auto config = FairMQProgOptions{}; + FairMQProgOptions config{}; FairMQDevice device{}; auto mgr = PluginManager{}; mgr.EmplacePluginServices(config, device);