diff --git a/include/rosa/deluxe/DeluxeAgent.hpp b/include/rosa/deluxe/DeluxeAgent.hpp index 535a2ec..0d4993f 100755 --- a/include/rosa/deluxe/DeluxeAgent.hpp +++ b/include/rosa/deluxe/DeluxeAgent.hpp @@ -1,698 +1,700 @@ //===-- rosa/deluxe/DeluxeAgent.hpp -----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeAgent.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Specialization of \c rosa::Agent for *agent* role of the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXEAGENT_HPP #define ROSA_DELUXE_DELUXEAGENT_HPP #include "rosa/core/Agent.hpp" #include "rosa/deluxe/DeluxeAtoms.hpp" #include "rosa/deluxe/DeluxeExecutionPolicy.h" #include /// Local helper macros to deal with built-in types. /// ///@{ /// Creates function name for member functions in \c rosa::deluxe::DeluxeAgent. /// /// \param N name suffix to use #define DAHANDLERNAME(N) handleSlave_##N /// Defines member functions for handling messages from *slaves* in /// \c rosa::deluxe::DeluxeAgent. /// /// \see \c DeluxeAgentInputHandlers /// /// \note No pre- and post-conditions are validated directly by these functions, /// they rather rely on \c rosa::deluxe::DeluxeAgent::saveInput to do that. /// /// \param T the type of input to handle /// \param N name suffix for the function identifier #define DAHANDLERDEFN(T, N) \ void DAHANDLERNAME(N)(atoms::Slave, id_t SlaveId, T Value) noexcept { \ saveInput(SlaveId, Value); \ } /// Convenience macro for \c DAHANDLERDEFN with identical arguments. /// /// \see \c DAHANDLERDEFN /// /// This macro can be used instead of \c DAHANDLERDEFN if the actual value of /// \p T can be used as a part of a valid identifier. /// /// \param T the type of input to handle #define DAHANDLERDEF(T) DAHANDLERDEFN(T, T) /// Results in a \c THISMEMBER reference to a member function defined by /// \c DAHANDLERDEFN. /// /// Used in the constructor of \c rosa::deluxe::DeluxeAgent to initialize super /// class \c rosa::Agent with member function defined by \c DAHANDLERDEFN. /// /// \see \c DAHANDLERDEFN, \c THISMEMBER /// /// \param N name suffix for the function identifier #define DAHANDLERREF(N) THISMEMBER(DAHANDLERNAME(N)) ///@} namespace rosa { namespace deluxe { /// Specialization of \c rosa::Agent for *agent* role of the *deluxe interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// -/// \invariant All input-related container objects have a size matching -/// \c rosa::deluxe::DeluxeAgent::NumberOfInputs, thus having a corresponding -/// entry for each input. Types of input values are consistent throughout all -/// the input-related containers. No *slave* is registered at more than one -/// input position. *Slave* registrations and corresponding reverse lookup -/// information are consistent. +/// \invariant There is a compatible *execution policy* set, all input-related +/// container objects have a size matching \c +/// rosa::deluxe::DeluxeAgent::NumberOfInputs, thus having a corresponding entry +/// for each input. Types of input values are consistent throughout all the +/// input-related containers. No *slave* is registered at more than one input +/// position. *Slave* registrations and corresponding reverse lookup information +/// are consistent. /// /// \see Definition of \c rosa::deluxe::DeluxeAgent::inv on the class invariant /// /// \note All member functions validate the class invariant as part of their /// precondition. Moreover, non-const functions validate the invariant before /// return as their postcondition. class DeluxeAgent : public Agent { /// Checks whether \p this object holds the class invariant. /// /// \see Invariant of the class \c rosa::deluxe::DeluxeAgent /// /// \return if \p this object holds the class invariant bool inv(void) const noexcept; /// The \p DeluxeExecutionPolicy that controls the execution of \t this /// object. std::unique_ptr ExecutionPolicy; public: /// Template alias for function objects used to process input and generate /// output for \c rosa::deluxe::DeluxeAgent. /// /// The output generated by the function is optional as an agent may decide /// not to output anything at some situation. /// /// \note The function used for \c D is to be \c noexcept. /// /// \tparam T type of output /// \tparam As types of input values template using D = std::function(std::pair...)>; /// The type of values produced by \p this object. /// /// That is the type of values \p this object sends to its *master*. /// /// \see \c rosa::deluxe::DeluxeAgent::master const TypeNumber OutputType; /// Number of inputs processed by \p this object. const size_t NumberOfInputs; private: /// Types of input values produced by *slaves* of \p this object. /// /// \note The \c rosa::TypeNumber values stored here match the corresponding /// values in \c rosa::deluxe::DeluxeAgent::InputValues. /// /// \note The position of a type in the \c std::vector indicates which /// argument of \p this object's processing function it belongs to. See also /// \c rosa::deluxe::DeluxeAgent::D. const std::vector InputTypes; /// Indicates whether any particular input value has been changed since the /// last trigger received from the system. /// /// All the flags are reset to \c false upon handling a trigger and then set /// to \c true by \c rosa::deluxe::DeluxeAgent::saveInput when storing a new /// input value in \c rosa::deluxe::DeluxeAgent::InputValues. /// /// \note The position of a flag in the \c std::vector indicates which /// argument of \p this object's processing function it belongs to. See also /// \c rosa::deluxe::DeluxeAgent::D. std::vector InputChanged; /// Stores the actual input values. /// /// \note The types of stored values match the corresponding /// \c rosa::TypeNumber values in \c rosa::deluxe::DeluxeAgent::InputTypes. /// /// \note The position of a value in the \c rosa::AbstractTokenizedStorage /// indicates which argument of \p this object's processing function it is. /// See also \c rosa::deluxe::DeluxeAgent::D. const std::unique_ptr InputValues; /// Alias for function objects used as trigger handler for /// \c rosa::deluxe::DeluxeAgent. /// /// \note The function used for \c H is to be \c noexcept. /// /// \see \c rosa::deluxe::DeluxeAgent::FP using H = std::function; /// Handles trigger from the system. /// /// The actual function processing *slave* inputs and generating optional /// output to *master* is captured in a lambda expression that is in turn /// wrapped in a \c std::function object. The lambda expression calls the /// processing function with the actual input data and sends its result -- if /// any -- to *master* by calling \c rosa::deluxe::DeluxeAgent::sendToMaster. /// Also, all the flags stored in \c rose::deluxe::DeluxeAgent::InputChanged /// are reset when the current input values are processed. The function /// \c rosa::deluxe::DeluxeAgent::handleTrigger needs only to call the /// function object. /// /// \see \c rosa::deluxe::DeluxeAgent::triggerHandlerFromProcessingFunction const H FP; /// The *master* to send values to. /// /// \note *Masters* are set dynamically, hence it is possible that a /// \c rosa::deluxe::DeluxeAgent instance does not have any *master* at a /// given moment. Optional Master; /// The *slaves* sending input to \p this object. /// /// \note The position of a *slave* in the \c std::vector indicates which /// argument of \p this object's processing function it belongs to. See also /// \c rosa::deluxe::DeluxeAgent::D. /// /// \note *Slaves* are set dynamically, hence it is possible that a /// \c rosa::deluxe::DeluxeAgent instance does have input positions without /// any *slave* associated to them. /// /// \note Reverse lookup information is maintained in /// \c rosa::deluxe::DeluxeAgent::SlaveIds, which is to be kept in sync with /// the *slaves* stored here. std::vector> Slaves; /// Associates \c rosa::id_t values to corresponding indices of registered /// *slaves*. /// /// \see \c rosa::deluxe::DeluxeAgent::Slaves std::map SlaveIds; /// Tells whether types \p As... match the input types of \p this object. /// /// \tparam As types to match against values in /// \c rosa::deluxe::DeluxeAgent::InputTypes /// /// \return if types \p As... match \c rosa::TypeNumber values stored in /// \c rosa::deluxe::DeluxeAgent::InputTypes template bool inputTypesMatch(void) const noexcept; /// Gives an \c std::tuple containing the current input values and their /// change flags so that they can be used for the processing function. /// /// \tparam As types of the input values /// \tparam S0 indices for accessing input values and their change flags /// /// \note The only argument provides indices statically as template arguments /// \p S0..., so its actual value is ignored. /// /// \return current input values and their change flags prepared for invoking /// the processing function with them /// /// \pre The type arguments \p As... match the input types of \p this object /// and the provided indices \p S0... constitute a proper sequence for /// accessing input values and their change flags: \code /// inputTypesMatch() && sizeof...(As) == sizeof...(S0) /// \endcode template std::tuple...> prepareCurrentInputs(Seq) const noexcept; /// Invokes a processing function matching the output and input types of /// \p this object with actual arguments provided in a \c std::tuple. /// /// \note \p Args providing the actual arguments for \p F is to be created by /// \c rosa::deluxe::DeluxeAgent::prepareCurrentInputs. /// /// \tparam T output type of the processing function /// \tparam As types of inputs for the processing function /// \tparam S0 indices starting with `0` for extracting actual arguments from /// \p Args /// /// \param F the processing function to invoke /// \param Args the actual arguments to invoke \p F with /// /// \note The last argument provides indices statically as template arguments /// \p S0..., so its actual value is ignored. /// /// \return the result of \p F for actual arguments \p Args /// /// \pre The provided sequence of indices \p S0... constitutes a proper /// sequence for extracting all actual arguments for /// \p F from \p Args: \code /// sizeof...(As) == sizeof...(S0) /// \endcode template static Optional invokeWithTuple(D F, std::tuple...> Args, Seq) noexcept; /// Wraps a processing function into a trigger handler. /// /// \see \c rosa::deluxe::DeluxeAgent::FP /// /// \note The function cannot be const qualified because the lambda /// expression defined in it needs to capture \p this object by a non-const /// reference /// /// \tparam T type of output /// \tparam As types of input values /// /// \param F function processing inputs and generating output /// /// \pre Template arguments \p T and \p As... match the corresponding /// types \p this object was created with: \code /// OutputType == TypeNumberOf::Value && inputTypesMatch() /// \endcode template H triggerHandlerFromProcessingFunction(D &&F) noexcept; public: /// Creates a new instance. /// /// The constructor instantiates the base-class with functions to handle /// messages as defined for the *deluxe interface*. /// /// \todo Enforce F does not potentially throw exception. /// /// \tparam T type of output of \p F /// \tparam As types of input values of \p F /// /// \note Instantiation fails if any of the type arguments \p T and \p As... /// is not a built-in type. /// /// \param Kind kind of the new \c rosa::Unit instance /// \param Id unique identifier of the new \c rosa::Unit instance /// \param Name name of the new \c rosa::Unit instance /// \param S \c rosa::MessagingSystem owning the new instance /// \param F function to process input values and generate output with /// /// \pre Statically, all of the type arguments \p T and \p As... is a /// built-in type: \code /// TypeListSubsetOf, BuiltinTypes>::Value /// \endcode Dynamically, the instance is created as of kind /// \c rosa::deluxe::atoms::AgentKind: \code /// Kind == rosa::deluxe::atoms::AgentKind /// \endcode template , BuiltinTypes>::Value>> DeluxeAgent(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, D &&F) noexcept; /// Destroys \p this object. ~DeluxeAgent(void) noexcept; /// Returns the current \p DeluxeExecutionPolicy of \p this object. /// /// \note The returned reference is valid only as long as \p /// setExecutionPolicy() is not called and \p this object is alive. /// /// \return the \p DeluxeExecutionPolicy from \p ExecutionPolicy const DeluxeExecutionPolicy &executionPolicy(void) const noexcept; /// Sets the current \p DeluxeExecutionPolicy of \this object to \p EP. /// /// \note \p EP is set only if it can handle \p this object. /// /// \param EP the new execution policy for \p this object /// /// \return if \p EP was successfully set for \p this object. bool setExecutionPolicy(std::unique_ptr &&EP) noexcept; /// The *master* of \p this object, if any is registered. /// /// \see \c rosa::deluxe::DeluxeAgent::registerMaster /// /// \return the *master* registered for \p this object Optional master(void) const noexcept; /// Registers a *master* for \p this object. /// /// The new *master* is registered by overwriting the reference to any /// already registered *master*. One can clear the registered reference by /// passing an *empty* \c rosa::Optional object as actual argument. /// /// \note The role of the referred *master* is validated by checking its /// *kind*. /// /// \param _Master the *master* to register /// /// \pre \p _Master is empty or of kind \c rosa::deluxe::atoms::AgentKind: /// \code /// !_Master || unwrapAgent(*_Master).Kind == rosa::deluxe::atoms::AgentKind /// \endcode void registerMaster(const Optional _Master) noexcept; /// Tells the type of values consumed from the *slave* at a position. /// /// That is the type of values \p this object expect to be sent to it by its /// *slave* registered at position \p Pos. /// /// \see \c rosa::deluxe::DeluxeAgent::slave /// /// \param Pos position of *slave* /// /// \return \c rosa::TypeNumber representing the type of values consumed from /// the *slave* at position \p Pos /// /// \pre \p Pos is a valid index of input: \code /// Pos < NumberOfInputs /// \endcode TypeNumber inputType(const size_t Pos) const noexcept; /// The *slave* of \p this object registered at a position, if any. /// /// \see \c rosa::deluxe::DeluxeAgent::registerSlave /// /// \param Pos position of *slave* /// /// \return the *slave* registered for \p this object at position \p Pos /// /// \pre \p Pos is a valid index of input: \code /// Pos < NumberOfInputs /// \endcode Optional slave(const size_t Pos) const noexcept; /// Registers a *slave* for \p this object at a position. /// /// The new *slave* is registered by overwriting the reference to any already /// registered *slave* at position \p Pos. One can clear the registered /// reference by passing an *empty* \c rosa::Optional object as actual /// argument. If \p Slave is already registered for another position, the /// other position gets cleared. /// /// \note The role of the referred *slave* is validated by checking its /// *kind*. /// /// \note The type of values produced by the referred *slave* is validated by /// matching its `OutputType` against the corresponding value in /// \c rosa::deluxe::DeluxeAgent::InputTypes. /// /// \param Pos position to register \p Slave at /// \param Slave the *slave* to register /// /// \pre \p Pos is a valid index of input, \p Slave is empty or of kind /// \c rosa::deluxe::atoms::AgentKind or \c rosa::deluxe::atoms::SensorKind, /// and \p Slave -- if not empty -- produces values of types matching the /// expected input type at position \p Pos: /// \code /// Pos < NumberOfInputs && /// (!Slave || /// (unwrapAgent(*Slave.)Kind == rosa::deluxe::atoms::SensorKind && /// static_cast(unwrapAgent(*Slave)).OutputType == /// InputTypes[Pos]) || /// (unwrapAgent(*Slave).Kind == rosa::deluxe::atoms::AgentKind && /// static_cast(unwrapAgent(*Slave)).OutputType == /// InputTypes[Pos])) /// \endcode void registerSlave(const size_t Pos, const Optional Slave) noexcept; /// Tells the position of a registered *slave*. /// /// \param Slave \c rosa::AgentHandle for the *slave* to check /// /// \return position of \p Slave if it is registered and found, /// \c rosa::deluxe::DeluxeAgent::NumberOfInputs otherwise. size_t positionOfSlave(AgentHandle Slave) const noexcept; private: /// Sends a value to the *master* of \p this object. /// /// \p Value is getting sent to \c rosa::deluxe::DeluxeAgent::Master if it /// contains a valid handle for a \c rosa::deluxe::DeluxeAgent. The function /// does nothing otherwise. /// /// \tparam T type of the value to send /// /// \param Value value to send /// /// \pre \p T matches \c rosa::deluxe::DeluxeiAgent::OutputType: \code /// OutputType == TypeNumberOf::Value /// \endcode template void sendToMaster(const T &Value) noexcept; /// Generates the next output by processing current input values upon trigger /// from the system. /// /// Executes \c rosa::deluxe::DeluxeAgent::FP. /// /// \note The only argument is a \c rosa::AtomConstant, hence its actual /// value is ignored. void handleTrigger(atoms::Trigger) noexcept; /// Stores a new input value from a *slave*. /// /// The function stores \p Value in \c rosa::deluxe::DeluxeAgent::InputValues /// at the position associated to \p Id in /// \c rosa::deluxe::DeluxeAgent::SlaveIds and also sets the corresponding /// flag in \c rosa::deluxe::DeluxeAgent::InputChanged. /// /// \note Utilized by member functions of group \c DeluxeAgentInputHandlers. /// /// \tparam T type of input to store /// /// \param Id unique identifier of *slave* /// \param Value the input value to store /// /// \pre The *slave* with \p Id is registered and the input from it is /// expected to be of type \p T: \code /// SlaveIds.find(Id) != SlaveIds.end() && /// InputTypes[SlaveIds.find(Id)->second] == TypeNumberOf::Value /// \endcode template void saveInput(id_t Id, T Value) noexcept; /// \defgroup DeluxeAgentInputHandlers Input handlers of rosa::deluxe::DeluxeAgent /// /// Definition of member functions handling messages from *slaves* with /// different types of input /// /// A *master* generally needs to be prepared to deal with values of any /// built-in type. Each type requires a separate message handler, which are /// implemented by these functions. The functions instantiate /// \c rosa::deluxe::DeluxeAgent::saveInput with the proper template argument /// and pass the content of the message on for processing. /// /// \note The member functions in this group are defined by \c DAHANDLERDEF. /// /// \note Keep these definitions in sync with \c rosa::BuiltinTypes. /// ///@{ DAHANDLERDEF(AtomValue) DAHANDLERDEF(int16_t) DAHANDLERDEF(int32_t) DAHANDLERDEF(int64_t) DAHANDLERDEF(int8_t) DAHANDLERDEFN(long double, long_double) DAHANDLERDEFN(std::string, std__string) DAHANDLERDEF(uint16_t) DAHANDLERDEF(uint32_t) DAHANDLERDEF(uint64_t) DAHANDLERDEF(uint8_t) DAHANDLERDEF(unit_t) DAHANDLERDEF(bool) DAHANDLERDEF(double) DAHANDLERDEF(float) /// @} }; /// Anonymous namespace with implementation for /// \c rosa::deluxe::DeluxeAgent::inputTypesMatch, consider it private. namespace { /// Template \c struct whose specializations provide a recursive implementation /// for \c rosa::deluxe::DeluxeAgent::inputTypesMatch. /// /// \note Matching a list of types \p As... against a \c std::vector of /// \c rosa::TypeNumber values, \c InputTypes, like \code /// bool match = InputTypesMatchImpl::f(InputTypes, 0); /// \endcode /// /// \tparam As types to match template struct InputTypesMatchImpl; /// Template specialization for the general case, when at least one type is to /// be matched. /// /// \tparam A first type to match /// \tparam As further types to match template struct InputTypesMatchImpl { /// Tells whether types \p A, \p As... match \c rosa::TypeNumber values /// stored in \p InputTypes starting at position \p Pos. /// /// The function has got a recursive implementation: it matches the first /// type \p A against \c rosa::TypeNumber at position \p Pos of \p /// InputTypes, then further types \p As.. are matched recursively starting /// at position \c (Pos + 1). /// /// \param InputTypes container of \c rosa::TypeNumber values to match /// types against /// \param Pos position in \p InputTypes to start matching at /// /// \return if types \p A, \p As... match \c rosa::TypeNumber values stored /// in \p InputTypes starting at position \p Pos static bool f(const std::vector &InputTypes, size_t Pos) noexcept { return Pos < InputTypes.size() && TypeNumberOf::Value == InputTypes[Pos] && InputTypesMatchImpl::f(InputTypes, Pos + 1); } }; /// Template specialization for the terminal case, when no type remains to /// check. template <> struct InputTypesMatchImpl<> { /// Tells whether \p Pos is the number of values stored in \p InputTypes. /// /// In this terminal case, there is no more types to matchi because all the /// types are supposed to be already matched successfully. The whole list of /// types already matched is a complete match if it covers all values in /// \p InputTypes. That is true if \p Pos points exactly to the end of /// \p InputTypes. /// /// \param InputTypes container of \c rosa::TypeNumber values to match /// types against /// \param Pos position in \p InputTypes to start matching at /// /// \return if \p Pos is the number of values stored in \p InputTypes static bool f(const std::vector &InputTypes, size_t Pos) noexcept { return Pos == InputTypes.size(); } }; } // End namespace template bool DeluxeAgent::inputTypesMatch(void) const noexcept { return InputTypesMatchImpl::f(InputTypes, 0); } template std::tuple...> DeluxeAgent::prepareCurrentInputs(Seq) const noexcept { // Need to indirectly reference \c rosa::deluxe::DeluxeAgent::inputTypesMatch // inside \c ASSERT because of the comma in its template argument list. auto MFP = &DeluxeAgent::inputTypesMatch; ASSERT(inv() && (this->*MFP)() && sizeof...(As) == sizeof...(S0)); return std::make_tuple( std::make_pair(*static_cast(InputValues->pointerTo(S0)), InputChanged[S0])...); } template Optional DeluxeAgent::invokeWithTuple( D F, std::tuple...> Args, Seq) noexcept { ASSERT(sizeof...(As) == sizeof...(S0)); return F(std::get(Args)...); } template DeluxeAgent::H DeluxeAgent::triggerHandlerFromProcessingFunction(D &&F) noexcept { // Need to indirectly reference \c rosa::deluxe::DeluxeAgent::inputTypesMatch // inside \c ASSERT because of the comma in its template argument list. auto MFP = &DeluxeAgent::inputTypesMatch; ASSERT(OutputType == TypeNumberOf::Value && (this->*MFP)()); return [ this, F ]() noexcept { // Call the processing function only if \p ExecutionPolicy allows. if (ExecutionPolicy->doExecute(InputChanged)) { using Indices = typename GenSeq::Type; auto Args = prepareCurrentInputs(Indices()); std::fill(InputChanged.begin(), InputChanged.end(), false); Optional R = invokeWithTuple(F, Args, Indices()); if (R) { sendToMaster(*R); } } }; } template DeluxeAgent::DeluxeAgent(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, D &&F) noexcept : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger), DAHANDLERREF(AtomValue), DAHANDLERREF(int16_t), DAHANDLERREF(int32_t), DAHANDLERREF(int64_t), DAHANDLERREF(int8_t), DAHANDLERREF(long_double), DAHANDLERREF(std__string), DAHANDLERREF(uint16_t), DAHANDLERREF(uint32_t), DAHANDLERREF(uint64_t), DAHANDLERREF(uint8_t), DAHANDLERREF(unit_t), DAHANDLERREF(bool), DAHANDLERREF(double), DAHANDLERREF(float)), ExecutionPolicy(DeluxeExecutionPolicy::decimation(1)), OutputType(TypeNumberOf::Value), NumberOfInputs(sizeof...(As)), InputTypes({TypeNumberOf::Value...}), InputChanged(NumberOfInputs, false), InputValues(new TokenizedStorage()), FP(triggerHandlerFromProcessingFunction(std::move(F))), Slaves(NumberOfInputs) { ASSERT(Kind == atoms::AgentKind); LOG_TRACE("DeluxeAgent is created."); ASSERT(inv()); } template void DeluxeAgent::sendToMaster(const T &Value) noexcept { ASSERT(inv() && OutputType == TypeNumberOf::Value); // There is a handle and the referred *master* is in a valid state. if (Master && *Master) { Master->sendMessage(Message::create(atoms::Slave::Value, Id, Value)); } + ASSERT(inv()); } template void DeluxeAgent::saveInput(id_t Id, T Value) noexcept { ASSERT(inv() && SlaveIds.find(Id) != SlaveIds.end() && InputTypes[SlaveIds.find(Id)->second] == TypeNumberOf::Value); size_t Pos = SlaveIds.at(Id); *static_cast(InputValues->pointerTo(Pos)) = Value; InputChanged[Pos] = true; ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa #undef DAHANDLEREF #undef DAHANDLEDEF #undef DAHANDLEDEFN #undef DAHANDLENAME #endif // ROSA_DELUXE_DELUXEAGENT_HPP diff --git a/include/rosa/deluxe/DeluxeSensor.hpp b/include/rosa/deluxe/DeluxeSensor.hpp index 5c6dce8..a3bb2d0 100755 --- a/include/rosa/deluxe/DeluxeSensor.hpp +++ b/include/rosa/deluxe/DeluxeSensor.hpp @@ -1,293 +1,310 @@ //===-- rosa/deluxe/DeluxeSensor.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeSensor.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Specialization of \c rosa::Agent for *sensor* role of the the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXESENSOR_HPP #define ROSA_DELUXE_DELUXESENSOR_HPP #include "rosa/core/Agent.hpp" #include "rosa/deluxe/DeluxeAtoms.hpp" #include "rosa/deluxe/DeluxeExecutionPolicy.h" namespace rosa { namespace deluxe { /// Specialization of \c rosa::Agent for *sensor* role of the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext +/// +/// \invariant There is a compatible *execution policy* set +/// +/// \see Definition of \c rosa::deluxe::DeluxeSensor::inv on the class invariant +/// +/// \note All member functions validate the class invariant as part of their +/// precondition. Moreover, non-const functions validate the invariant before +/// return as their postcondition. class DeluxeSensor : public Agent { -private: + /// Checks whether \p this object holds the class invariant. + /// + /// \see Invariant of the class \c rosa::deluxe::DeluxeSensor + /// + /// \return if \p this object holds the class invariant + bool inv(void) const noexcept; + /// The \p DeluxeExecutionPolicy that controls the execution of \t this /// object. std::unique_ptr ExecutionPolicy; public: /// Template alias for function objects used as data source for /// \c rosa::deluxe::DeluxeSensor. /// /// \note The function used for \c D is to be \c noexcept. /// /// \tparam T type of data provided by the function template using D = std::function; /// The type of values produced by \p this object. /// /// That is the type of values \p this object sends to its *master*. /// /// \see \c rosa::deluxe::DeluxeSensor::master const TypeNumber OutputType; private: /// Alias for function objects used as trigger handler for /// \c rosa::deluxe::DeluxeSensor. /// /// \note The function used for \c H is to be \c noexcept. /// /// \see \c DeluxeSensorTriggerHandlers using H = std::function; /// \defgroup DeluxeSensorTriggerHandlers Trigger handlers of rosa::deluxe::DeluxeSensor /// /// \brief Trigger handler functions of \c rosa::deluxe::DeluxeSensor /// /// The actual data source functions are captured in a lambda expression that /// is in turn wrapped in a \c std::function object. The lambda expression /// calls the data source function to obtain the next sensory value and sends /// it to *master* by calling \c rosa::deluxe::DeluxeSensor::sendToMaster. The /// function \c rosa::deluxe::DeluxeSensor::handleTrigger needs only to call /// the proper function object. /// Handles trigger during normal execution. /// /// \ingroup DeluxeSensorTriggerHandlers /// /// The function is used during normal execution. During simulation, the /// simulation environment sets \c rosa::deluxe::DeluxeSensor::SFP, which is /// used instead of \c rosa::deluxe::DeluxeSensor::FP. const H FP; /// Handles trigger during simulation. /// /// \ingroup DeluxeSensorTriggerHandlers /// /// The function is empty by default. The simulation environment sets it to be /// used during simulation. H SFP; /// The *master* to send values to. /// /// \note *Masters* are set dynamically, hence it is possible that a /// \c rosa::deluxe::DeluxeSensor instance does not have any *master* at a /// given moment. Optional Master; /// Wraps a data source function into a trigger handler. /// /// \see \c DeluxeSensorTriggerHandlers /// /// \tparam T type of data provided by \p F /// /// \param F function to generate value with /// \param inSimulation if F is a data source for Simulation /// /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == TypeNumberOf::Value /// \endcode template H triggerHandlerFromDataSource(D &&F, bool inSimulation) noexcept; public: /// Creates a new instance. /// /// The constructor instantiates the base-class with functions to handle /// messages as defined for the *deluxe interface*. /// /// \todo Enforce F does not potentially throw exception. /// /// \tparam T type of data to operate on /// /// \param Kind kind of the new \c rosa::Unit instance /// \param Id unique identifier of the new \c rosa::Unit instance /// \param Name name of the new \c rosa::Unit instance /// \param S \c rosa::MessagingSystem owning the new instance /// \param F function to generate the next value with during normal operation /// /// \pre Statically, \p T is a built-in type:\code /// TypeListContains::Value /// \endcode /// Dynamically, the instance is created as of kind /// \c rosa::deluxe::atoms::SensorKind: /// \code /// Kind == rosa::deluxe::atoms::SensorKind /// \endcode template ::Value>> DeluxeSensor(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, D &&F) noexcept; /// Destroys \p this object. ~DeluxeSensor(void) noexcept; /// Returns the current \p DeluxeExecutionPolicy of \p this object. /// /// \note The returned reference is valid only as long as \p /// setExecutionPolicy() is not called and \p this object is alive. /// /// \return the \p DeluxeExecutionPolicy from \p ExecutionPolicy const DeluxeExecutionPolicy &executionPolicy(void) const noexcept; /// Sets the current \p DeluxeExecutionPolicy of \this object to \p EP. /// /// \note \p EP is set only if it can handle \p this object. /// /// \param EP the new execution policy for \p this object /// /// \return if \p EP was successfully set for \p this object. bool setExecutionPolicy(std::unique_ptr &&EP) noexcept; /// The *master* of \p this object, if any. /// /// \see \c rosa::deluxe::DeluxeSensor::registerMaster /// /// \return the *master* registered for \p this object Optional master(void) const noexcept; /// Registers a *master* for \p this object. /// /// The new *master* is registered by overwriting the reference to any /// already registered *master*. One can clear the registered reference by /// passing an *empty* \c rosa::Optional object as actual argument. /// /// \note The role of the referred *master* is validated by checking its /// *kind*. /// /// \param _Master the *master* to register /// /// \pre \p Master is empty or of kind \c rosa::deluxe::atoms::AgentKind: /// \code /// !_Master || unwrapAgent(*_Master).Kind == rosa::deluxe::atoms::AgentKind /// \endcode void registerMaster(const Optional _Master) noexcept; /// Clears the simulation trigger handler of \p this object. /// /// The function assigns \c rosa::deluxe::DeluxeSensor::SFP with \c nullptr. void clearSimulationDataSource(void) noexcept; /// Tells whether a simulation trigger handler is set for \p this object. /// /// The function returns whether \c rosa::deluxe::DeluxeSensor::SFP is not /// \c nullptr. /// /// \return if a simulation trigger handler is set for \p this object. bool simulationDataSourceIsSet(void) const noexcept; /// Registers a simulation data source for \p this object. /// /// A new simulation trigger handler wrapping \p SF is stored in /// \c rosa::deluxe::DeluxeSensor::SFP by overwriting any already registered /// simulation data source. /// /// \todo Enforce SF does not potentially throw exception. /// /// \tparam T type of data provided by \p SF /// /// \param SF function to generate value with /// /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == TypeNumberOf::Value /// \endcode template void registerSimulationDataSource(D &&SF) noexcept; private: /// Sends a value to the *master* of \p this object. /// /// \p Value is getting sent to \c rosa::deluxe::DeluxeSensor::Master if it /// contains a valid handle for a \c rosa::deluxe::DeluxeAgent. The function /// does nothing otherwise. /// /// \tparam T type of the value to send /// /// \param Value value to send /// /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == TypeNumberOf::Value /// \endcode template void sendToMaster(const T &Value) noexcept; /// Generates the next sensory value upon trigger from the system. /// /// Executes \c rosa::deluxe::DeluxeSensor::FP or /// \c rosa::deluxe::DeluxeSensor::SFP if set. /// /// \note The only argument is a \c rosa::AtomConstant, hence its actual /// value is ignored. void handleTrigger(atoms::Trigger) noexcept; }; template DeluxeSensor::H DeluxeSensor::triggerHandlerFromDataSource(D &&F, bool inSimulation) noexcept { ASSERT(OutputType == TypeNumberOf::Value); return [ this, F, inSimulation ](void) noexcept { // Get value and send it to master only if \p ExecutionPolicy allows it. if (ExecutionPolicy->doExecute({})) { sendToMaster(F()); } else if (inSimulation) { // But read input value in Simulation anyway as input values are provided // for the highest execution frequency for simulation F(); } }; } template DeluxeSensor::DeluxeSensor(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, D &&F) noexcept : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger)), ExecutionPolicy(DeluxeExecutionPolicy::decimation(1)), OutputType(TypeNumberOf::Value), FP(triggerHandlerFromDataSource(std::move(F), false)), SFP(nullptr) { ASSERT(Kind == atoms::SensorKind); LOG_TRACE("DeluxeSensor is created."); + ASSERT(inv()); } template void DeluxeSensor::registerSimulationDataSource(D &&SF) noexcept { - ASSERT(OutputType == TypeNumberOf::Value); + ASSERT(inv() && OutputType == TypeNumberOf::Value); SFP = triggerHandlerFromDataSource(std::move(SF), true); + ASSERT(inv()); } template void DeluxeSensor::sendToMaster(const T &Value) noexcept { - ASSERT(OutputType == TypeNumberOf::Value); + ASSERT(inv() && OutputType == TypeNumberOf::Value); // There is a handle and the referred *master* is in a valid state. if (Master && *Master) { Master->sendMessage(Message::create(atoms::Slave::Value, Id, Value)); } + ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa #endif // ROSA_DELUXE_DELUXESENSOR_HPP diff --git a/lib/deluxe/DeluxeAgent.cpp b/lib/deluxe/DeluxeAgent.cpp index 278c1af..3728913 100755 --- a/lib/deluxe/DeluxeAgent.cpp +++ b/lib/deluxe/DeluxeAgent.cpp @@ -1,222 +1,233 @@ //===-- deluxe/DeluxeAgent.cpp ----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file deluxe/DeluxeAgent.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Implementation of rosa/deluxe/DeluxeAgent.hpp. /// //===----------------------------------------------------------------------===// #include "rosa/deluxe/DeluxeAgent.hpp" #include "rosa/deluxe/DeluxeSystem.hpp" #include namespace rosa { namespace deluxe { bool DeluxeAgent::inv(void) const noexcept { + // Check execution policy. + // \note The \c rosa::System the \c rosa::Unit is created with is a + // \c rosa::DeluxeSystem. + const DeluxeSystem &DS = static_cast(Unit::system()); + if (!ExecutionPolicy || !ExecutionPolicy->canHandle(Self, DS)) { + return false; + } + // Check container sizes. if (!(InputTypes.size() == NumberOfInputs && InputChanged.size() == NumberOfInputs && InputValues->size() == NumberOfInputs && Slaves.size() == NumberOfInputs)) { return false; } // Check *slave* types and validate *slave* registrations and reverse lookup // information. std::map RefIds; // Build up a reference of SlaveIds in this. for (size_t I = 0; I < NumberOfInputs; ++I) { // First, validate input types at position \c I. const TypeNumber T = InputTypes[I]; if (InputValues->typeAt(I) != T) { return false; } // Check the registered *slave* at position \c I. const auto &Slave = Slaves[I]; // If \c Slave is empty, nothing to check. if (!Slave) continue; // \c Slave is not empty here. // Check the `OutputType` of the registered *slave*. const auto &A = unwrapAgent(*Slave); if (!((A.Kind == atoms::SensorKind && static_cast(A).OutputType == T) || (A.Kind == atoms::AgentKind && static_cast(A).OutputType == T))) { return false; } // Validate that the *slave* is not registered more than once. if (std::any_of( Slaves.begin() + I + 1, Slaves.end(), [&Slave](const Optional &O) { return O && *Slave == *O; })) { return false; } // Build the content of \c RefIds. RefIds.emplace(A.Id, I); } // Validate *slave* reverse lookup information against our reference. if (RefIds != SlaveIds) { return false; } // All checks were successful, the invariant is held. return true; } DeluxeAgent::~DeluxeAgent(void) noexcept { ASSERT(inv()); LOG_TRACE("Destroying DeluxeAgent..."); // Make sure \p this object is not a registered *slave*. if (Master) { ASSERT(unwrapAgent(*Master).Kind == atoms::AgentKind); // Sanity check. DeluxeAgent &M = static_cast(unwrapAgent(*Master)); ASSERT(M.positionOfSlave(self()) != M.NumberOfInputs); // Sanity check. M.registerSlave(M.positionOfSlave(self()), {}); Master = {}; } // Also, make sure \p this object is no acting *master*. for (size_t Pos = 0; Pos < NumberOfInputs; ++Pos) { registerSlave(Pos, {}); } // Now there is no connection with other entities, safe to destroy. } const DeluxeExecutionPolicy &DeluxeAgent::executionPolicy(void) const noexcept { + ASSERT(inv()); return *ExecutionPolicy; } bool DeluxeAgent::setExecutionPolicy( std::unique_ptr &&EP) noexcept { + ASSERT(inv()); + bool Success = false; // \note The \c rosa::System the \c rosa::Unit is created with is a // \c rosa::DeluxeSystem. const DeluxeSystem &DS = static_cast(Unit::system()); if (EP && EP->canHandle(self(), DS)) { ExecutionPolicy.swap(EP); - return true; - } else { - return false; + Success = true; } + ASSERT(inv()); + return Success; } Optional DeluxeAgent::master(void) const noexcept { ASSERT(inv()); return Master; } void DeluxeAgent::registerMaster(const Optional _Master) noexcept { ASSERT(inv() && (!_Master || unwrapAgent(*_Master).Kind == atoms::AgentKind)); Master = _Master; ASSERT(inv()); } TypeNumber DeluxeAgent::inputType(const size_t Pos) const noexcept { ASSERT(inv() && Pos < NumberOfInputs); return InputTypes[Pos]; } Optional DeluxeAgent::slave(const size_t Pos) const noexcept { ASSERT(inv() && Pos < NumberOfInputs); return Slaves[Pos]; } void DeluxeAgent::registerSlave(const size_t Pos, const Optional Slave) noexcept { ASSERT(inv() && Pos < NumberOfInputs && (!Slave || (unwrapAgent(*Slave).Kind == atoms::SensorKind && static_cast(unwrapAgent(*Slave)).OutputType == InputTypes[Pos]) || (unwrapAgent(*Slave).Kind == atoms::AgentKind && static_cast(unwrapAgent(*Slave)).OutputType == InputTypes[Pos]))); // If registering an actual *slave*, not just clearing the slot, make sure // the same *slave* is not registered to another slot. if (Slave) { auto It = SlaveIds.find(unwrapAgent(*Slave).Id); if (It != SlaveIds.end()) { Slaves[It->second] = {};//Optional(); SlaveIds.erase(It); } } // Obtain the place whose content is to be replaced with \p Slave auto &OldSlave = Slaves[Pos]; // If there is already a *slave* registered at \p Pos, clear reverse lookup // information for it, and make sure it no longer has \p this object as // *master*. if (OldSlave) { auto &A = unwrapAgent(*OldSlave); ASSERT(SlaveIds.find(A.Id) != SlaveIds.end()); // Sanity check. SlaveIds.erase(A.Id); if (A.Kind == atoms::AgentKind) { static_cast(A).registerMaster({}); } else { ASSERT(A.Kind == atoms::SensorKind); // Sanity check. static_cast(A).registerMaster({}); } } // Register \p Slave at \p Pos. OldSlave = Slave; // If registering an actual *slave*, not just clearing the slot, register // reverse lookup information for the new *slave*. if (Slave) { SlaveIds.emplace(unwrapAgent(*Slave).Id, Pos); } ASSERT(inv()); } size_t DeluxeAgent::positionOfSlave(const AgentHandle Slave) const noexcept { ASSERT(inv()); bool Found = false; size_t Pos = 0; while (!Found && Pos < NumberOfInputs) { auto &ExistingSlave = Slaves[Pos]; if (ExistingSlave && *ExistingSlave == Slave) { Found = true; } else { ++Pos; } } ASSERT(Found || Pos == NumberOfInputs); // Sanity check. return Pos; } void DeluxeAgent::handleTrigger(atoms::Trigger) noexcept { ASSERT(inv()); FP(); ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa diff --git a/lib/deluxe/DeluxeSensor.cpp b/lib/deluxe/DeluxeSensor.cpp index 83d3c37..0175cd5 100755 --- a/lib/deluxe/DeluxeSensor.cpp +++ b/lib/deluxe/DeluxeSensor.cpp @@ -1,81 +1,108 @@ //===-- deluxe/DeluxeSensor.cpp ---------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file deluxe/DeluxeSensor.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Implementation of rosa/deluxe/DeluxeSensor.hpp. /// //===----------------------------------------------------------------------===// #include "rosa/deluxe/DeluxeSensor.hpp" #include "rosa/deluxe/DeluxeSystem.hpp" namespace rosa { namespace deluxe { +bool DeluxeSensor::inv(void) const noexcept { + // Check execution policy. + // \note The \c rosa::System the \c rosa::Unit is created with is a + // \c rosa::DeluxeSystem. + const DeluxeSystem &DS = static_cast(Unit::system()); + if (!ExecutionPolicy || !ExecutionPolicy->canHandle(Self, DS)) { + return false; + } + + // All checks were successful, the invariant is held. + return true; +} + DeluxeSensor::~DeluxeSensor(void) noexcept { + ASSERT(inv()); LOG_TRACE("Destroying DeluxeSensor..."); // Make sure \p this object is not a registered *slave*. if (Master) { ASSERT(unwrapAgent(*Master).Kind == atoms::AgentKind); // Sanity check. DeluxeAgent &M = static_cast(unwrapAgent(*Master)); ASSERT(M.positionOfSlave(self()) != M.NumberOfInputs); // Sanity check. M.registerSlave(M.positionOfSlave(self()), {}); Master = {}; } } const DeluxeExecutionPolicy &DeluxeSensor::executionPolicy(void) const noexcept { + ASSERT(inv()); return *ExecutionPolicy; } bool DeluxeSensor::setExecutionPolicy( std::unique_ptr &&EP) noexcept { + ASSERT(inv()); + bool Success = false; // \note The \c rosa::System the \c rosa::Unit is created with is a // \c rosa::DeluxeSystem. const DeluxeSystem &DS = static_cast(Unit::system()); if (EP && EP->canHandle(self(), DS)) { ExecutionPolicy.swap(EP); - return true; - } else { - return false; + Success = true; } + ASSERT(inv()); + return Success; } Optional DeluxeSensor::master(void) const noexcept { + ASSERT(inv()); return Master; } void DeluxeSensor::registerMaster(const Optional _Master) noexcept { - ASSERT(!_Master || unwrapAgent(*_Master).Kind == atoms::AgentKind); + ASSERT(inv() && (!_Master || unwrapAgent(*_Master).Kind == atoms::AgentKind)); Master = _Master; + + ASSERT(inv()); } void DeluxeSensor::clearSimulationDataSource(void) noexcept { + ASSERT(inv()); SFP = nullptr; + ASSERT(inv()); } bool DeluxeSensor::simulationDataSourceIsSet(void) const noexcept { + ASSERT(inv()); return SFP != nullptr; } void DeluxeSensor::handleTrigger(atoms::Trigger) noexcept { + ASSERT(inv()); + // Use \c rosa::deluxe::DeluxeSensor::SFP if set, otherwise // \c rosa::deluxe::DeluxeSensor::FP. const H &F = SFP ? SFP : FP; F(); + + ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa