diff --git a/include/rosa/deluxe/DeluxeAgent.hpp b/include/rosa/deluxe/DeluxeAgent.hpp index 4d54a08..a75d727 100644 --- a/include/rosa/deluxe/DeluxeAgent.hpp +++ b/include/rosa/deluxe/DeluxeAgent.hpp @@ -1,1478 +1,1478 @@ //===-- 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 "rosa/deluxe/DeluxeTuple.hpp" #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 DASLAVEHANDLERNAME(N) handleSlave_##N /// Creates function name for member functions in \c rosa::deluxe::DeluxeAgent. /// /// \param N name suffix to use #define DAMASTERHANDLERNAME(N) handleMaster_##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 DASLAVEHANDLERDEFN(T, N) \ void DASLAVEHANDLERNAME(N)(atoms::Slave, id_t SlaveId, token_size_t Pos, \ T Value) noexcept { \ saveInput(SlaveId, Pos, Value); \ } /// Defines member functions for handling messages from *master* in /// \c rosa::deluxe::DeluxeAgent. /// /// \see \c DeluxeAgentMasterInputHandlers /// /// \note No pre- and post-conditions are validated directly by these functions, /// they rather rely on \c rosa::deluxe::DeluxeAgent::saveMasterInput to do /// that. /// /// \param T the type of input to handle /// \param N name suffix for the function identifier #define DAMASTERHANDLERDEFN(T, N) \ void DAMASTERHANDLERNAME(N)(atoms::Master, id_t MasterId, token_size_t Pos, \ T Value) noexcept { \ saveMasterInput(MasterId, Pos, Value); \ } /// Convenience macro for \c DASLAVEHANDLERDEFN with identical arguments. /// /// \see \c DASLAVEHANDLERDEFN /// /// This macro can be used instead of \c DASLAVEHANDLERDEFN 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 DASLAVEHANDLERDEF(T) DASLAVEHANDLERDEFN(T, T) /// Convenience macro for \c DAMASTERHANDLERDEFN with identical arguments. /// /// \see \c DAMASTERHANDLERDEFN /// /// This macro can be used instead of \c DAMASTERHANDLERDEFN 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 DAMASTERHANDLERDEF(T) DAMASTERHANDLERDEFN(T, T) /// Results in a \c THISMEMBER reference to a member function defined by /// \c DASLAVEHANDLERDEFN. /// /// Used in the constructor of \c rosa::deluxe::DeluxeAgent to initialize super /// class \c rosa::Agent with member function defined by \c DASLAVEHANDLERDEFN. /// /// \see \c DASLAVEHANDLERDEFN, \c THISMEMBER /// /// \param N name suffix for the function identifier #define DASLAVEHANDLERREF(N) THISMEMBER(DASLAVEHANDLERNAME(N)) /// Results in a \c THISMEMBER reference to a member function defined by /// \c DAMASTERHANDLERDEFN. /// /// Used in the constructor of \c rosa::deluxe::DeluxeAgent to initialize super /// class \c rosa::Agent with member function defined by \c DAMASTERHANDLERDEFN. /// /// \see \c DAMASTERHANDLERDEFN, \c THISMEMBER /// /// \param N name suffix for the function identifier #define DAMASTERHANDLERREF(N) THISMEMBER(DAMASTERHANDLERNAME(N)) ///@} namespace rosa { namespace deluxe { /// Specialization of \c rosa::Agent for *agent* role of the *deluxe interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// /// \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. \c rosa::deluxe::DeluxeAgent::NumberOfMasterOutputs matches /// \c rosa::deluxe::DeluxeAgent::NumberOfInputs. All master-output-related /// container objects have a size matching \c /// rosa::deluxe::DeluxeAgent::NumberOfMasterOutputs. Types and type-related /// information of input and master-output values are consistent throughout all /// the input-related and master-output-related containers, respectively. The /// actual values in \c rosa::deluxe::DeluxeAgent::InputNextPos and \c /// rosa::deluxe::DeluxeAgent::MasterInputNextPos are valid with respect to the /// corresponding types. 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 \c rosa::deluxe::DeluxeExecutionPolicy that controls the execution of /// \c this object. std::unique_ptr ExecutionPolicy; public: /// The type of values produced by \p this object. /// /// That is the types of values \p this object sends to its *master* in a \c /// rosa::deluxe::DeluxeTUple. /// /// \see \c rosa::deluxe::DeluxeAgent::master const Token OutputType; /// Number of inputs processed by \p this object. const size_t NumberOfInputs; /// The type of values \p this object processes from its *master*. /// /// That is the types of values \p this object receives from its *master* in a /// \c rosa::deluxe::DeluxeTuple. /// /// \see \c rosa::deluxe::DeluxeAgent::master const Token MasterInputType; /// Number of outputs produces by \p this object for its *slaves*. /// /// \note This values is equal to \c /// rosa::deluxe::DeluxeAgent::NumberOfInputs. /// /// \see \c rosa::deluxe::DeluxeAgent::slave. const size_t NumberOfMasterOutputs; private: /// Types of input values produced by *slaves* of \p this object. /// /// \note The \c rosa::Token values stored correspond to \c /// rosa::deluxe::DeluxeTuple instances at each argument position. The \c /// rosa::TypeNumber values from the stored \c rosa::Token values match the /// corresponding values in \c rosa::deluxe::DeluxeAgent::InputValues in /// order. /// /// \note The position of a \c rosa::Token in the \c std::vector indicates /// which argument of \p this object's processing function it belongs to. See /// also \c rosa::deluxe::DeluxeAgent::DeluxeAgent. const std::vector InputTypes; /// Indicates which element of an input is expected from any particular /// *slave*. /// /// The *slave* is supposed to send one \c rosa::deluxe::DeluxeTuple value /// element by element in their order of definition. This member field tells /// the element at which position in the tuple should be received next from /// the *slave* at a given position. /// /// \p this object is supposed to be triggered only when input values has been /// received completely, that is all values in the field should hold the value /// `0`. /// /// \see \c rosa::deluxe::DeluxeAgent::handleTrigger /// \c rosa::deluxe::DeluxeAgent::saveInput std::vector InputNextPos; /// 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::DeluxeAgent. std::vector InputChanged; /// Tells at which position in \c rosa::deluxe::DeluxeAgent::InputValues the /// input from any particular *slave* starts. /// /// \note A value in the vector corresponds to the *slave* at the same /// position and it is the sum of the elements of input values from *slaves* /// at previous positions. /// /// \see \c rosa::deluxe::DeluxeAgent::saveInput const std::vector InputStorageOffsets; /// Stores the actual input values. /// /// \note The types of stored values match the corresponding /// \c rosa::TypeNumber values (in \c rosa::Token in order) in \c /// rosa::deluxe::DeluxeAgent::InputTypes. /// /// \note The position of a value in the \c rosa::AbstractTokenizedStorage /// indicates which element of the tuple of which argument of \p this object's /// processing function it is. See also \c /// rosa::deluxe::DeluxeAgent::DeluxeAgent. const std::unique_ptr InputValues; /// Indicates which element of the master-input is expected from the *master*. /// /// The *master* is supposed to send one \c rosa::deluxe::DeluxeTuple value /// element by element in their order of definition. This member field tells /// the element at which position should be received next. /// /// \p this object is supposed to be triggered only when a complete /// master-input has been received, that is the field should hold the value /// `0`. /// /// \see \c rosa::deluxe::DeluxeAgent::handleTrigger /// \c rosa::deluxe::DeluxeAgent::saveMasterInput token_size_t MasterInputNextPos; /// Indicates whether the input value from the *master* has been changed since /// the last trigger received from the system. /// /// The flag is reset to \c false upon handling a trigger and then set to \c /// true by \c rosa::deluxe::DeluxeAgent::saveMasterInput when storig a new /// input value in \c rosa::deluxe::DeluxeAgent::MasterInputValue. bool MasterInputChanged; /// Stores the actual input value from *master*. /// /// \note The type of the stored value matches the types indicated by \c /// rosa::deluxe::DeluxeAgent::MasterInputType. const std::unique_ptr MasterInputValue; /// Types of output values produced by \p this object for its *slaves*. /// /// That is the types of values \p this object sends to its *slaves* in a \c /// rosa::deluxe::DeluxeTuple. /// /// \note The position of a type in the \c std::vector indicates which /// *slave* of \p this object the type belongs to. See also /// \c rosa::deluxe::DeluxeAgent::DeluxeAgent. const std::vector MasterOutputTypes; /// 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 functions processing *slave* and *master* inputs and generating /// optional output to *master* and *slaves* are captured in a lambda /// expression that is in turn wrapped in a \c std::function object. The /// lambda expression calls the master-input processing function with the /// actual master-input data and sends its result -- if any -- to *slaves* by /// calling \c rosa::deluxe::DeluxeAgent::handleMasterOutputs; then calls the /// input processing function with the actual input data and sends its result /// -- if any -- to *master* by calling \c /// rosa::deluxe::DeluxeAgent::sendToMaster and *slaves* by calling \c /// rosa::deluxe::DeluxeAgent::handleMasterOutputs. Also, all the flags stored /// in \c rosa::deluxe::DeluxeAgent::InputChanged and \c /// rosa::deluxe::DeluxeAgent::MasterInputChanged are reset when the current /// values are processed. The function \c /// rosa::deluxe::DeluxeAgent::handleTrigger needs only to call the /// function object. /// /// \see \c /// rosa::deluxe::DeluxeAgent::triggerHandlerFromProcessingFunctions 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::DeluxeAgent. /// /// \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 the unique identifier of the *master* of \p this object, if any /// registered. /// /// \return the unique identifier of the *master* /// /// \pre A *master* is registered for \p this object: \code /// Master /// \endcode id_t masterId(void) const noexcept; /// Tells whether types stored in \c rosa::TypeList \p As match the input /// types of \p this object. /// /// \tparam As \c rosa::TypeList containing types to match against values in /// \c rosa::deluxe::DeluxeAgent::InputTypes /// /// \note Instatiation of the template fails if \p As is not \c /// rosa::TypeList. /// /// \return if types in \p As are instances of \c rosa::deluxe::DeluxeTuple /// and their types match \c rosa::Token values stored in \c /// rosa::deluxe::DeluxeAgent::InputTypes template bool inputTypesMatch(void) const noexcept; /// Tells whether types stored in \c rosa::TypeList \p Ts match the /// master-output types of \p this object. /// /// \tparam Ts \c rosa::TypeList containing types to match against values in /// \c rosa::deluxe::DeluxeAgent::MasterOutputTypes /// /// \note Instatiation of the template fails if \p As is not \c /// rosa::TypeList. /// /// \return if types in \p Ts match \c rosa::Token and in turn \c /// rosa::TypeNumber values stored in \c /// rosa::deluxe::DeluxeAgent::MasterOutputTypes template bool masterOutputTypesMatch(void) const noexcept; /// Gives the current input value for slave position \p Pos. /// /// \tparam Pos slave position to get input value for /// \tparam Ts types of elements of the input value /// \tparam S0 indices for accessing elements of the input value /// /// \note The arguments provide types and indices statically as template /// arguments \p Ts... \p S0..., respectively, so their actual values are /// ignored. /// /// \return current input value for slave position \p Pos /// /// \pre Statically, the provided indices \p S0... match the length of \p /// Ts...: \code /// sizeof...(Ts) == sizeof...(S0) /// \endcode Dynamically, \p Pos is a valid slave position and type arguments /// \p Ts... match the corresponding input value: \code /// Pos < NumberOfInputs && DeluxeTuple::TT == InputTypes[Pos] /// \endcode template DeluxeTuple prepareInputValueAtPos(TypeList, Seq) 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 Statically, all type arguments \p As... are instances of \c /// rosa::deluxe::DeluxeTuple and the provided indices \p S0... match the /// length of \p As...: \code /// TypeListAllDeluxeTuple>::Value && /// sizeof...(As) == sizeof...(S0) /// \endcode Dynamically, type arguments \p As... match the input types of \p /// this object: \code /// inputTypesMatch>() /// \endcode template std::tuple...> prepareCurrentInputs(Seq) const noexcept; /// Invokes a processing function matching the input, output, and /// master-output 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 Ts types of master-output values 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 std::tuple, Optional...> invokeWithTuple(std::function, Optional...>( std::pair...)> F, const std::tuple...> Args, Seq) noexcept; /// Handles a master-output value for a particular *slave* position. /// /// \p Value is a \c rosa::Optional resulted by a processing function and /// contains a master-output value for the *slave* at position \p Pos. The /// function takes the master-output value and sends its actual value, if any, /// to the corresponding *slave*. /// /// \note A master-output of type \c rosa::deluxe::EmptyDeluxeTuple indicates /// no actual output and hence no message is generated for a position whose /// corresponding master-output type is \c rosa::deluxe::EmptyDeluxeTuple. /// /// \note The function provides position-based implementation for \c /// rosa::deluxe::DeluxeAgent::handleMasterOutputs. /// /// \tparam Pos the position of the master-output to send \p Value for /// \tparam Ts types of elements in \p Value /// /// \param Value \c rosa::deluxe::DeluxeTuple resulted by the processing /// function for *slave* position \p Pos /// /// \pre \p Pos is a valid master-output position and \p Value matches the /// master-output type of \p this object at position \p Pos: \code /// Pos < NumberOfMasterOutputs && /// DeluxeTuple::TT == MasterOutputTypes[Pos] /// \endcode template void handleMasterOutputAtPos(const Optional> &Value) noexcept; /// Handles master-output values from \p Output. /// /// \p Output is a \c std::tuple resulted by a processing function and /// contains master-output values starting at position \p Offset. The function /// takes master-output values and sends each actual value to the /// corresponding *slave*. /// /// \tparam Offset index of the first master-output value in \p Output /// \tparam Ts output types stored in \p Output /// \tparam S0 indices starting with `0` for extracting master-output values /// from \p Output /// /// \note Instantiation fails if any of the type arguments \p Ts... starting /// at position \p Offset is not an instance of \c rosa::deluxe::DeluxeTuple /// or the number of types \p Ts... is not consistent with the other template /// arguments. /// /// \param Output \c std::tuple resulted by a processing function /// /// \pre Statically, type arguments \p Ts... starting at position \p Offset /// are instances of \c rosa::deluxe::DeluxeTuple and the number of types \p /// Ts... is consistent with the other template arguments: \code /// TypeListAllDeluxeTuple< /// typename TypeListDrop>::Type>::Value && /// sizeof...(Ts) == Offset + sizeof...(S0) /// \endcode Dynamically, \p Output matches the master-output types \p this /// object was created with and the provided sequence of indices \p S0... /// constitues a proper sequence for extracting all master-output values from /// \p Output: \code /// masterOutputTypesMatch>::Type>() && /// sizeof...(S0) == NumberOfMasterOutputs /// \endcode template void handleMasterOutputs(const std::tuple...> &Output, Seq) noexcept; /// Wraps processing functions 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 MTs types of elements of master-input processed by \p MF /// \tparam T type of output /// \tparam Ts types of master-output values /// \tparam As types of input values /// \tparam S0 indices for accessing master-input values /// /// \note Instantiation fails if any of the type arguments \p T, \p Ts..., /// and \p As... is not an instance of \c rosa::deluxe::DeluxeTuple. /// /// \param MF function processing master-input and generating output /// \param F function processing inputs and generating output /// /// \note The last argument provides indices statically as template /// arguments \p S0..., so its actual value is ignored. /// /// \note A master-input type of \c rosa::deluxe::EmptyDeluxeTuple indicates /// that \p this object does not receive master-input, \p MF is never called /// if \p MTs is empty. /// /// \return trigger handler function based on \p F and \p MF /// /// \pre Statically, type arguments \p T, \p Ts..., and \p As... are /// instances of \c rosa::deluxe::DeluxeTuple and the indices match /// master-input elements: \code /// TypeListAllDeluxeTuple>::Value && /// sizeof...(MTs) == sizeof...(S0) /// \endcode Dynamically, template arguments \p MTs..., \p T, \p Ts..., and /// \p As... match the corresponding types \p this object was created with: /// \code /// MasterInputType == DeluxeTuple::TT && OutputType == T::TT && /// inputTypesMatch>() && /// masterOutputTypesMatch>() /// \endcode template H triggerHandlerFromProcessingFunctions( std::function...>( std::pair, bool>)> &&MF, std::function< std::tuple, Optional...>(std::pair...)> &&F, Seq) noexcept; public: /// Creates a new instance. /// /// The constructor instantiates the base-class with functions to handle /// messages as defined for the *deluxe interface*. /// /// The function \p F generates a \c std::tuple of values: the first value is /// the output for the *master* and the rest is for the *slaves*. All output /// generated by the function is optional as an agent may decide not to output /// anything at some situation. /// /// \todo Enforce \p F and \p MF do not potentially throw exception. /// /// \tparam MT type of master-input handled by \p MF /// \tparam T type of output of \p F /// \tparam Ts type of master-output values of \p F and \p MF /// \tparam As types of input values of \p F /// /// \note Instantiation fails if any of the type arguments \p MT, \p T, \p /// Ts..., and \p As... is not an instance of \c rosa::deluxe::DeluxeTuple or /// any of \p T and \p As... is \c rosa::deluxe::EmptyDeluxeTuple or the /// number of inputs and master-outputs are not equal. /// /// \note If \p MT is \c rosa::deluxe::EmptyDeluxeTuple, the constructed /// object does not receive master-input. Similarly, if any of \p Ts... is \c /// rosa::deluxe::EmptyDeluxeTuple, the constructed object does not generated /// master-output for the corresponding *slave* position. /// /// \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 MF function to process master-input values and generate /// master-output with /// \param F function to process input values and generate output and /// master-output with /// /// \pre Statically, all the type arguments \p MT, \p T, \p Ts..., and \p /// As... are instances of \c rosa::deluxe::DeluxeTuple, with \p T and \p /// As... containing at least one element, and the number of input and /// master-output types are equal: \code /// TypeListAllDeluxeTuple::Value && /// T::Length > 0 && (true && ... && As::Length > 0) && /// sizeof...(Ts) == sizeof...(As) ///\endcode /// Dynamically, the instance is created as of kind \c /// rosa::deluxe::atoms::AgentKind: \code /// Kind == rosa::deluxe::atoms::AgentKind /// \endcode /// /// \see \c rosa::deluxe::DeluxeTuple template >::Value && (T::Length > 0) && (true && ... && (As::Length > 0)) && sizeof...(Ts) == sizeof...(As)>> DeluxeAgent( const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept; /// Destroys \p this object. ~DeluxeAgent(void) noexcept; /// Returns the current execution policy of \p this object. /// /// \see \c rosa::deluxe::DeluxeExecutionPolicy /// /// \note The returned reference is valid only as long as \c /// rosa::deluxe::DeluxeAgent::setExecutionPolicy() is not called and \p this /// object is not destroyed. /// /// \return \c rosa::deluxe::DeluxeAgent::ExecutionPolicy const DeluxeExecutionPolicy &executionPolicy(void) const noexcept; /// Sets the current execution policy of \p this object to \p EP. /// /// \see \c rosa::deluxe::DeluxeExecutionPolicy /// /// \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*. /// /// \note Any call to \c rosa::deluxe::DeluxeAgent::registerMaster should be /// paired with a corresponding call of \c /// rosa::deluxe::DeluxeAgent::registerSlave, which validates that /// input/output types of master and slave matches. /// /// \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 types of values consumed from the *slave* at a position. /// /// That is the type of values \p this object expect to be sent to it in a \c /// rosa::deluxe::DeluxeTuple by its *slave* registered at position \p Pos. /// /// \see \c rosa::deluxe::DeluxeAgent::slave /// /// \param Pos position of *slave* /// /// \return \c rosa::Token representing the types of values consumed from /// the *slave* at position \p Pos /// /// \pre \p Pos is a valid index of input: \code /// Pos < NumberOfInputs /// \endcode Token inputType(const size_t Pos) const noexcept; /// Tells the types of values produced for the *slave* at a position. /// /// That is the types of values \p this object potentially sends in a \c /// rosa::deluxe::DeluxeTuple to its *slave* registered at position \p Pos. /// /// \see \c rosa::deluxe::DeluxeAgent::slave /// /// \param Pos position of *slave* /// /// \return \c rosa::Token representing the types of values produced for /// the *slave* at position \p Pos /// /// \pre \p Pos is a valid index of input: \code /// Pos < NumberOfMasterOutputs /// \endcode Token masterOutputType(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. /// /// \note The type of master-input values processed by the referred *slave* is /// validated by matching its `MasterInputType` against the corresponding /// value in \c rosa::deluxe::DeluxeAgent::MasterOutputTypes. /// /// \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 and processes values of types /// matching the produced master-output type at position \p Pos: /// \code /// Pos < NumberOfInputs && /// (!Slave || /// (unwrapAgent(*Slave.)Kind == rosa::deluxe::atoms::SensorKind && /// static_cast(unwrapAgent(*Slave)).OutputType == /// InputTypes[Pos] && /// (emptyToken(MasterOutputTypes[Pos]) || /// static_cast(unwrapAgent(*Slave)).MasterInputType /// == MasterOutputTypes[Pos])) || /// (unwrapAgent(*Slave).Kind == rosa::deluxe::atoms::AgentKind && /// static_cast(unwrapAgent(*Slave)).OutputType == /// InputTypes[Pos] && /// (emptyToken(MasterOutputTypes[Pos]) || /// static_cast(unwrapAgent(*Slave)).MasterInputType == /// MasterOutputTypes[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. /// /// The elements from \p Value are sent one by one in separate messages to the /// *master*. /// /// \tparam Ts types of the elements in \p Value /// \tparam S0 indices for accessing elements of \p Value /// /// \param Value value to send /// /// \note The second argument provides indices statically as template /// arguments \p S0..., so its actual value is ignored. /// /// \pre Statically, the indices match the elements: \code /// sizeof...(Ts) == sizeof...(S0) /// \endcode Dynamically, \p Ts match \c /// rosa::deluxe::DeluxeiAgent::OutputType: \code /// OutputType == TypeToken::Value /// \endcode template void sendToMaster(const DeluxeTuple &Value, Seq) noexcept; /// Sends a value to a *slave* of \p this object at position \p Pos. /// /// \p Value is getting sent to \c rosa::deluxe::DeluxeAgent::Slaves[Pos] if /// it contains a valid handle. The function does nothing otherwise. /// /// The elements from \p Value are sent one by one in separate messages to the /// *slave*. /// /// \tparam Ts types of the elements in \p Value /// \tparam S0 indices for accessing elements of \p Value /// /// \param Pos the position of the *slave* to send \p Value to /// \param Value value to send /// /// \pre Statically, the indices match the elements: \code /// sizeof...(Ts) == sizeof...(S0) /// \endcode Dynamically, \p Pos is a valid *slave* position and \p Ts match /// \c rosa::deluxe::DeluxeiAgent::MasterOutputTypes[Pos]: \code /// Pos < NumberOfMasterOutputs && /// MasterOutputTypes[Pos] == TypeToken::Value /// \endcode template void sendToSlave(const size_t Pos, const DeluxeTuple &Value, Seq) 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. /// /// \pre Master-input and all input from *slaves* are supposed to be /// completely received upon triggering: \code /// MasterInputNextPos == 0 && /// std::all_of(InputNextPos.begin(), InputNextPos.end(), /// [](const token_size_t &I){return I == 0;}) /// \endcode void handleTrigger(atoms::Trigger) noexcept; /// Stores a new input value from a *slave*. /// /// The function stores \p Value at position \p Pos 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. The function also /// takes care of checking and updating \c /// rosa::deluxe::DeluxeSensor::MasterInputNextPos at the corresponding /// position: increments the value and resets it to `0` when the last element /// is received. /// /// \note Utilized by member functions of group \c DeluxeAgentInputHandlers. /// /// \tparam T type of input to store /// /// \param Id unique identifier of *slave* /// \param Pos position of the value in the \c rosa::deluxe::DeluxeTuple /// \param Value the input value to store /// /// \pre The *slave* with \p Id is registered, \p Pos is the expected /// position of input from the *slave*, and the input from it is expected to /// be of type \p T: \code /// SlaveIds.find(Id) != SlaveIds.end() && /// Pos == InputNextPos[SlaveIds.find(Id)->second] && /// typeAtPositionOfToken(InputTypes[SlaveIds.find(Id)->second], Pos) == /// TypeNumberOf::Value /// \endcode template void saveInput(id_t Id, token_size_t Pos, T Value) noexcept; /// Stores a new input value from the *master*. /// /// The function stores \p Value at position \p Pos in \c /// rosa::deluxe::DeluxeAgent::MasterInputValue and also sets the /// flag \c rosa::deluxe::DeluxeAgent::MasterInputChanged. The function also /// takes care of checking and updating \c /// rosa::deluxe::DeluxeAgent::MasterInputNextPos: increments its value and /// reset to `0` when the last element is received. /// /// \note Utilized by member functions of group \c /// DeluxeAgentMasterInputHandlers. /// /// \tparam T type of input to store /// /// \param Id unique identifier of the *master* /// \param Pos position of the value in the \c rosa::deluxe::DeluxeTuple /// \param Value the input value to store /// /// \pre The *master* with \p Id is registered, \p Pos is the expected /// position of master-input, and the input from the *master* at position \p /// Pos is expected to be of type \p T: \code /// Master && masterId() == Id && Pos == MasterInputNextPos && /// typeAtPositionOfToken(MasterInputType, Pos) == TypeNumberOf::Value /// \endcode template void saveMasterInput(id_t Id, token_size_t Pos, 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 to handle messages from its *slaves*. 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 /// DASLAVEHANDLERDEF. /// /// \note Keep these definitions in sync with \c rosa::BuiltinTypes. /// ///@{ DASLAVEHANDLERDEF(AtomValue) DASLAVEHANDLERDEF(int16_t) DASLAVEHANDLERDEF(int32_t) DASLAVEHANDLERDEF(int64_t) DASLAVEHANDLERDEF(int8_t) DASLAVEHANDLERDEFN(long double, long_double) DASLAVEHANDLERDEFN(std::string, std__string) DASLAVEHANDLERDEF(uint16_t) DASLAVEHANDLERDEF(uint32_t) DASLAVEHANDLERDEF(uint64_t) DASLAVEHANDLERDEF(uint8_t) DASLAVEHANDLERDEF(unit_t) DASLAVEHANDLERDEF(bool) DASLAVEHANDLERDEF(double) DASLAVEHANDLERDEF(float) /// @} /// \defgroup DeluxeAgentMasterInputHandlers Master-input handlers of /// rosa::deluxe::DeluxeAgent /// /// Definition of member functions handling messages from the *master* with /// different types of input /// /// A *slave* generally needs to be prepared to deal with values of any /// built-in type to handle messages from its *master*. Each type requires a /// separate message handler, which are implemented by these functions. The /// functions instantiate \c rosa::deluxe::DeluxeAgent::saveMasterInput 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 /// DAMASTERHANDLERDEF. /// /// \note Keep these definitions in sync with \c rosa::BuiltinTypes. /// ///@{ DAMASTERHANDLERDEF(AtomValue) DAMASTERHANDLERDEF(int16_t) DAMASTERHANDLERDEF(int32_t) DAMASTERHANDLERDEF(int64_t) DAMASTERHANDLERDEF(int8_t) DAMASTERHANDLERDEFN(long double, long_double) DAMASTERHANDLERDEFN(std::string, std__string) DAMASTERHANDLERDEF(uint16_t) DAMASTERHANDLERDEF(uint32_t) DAMASTERHANDLERDEF(uint64_t) DAMASTERHANDLERDEF(uint8_t) DAMASTERHANDLERDEF(unit_t) DAMASTERHANDLERDEF(bool) DAMASTERHANDLERDEF(double) DAMASTERHANDLERDEF(float) /// @} }; /// Anonymous namespace with implementation for \c /// rosa::deluxe::DeluxeAgent::DeluxeAgent, \c /// rosa::deluxe::DeluxeAgent::inputTypesMatch, and \c /// rosa::deluxe::DeluxeAgent::masterOutputTypesMatch, consider it private. namespace { /// Calculates storage offsets for values of \p Ts... stored in a \c /// rosa::TokenizedStorage. /// /// \note Utilized by \c rosa::deluxe::DeluxeAgnet::DeluxeAgent to initialize \c /// rosa::deluxe::DeluxeAgent::InputStorageOffsets. /// /// \tparam Ts types whose offsets to calculate /// \tparam S0 indices for referring to positions in \p Ts... /// /// \note Instantiation fails if any of the type arguments \p Ts... is not an /// instance of \c rosa::deluxe::DeluxeTuple. /// /// \note The only argument provides indices statically as template /// arguments \p S0..., so its actual value is ignored. /// /// \return \c std::vector containing the calculated offsets /// /// \pre Statically, all the type arguments \p Ts... are instances of \c /// rosa::deluxe::DeluxeTuple and the indices match the types: \code /// TypeListAllDeluxeTuple>::Value && /// sizeof...(Ts) == sizeof...(S0) /// \endcode template < typename... Ts, size_t... S0, typename = std::enable_if_t>::Value>> static std::vector storageOffsets(Seq) noexcept { STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent arguments"); std::vector Offsets(sizeof...(Ts)); // Do nothing for no types. if constexpr (sizeof...(Ts) != 0) { Offsets[0] = 0; // The offset of the very first value is always `0`. // Calculate further offsets... (((S0 != sizeof...(Ts) - 1) && (Offsets[S0 + 1] = Offsets[S0] + Ts::Length)), ...); } return Offsets; } /// Template \c struct whose specializations provide a recursive implementation /// for \c TypesMatchList. /// /// \tparam As types to match template struct TypesMatchImpl; /// Template specialization for the case, when at least one type is to /// be matched and that is an instance of \c rosa::deluxe::DeluxeTuple. /// /// \tparam Ts types of elements in the \c rosa::deluxe::DeluxeTuple to match /// \tparam As further types to match template struct TypesMatchImpl, As...> { /// Tells whether types \c rosa::deluxe::DeluxeTuple and \p As... match /// \c rosa::Token values stored in \p Tokens starting at position \p Pos. /// /// The function has got a recursive implementation: it matches the first /// type \c rosa::deluxe::DeluxeTuple against \c rosa::Token at /// position \p Pos of \p Tokens, then further types \p As... are matched /// recursively starting at position \c (Pos + 1). /// /// \param Tokens container of \c rosa::Token values to match types against /// \param Pos position in \p Tokens to start matching at /// /// \return if types \c rosa::deluxe::DeluxeTuple and \p As... match \c /// rosa::Token values stored in \p Tokens starting at position \p Pos static bool f(const std::vector &Tokens, size_t Pos) noexcept { return Pos < Tokens.size() && TypeToken::Value == Tokens[Pos] && TypesMatchImpl::f(Tokens, Pos + 1); } }; /// Template specialization for the case, when at least one type is to /// be matched and that is *not* an instance of \c rosa::deluxe::DeluxeTuple. /// /// \tparam T first type to match /// \tparam As further types to match template struct TypesMatchImpl { /// Tells whether types \p T and \p As... match \c rosa::Token values stored /// in \p Tokens starting at position \p Pos. /// /// This specialization is used only when \p T is not an instance of \c /// rosa::deluxe::DeluxeTuple, in which case the match is not successful. /// /// \note The function takes two parameters to match the general signature but /// the actual values are ignored. /// /// \return `false` static bool f(const std::vector &, size_t) noexcept { return false; } }; /// Template specialization for the terminal case, when no type remains to /// check. template <> struct TypesMatchImpl<> { /// Tells whether \p Pos is the number of values stored in \p Tokens. /// /// In this terminal case, there is no more types to match 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 Tokens. That is true if \p Pos points exactly to the end of \p Tokens. /// /// \param Tokens container of \c rosa::Token values to match types against /// \param Pos position in \p Tokens to start matching at /// /// \return if \p Pos is the number of values stored in \p Tokens static bool f(const std::vector &Tokens, size_t Pos) noexcept { return Pos == Tokens.size(); } }; /// Template \c struct that provides an implementation for \c /// rosa::deluxe::DeluxeAgent::inputTypesMatch and \c /// rosa::deluxe::DeluxeAgent::masterOutputTypesMatch. /// /// \note Match a list of types \p List against a \c std::vector of /// \c rosa::Token values, \c Tokens, like \code /// bool match = TypesMatchList::f(Tokens); /// \endcode /// If any type in \c rosa::TypeList \p Listis not an instance of \c /// rosa::deluxe::DeluxeTuple, the match gives a negative result. /// /// \tparam List \c rosa::TypeList that contains types to match template struct TypesMatchList; /// Template specialization implementing the feature. /// /// \tparam As types to match template struct TypesMatchList> { /// Tells whether types \p As... match \c rosa::Token values stored in \p /// Tokens. /// /// The function unwraps the types from \c rosa::TypeList and utilizes \c /// TypesMatchImpl to do the check. /// /// \param Tokens container of \c rosa::Token values to match types against /// /// \return if types \p As... match \c rosa::Token values stored in \p Tokens static bool f(const std::vector &Tokens) noexcept { return TypesMatchImpl::f(Tokens, 0); } }; } // End namespace template bool DeluxeAgent::inputTypesMatch(void) const noexcept { return TypesMatchList::f(InputTypes); } template bool DeluxeAgent::masterOutputTypesMatch(void) const noexcept { return TypesMatchList::f(MasterOutputTypes); } template DeluxeTuple DeluxeAgent::prepareInputValueAtPos(TypeList, Seq) const noexcept { using T = DeluxeTuple; STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent type arguments"); ASSERT(inv() && Pos < NumberOfInputs && T::TT == InputTypes[Pos]); const token_size_t StorageOffset = InputStorageOffsets[Pos]; // The below should hold because of the above, just leave it for sanity check. ASSERT((true && ... && (static_cast(static_cast(S0)) == S0))); // Get all elements of the tuple in a fold expression. return T(*static_cast(InputValues->pointerTo( static_cast(StorageOffset + S0)))...); } template std::tuple...> DeluxeAgent::prepareCurrentInputs(Seq) const noexcept { STATIC_ASSERT(TypeListAllDeluxeTuple>::Value, "not tuple types"); STATIC_ASSERT(sizeof...(As) == sizeof...(S0), "inconsistent type arguments"); ASSERT(inv() && inputTypesMatch>()); return std::make_tuple(std::make_pair( prepareInputValueAtPos(typename UnwrapDeluxeTuple::Type(), seq_t()), InputChanged[S0])...); } template std::tuple, Optional...> DeluxeAgent::invokeWithTuple( std::function< std::tuple, Optional...>(std::pair...)> F, const std::tuple...> Args, Seq) noexcept { ASSERT(sizeof...(As) == sizeof...(S0)); return F(std::get(Args)...); } template void DeluxeAgent::handleMasterOutputAtPos( const Optional> &Value) noexcept { using MOT = DeluxeTuple; ASSERT(inv() && Pos < NumberOfMasterOutputs && MOT::TT == MasterOutputTypes[Pos]); // Do not do anything for master-output of type \c // rosa::deluxe::EmptyDeluxeTuple and when \p Value is empty. - if constexpr (!std::is_same_v) { + if constexpr (!std::is_same::value) { if (Value) { sendToSlave(Pos, *Value, seq_t()); } } else { (void)Value; } ASSERT(inv()); } template void DeluxeAgent::handleMasterOutputs(const std::tuple...> &Output, Seq) noexcept { using MOTs = typename TypeListDrop>::Type; STATIC_ASSERT(TypeListAllDeluxeTuple::Value, "not tuple type arguments"); STATIC_ASSERT(sizeof...(Ts) == Offset + sizeof...(S0), "inconsistent arguments"); ASSERT(inv() && masterOutputTypesMatch() && sizeof...(S0) == NumberOfMasterOutputs); // Handle each master-output position in a fold expression. (handleMasterOutputAtPos(std::get(Output)), ...); ASSERT(inv()); } template DeluxeAgent::H DeluxeAgent::triggerHandlerFromProcessingFunctions( std::function< std::tuple...>(std::pair, bool>)> &&MF, std::function< std::tuple, Optional...>(std::pair...)> &&F, Seq) noexcept { using MT = DeluxeTuple; STATIC_ASSERT((TypeListAllDeluxeTuple>::Value), "not tuple type arguments"); STATIC_ASSERT(sizeof...(MTs) == sizeof...(S0), "inconsistent arguments"); ASSERT(MasterInputType == MT::TT && OutputType == T::TT && inputTypesMatch>() && masterOutputTypesMatch>()); return [ this, MF, F ]() noexcept { // \note These indices work for both inputs and master-outputs. using SlaveIndices = seq_t; // Handle master-input. // Do not do anything for master-input type \c // rosa::deluxe::EmptyDeluxeTuple. - if (!std::is_same_v) { + if (!std::is_same::value) { LOG_TRACE_STREAM << "DeluxeAgent " << FullName << " handles master-input." << std::endl; // The assert must hold if \p this object was successfuuly constructed. ASSERT((true && ... && (static_cast(static_cast(S0)) == S0))); const auto MasterInputArg = std::make_pair( // Get all elements of the tuple in a fold expression. MT(*static_cast( MasterInputValue->pointerTo(static_cast(S0)))...), MasterInputChanged); MasterInputChanged = false; const std::tuple...> MasterOutput = MF(MasterInputArg); handleMasterOutputs<0>(MasterOutput, SlaveIndices()); } // Handle inputs. // Call the processing function only if \p ExecutionPolicy allows. if (ExecutionPolicy->shouldProcess(InputChanged)) { LOG_TRACE_STREAM << "DeluxeAgent " << FullName << " handles input." << std::endl; const auto InputArgs = prepareCurrentInputs(SlaveIndices()); std::fill(InputChanged.begin(), InputChanged.end(), false); const std::tuple, Optional...> Output = invokeWithTuple(F, InputArgs, SlaveIndices()); const auto OutputToMaster = std::get<0>(Output); if (OutputToMaster) { sendToMaster(*OutputToMaster, seq_t()); } handleMasterOutputs<1>(Output, SlaveIndices()); } else { LOG_TRACE_STREAM << "DeluxeAgent " << Name << " skips input." << std::endl; } }; } template DeluxeAgent::DeluxeAgent( const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger), DASLAVEHANDLERREF(AtomValue), DASLAVEHANDLERREF(int16_t), DASLAVEHANDLERREF(int32_t), DASLAVEHANDLERREF(int64_t), DASLAVEHANDLERREF(int8_t), DASLAVEHANDLERREF(long_double), DASLAVEHANDLERREF(std__string), DASLAVEHANDLERREF(uint16_t), DASLAVEHANDLERREF(uint32_t), DASLAVEHANDLERREF(uint64_t), DASLAVEHANDLERREF(uint8_t), DASLAVEHANDLERREF(unit_t), DASLAVEHANDLERREF(bool), DASLAVEHANDLERREF(double), DASLAVEHANDLERREF(float), DAMASTERHANDLERREF(AtomValue), DAMASTERHANDLERREF(int16_t), DAMASTERHANDLERREF(int32_t), DAMASTERHANDLERREF(int64_t), DAMASTERHANDLERREF(int8_t), DAMASTERHANDLERREF(long_double), DAMASTERHANDLERREF(std__string), DAMASTERHANDLERREF(uint16_t), DAMASTERHANDLERREF(uint32_t), DAMASTERHANDLERREF(uint64_t), DAMASTERHANDLERREF(uint8_t), DAMASTERHANDLERREF(unit_t), DAMASTERHANDLERREF(bool), DAMASTERHANDLERREF(double), DAMASTERHANDLERREF(float)), ExecutionPolicy(DeluxeExecutionPolicy::decimation(1)), OutputType(T::TT), NumberOfInputs(sizeof...(As)), MasterInputType(MT::TT), NumberOfMasterOutputs(NumberOfInputs), InputTypes({As::TT...}), InputNextPos(NumberOfInputs, 0), InputChanged(NumberOfInputs, false), InputStorageOffsets(storageOffsets(seq_t())), InputValues(new typename TokenizedStorageForTypeList< typename TypeListUnwrapDeluxeTuple>::Type>:: Type()), MasterInputNextPos(0), MasterInputChanged(false), MasterInputValue(new typename TokenizedStorageForTypeList< typename UnwrapDeluxeTuple::Type>::Type()), MasterOutputTypes({Ts::TT...}), FP(triggerHandlerFromProcessingFunctions(std::move(MF), std::move(F), seq_t())), Slaves(NumberOfInputs) { ASSERT(Kind == atoms::AgentKind); LOG_TRACE_STREAM << "DeluxeAgent " << FullName << " is created." << std::endl; ASSERT(inv()); } template void DeluxeAgent::sendToMaster(const DeluxeTuple &Value, Seq) noexcept { STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent arguments"); ASSERT(inv() && OutputType == TypeToken::Value); // The assert must hold if \p this object was successfuuly constructed. ASSERT((true && ... && (static_cast(static_cast(S0)) == S0))); // Create a static constant array for these indices to be available as lvalue // references when creating messages below. \c S0... when used directly in a // fold expression is a temporary value, which would result in \c // rosa::Message instances being created with rvalue references. Further, all // other values would to copied into a temporary variable for making them /// available as rvalue references (they are constant lvalue references here). static constexpr std::array Indices{{S0...}}; LOG_TRACE_STREAM << "DeluxeAgent " << FullName << "(" << Id << ") sends to master (" << static_cast(Master && *Master) << "): " << Value << " (" << sizeof...(S0) << ")" << std::endl; // There is a handle and the referred *master* is in a valid state. if (Master && *Master) { // Handle each element of the tuple in a fold expression. (Master->sendMessage(Message::create(atoms::Slave::Value, Id, Indices[S0], std::get(Value))), ...); } ASSERT(inv()); } template void DeluxeAgent::sendToSlave(const size_t Pos, const DeluxeTuple &Value, Seq) noexcept { STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent arguments"); ASSERT(inv() && Pos < NumberOfMasterOutputs && MasterOutputTypes[Pos] == TypeToken::Value); // The assert must hold if \p this object was successfuuly constructed. ASSERT((true && ... && (static_cast(static_cast(S0)) == S0))); // Create a static constant array for these indices to be available as lvalue // references when creating messages below. \c S0... when used directly in a // fold expression is a temporary value, which would result in \c // rosa::Message instances being created with rvalue references. Further, all // other values would to copied into a temporary variable for making them /// available as rvalue references (they are constant lvalue references here). static constexpr std::array Indices{{S0...}}; // There is a handle and the referred *slave* is in a valid state. auto Slave = Slaves[Pos]; LOG_TRACE_STREAM << "DeluxeAgent " << FullName << "(" << Id << ") sends to slave (" << static_cast(Slave && *Slave) << ") at position " << Pos << ": " << Value << " (" << sizeof...(S0) << ")" << std::endl; if (Slave && *Slave) { // Handle each element of the tuple in a fold expression. (Slave->sendMessage(Message::create(atoms::Master::Value, Id, Indices[S0], std::get(Value))), ...); } } template void DeluxeAgent::saveInput(id_t Id, token_size_t Pos, T Value) noexcept { ASSERT(inv() && SlaveIds.find(Id) != SlaveIds.end() && Pos == InputNextPos[SlaveIds.find(Id)->second] && typeAtPositionOfToken(InputTypes[SlaveIds.find(Id)->second], Pos) == TypeNumberOf::Value); size_t SlavePos = SlaveIds.at(Id); LOG_TRACE_STREAM << "DeluxeAgent " << FullName << "(" << Id << ") saves value from slave at position " << SlavePos << ": (" << static_cast(Pos) << ") " << Value << std::endl; // Save value. size_t StoragePos = (size_t)InputStorageOffsets[SlavePos] + Pos; // This assert must hold if \p this object was successfully constructed. ASSERT(static_cast(static_cast(StoragePos)) == StoragePos); *static_cast( InputValues->pointerTo(static_cast(StoragePos))) = Value; // Update position of next value. if (++InputNextPos[SlavePos] == lengthOfToken(InputTypes[SlavePos])) { InputNextPos[SlavePos] = 0; } // Set flag. InputChanged[SlavePos] = true; ASSERT(inv()); } template void DeluxeAgent::saveMasterInput(id_t Id, token_size_t Pos, T Value) noexcept { ASSERT(inv() && Master && masterId() == Id && Pos == MasterInputNextPos && typeAtPositionOfToken(MasterInputType, Pos) == TypeNumberOf::Value); LOG_TRACE_STREAM << "DeluxeAgent " << FullName << "(" << Id << ") saves value from master: (" << static_cast(Pos) << ") " << Value << std::endl; // Save value. *static_cast(MasterInputValue->pointerTo(Pos)) = Value; // Update position of next value. if (++MasterInputNextPos == lengthOfToken(MasterInputType)) { MasterInputNextPos = 0; } // Set flag. MasterInputChanged = true; ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa #undef DASLAVEHANDLEREF #undef DAMASTERHANDLEREF #undef DASLAVEHANDLEDEF #undef DAMASTERHANDLEDEF #undef DASLAVEHANDLEDEFN #undef DAMASTERHANDLEDEFN #undef DASLAVEHANDLENAME #undef DAMASTERHANDLENAME #endif // ROSA_DELUXE_DELUXEAGENT_HPP diff --git a/include/rosa/deluxe/DeluxeContext.hpp b/include/rosa/deluxe/DeluxeContext.hpp index 259b34c..a54a2da 100644 --- a/include/rosa/deluxe/DeluxeContext.hpp +++ b/include/rosa/deluxe/DeluxeContext.hpp @@ -1,929 +1,932 @@ //===-- rosa/deluxe/DeluxeContext.hpp ---------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeContext.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Public interface for the *deluxe interface* for working with agent /// systems. /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXECONTEXT_HPP #define ROSA_DELUXE_DELUXECONTEXT_HPP #include "rosa/deluxe/DeluxeSystem.hpp" #include "rosa/support/types.hpp" #include #include #include /// Local helper macro to log and return a /// \c rosa::deluxe::DeluxeContext::ErrorCode value. /// /// Creates a debug message with the stringified value and returns the value. /// /// \param Err \c rosa::deluxe::DeluxeContext::ErrorCode value to log and /// return #define DCRETERROR(Err) \ { \ LOG_DEBUG(#Err); \ return Err; \ } namespace rosa { namespace deluxe { /// Defines the *deluxe interface*. /// /// \todo The classes \c rosa::deluxe::DeluxeSensor and \c /// rosa::deluxe::DeluxeAgent share some common features in relation to their /// *slave* role in the *deluxe interface*. But their definitions are completely /// independent. It could be investigated how to lift their common parts into a /// new *deluxe slave* class, which would serve as base for both, to avoid code /// duplication. class DeluxeContext { /// A system owned by \p this object. /// /// \note The reference is kept in a \c std::shared_ptr because of the member /// function \c rosa::deluxe::DeluxeContext::getSystem. std::shared_ptr System; /// References to all *sensors* and *agents* created by \p this object. std::set DeluxeUnits; public: /// Errors that may be resulted by some of the member functions of the class. enum struct ErrorCode { NoError, TypeMismatch, NotSensor, NotAgent, NotUnit, WrongPosition, AlreadyHasSlave, AlreadyHasMaster, AlreadyHasValueStream, UnsuitableExecutionPolicy }; /// Returns a new instance of \c rosa::deluxe::DeluxeContext. /// /// \param Name name of the underlying \c rosa::DeluxeSystem /// /// \return \c std::unique_ptr for the new instance of /// \c rosa::deluxe::DeluxeContext with a new, empty \c rosa::DeluxeSystem static std::unique_ptr create(const std::string &Name) noexcept; private: /// Creates a new instance. /// /// \note Private constructor restricts instantiation to member functions of /// the class. /// /// \param Name name of the underlying \c rosa::MessagingSystem DeluxeContext(const std::string &Name) noexcept; public: /// Destroys \p this object. ~DeluxeContext(void) noexcept; /// Returns a reference for the underlying \c rosa::MessagingSystem. /// /// \note One cannot do much with a \c rosa::MessagingSystem currently, this /// is for future use. /// /// \return reference for the underlying \c rosa::MessagingSystem. std::weak_ptr getSystem(void) const noexcept; private: /// Creates a new *sensor* in the context of \p this object. /// /// The new *sensor* handles master-input by \p MF. /// /// \tparam MT type of master-input the new *sensor* handles /// \tparam T type of data the new *sensor* operates on /// /// \note Instantiation fails if any of the type arguments \p MT and \p T /// is not an instance of \c rosa::deluxe::DeluxeTuple or \p T is \c /// rosa::deluxe::EmptyDeluxeTuple. /// /// \param Name name of the new *sensor* /// \param MF function for the new *sensors* to process master-input /// values with /// \param F function for the new *sensor* to generate the next value with /// during normal operation /// /// \note \p F is not used during simulation, in which case /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to /// register an alternative simulation data source with \c /// rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may /// safely keep relying on the default value of \p F as long as only /// simulation of the system is to be done. /// /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor. /// /// \return \c rosa::AgentHandle for the new *sensor* template >::Value && - !std::is_same_v>> + !std::is_same::value>> AgentHandle createSensorImpl(const std::string &Name, std::function)> &&MF, std::function &&F) noexcept; public: /// Creates a new *sensor* in the context of \p this object. /// /// The new *sensor* does not receive master-input. /// /// \tparam T type of data the new *sensor* operates on /// /// \note Instantiation fails if type argument \p T is neither a built-in type /// nor an instance of \c rosa::deluxe::DeluxeTuple with at least one element. /// /// \param Name name of the new *sensor* /// \param F function for the new *sensor* to generate the next value with /// during normal operation /// /// \note \p F is not used during simulation, in which case /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to register /// an alternative simulation data source with /// \c rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may /// safely keep relying on the default value of \p F as long as only /// simulation of the system is to be done. /// /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor. /// /// \return \c rosa::AgentHandle for the new *sensor* template ::Value || (IsDeluxeTuple::Value && - !std::is_same_v)>> + !std::is_same::value)>> AgentHandle createSensor( const std::string &Name, std::function &&F = [](void) { return T(); }) noexcept; /// Creates a new *sensor* in the context of \p this object. /// /// The new *sensor* handles master-input by \p MF. /// /// \tparam MT type of master-input the new *sensor* handles /// \tparam T type of data the new *sensor* operates on /// /// \note The type arguments \p MT and \p T must be either all built-in types /// or all instances of \c rosa::deluxe::DeluxeTuple. Moreover, \p T cannot be /// \c rosa::deluxe::EmptyDeluxeTuple. Instantiation fails if these conditions /// do not hold. /// /// \param Name name of the new *sensor* /// \param MF function for the new *sensors* to process master-input /// values with \param F function for the new *sensor* to generate /// the next value with during normal operation /// /// \note \p F is not used during simulation, in which case /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to /// register an alternative simulation data source with \c /// rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may /// safely keep relying on the default value of \p F as long as only /// simulation of the system is to be done. /// /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor. /// /// \return \c rosa::AgentHandle for the new *sensor* template , BuiltinTypes>::Value || (TypeListAllDeluxeTuple>::Value && - !std::is_same_v)>> + !std::is_same::value)>> AgentHandle createSensor( const std::string &Name, std::function)> &&MF, std::function &&F = [](void) { return T(); }) noexcept; private: /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* receives master-input by \p MF and produces /// master-output. /// /// \tparam MT type of master-input the new *agent* handles /// \tparam T type of data the new *agent* outputs /// \tparam Ts types of master-output the new *agent* produces /// \tparam As types of inputs the new *agent* takes /// /// \note Instantiation fails if any of the type arguments \p MT, \p T, \p /// Ts..., and \p As... is not an instance of \c rosa::deluxe::DeluxeTuple or /// any of \p T and \p As... is \c rosa::deluxe::EmptyDeluxeTuple. /// /// \param Name name of the new *agent* /// \param MF function for the new *agent* to process master-input /// values with \param F function for the new *agent* to process /// input values and generate output with /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* template >::Value && - !std::is_same_v && - (true && ... && (!std::is_same_v))>> + !std::is_same::value && + (true && ... && (!std::is_same::value))>> AgentHandle createAgentImpl( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept; public: /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* neither receives master-input nor produces /// master-output. /// /// \tparam T type of data the new *agent* outputs /// \tparam As types of inputs the new *agent* takes /// /// \note The type arguments \p T and \p As... must be either all built-in /// types or all instances of \c rosa::deluxe::DeluxeTuple. Moreover, none of /// them can be \c rosa::deluxe::EmptyDeluxeTuple. Instantiation fails if /// these conditions do not hold. /// /// \param Name name of the new *agent* /// \param F function for the new *agent* to process input values and /// generate output with /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* - template , BuiltinTypes>::Value || - (TypeListAllDeluxeTuple>::Value && - !std::is_same_v && - (true && ... && (!std::is_same_v)))>> + template < + typename T, typename... As, + typename = std::enable_if_t< + TypeListSubsetOf, BuiltinTypes>::Value || + (TypeListAllDeluxeTuple>::Value && + !std::is_same::value && + (true && ... && (!std::is_same::value)))>> AgentHandle createAgent(const std::string &Name, std::function(std::pair...)> &&F) noexcept; /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* receives master-input by \p MF but does not /// produce master-output. /// /// \tparam MT type of master-input the new *agent* handles /// \tparam T type of data the new *agent* outputs /// \tparam As types of inputs the new *agent* takes /// /// \note The type arguments \p MT, \p T, and \p As... must be either all /// built-in types or all instances of \c rosa::deluxe::DeluxeTuple. Moreover, /// none of \p T and \p As... can be \c rosa::deluxe::EmptyDeluxeTuple. /// Instantiation fails if these conditions do not hold. /// /// \param Name name of the new *agent* /// \param MF function for the new *agent* to process master-input /// values with /// \param F function for the new *agent* to process input values and /// generate output with /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* - template , BuiltinTypes>::Value || - (TypeListAllDeluxeTuple>::Value && - !std::is_same_v && - (true && ... && (!std::is_same_v)))>> + template < + typename MT, typename T, typename... As, + typename = std::enable_if_t< + TypeListSubsetOf, BuiltinTypes>::Value || + (TypeListAllDeluxeTuple>::Value && + !std::is_same::value && + (true && ... && (!std::is_same::value)))>> AgentHandle createAgent(const std::string &Name, std::function)> &&MF, std::function(std::pair...)> &&F) noexcept; /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* does not receive master-input but produces /// master-output. /// /// \tparam T type of data the new *agent* outputs /// \tparam Ts types of master-output the new *agent* produces /// \tparam As types of inputs the new *agent* takes /// /// \note The type arguments \p T, \p Ts, and \p As... must be either all /// built-in types or all instances of \c rosa::deluxe::DeluxeTuple. Moreover, /// none of \p T and \p As... can be \c rosa::deluxe::EmptyDeluxeTuple. /// Instantiation fails if these conditions do not hold. /// /// \param Name name of the new *agent* /// \param F function for the new *agent* to process input values and /// generate output with /// /// \note \p F does not produce master-output for a given position if the /// corresponding type is \c rosa::deluxe::EmptyDeluxeTuple. It is not /// possible to disable master-output at any position by using built-in types. /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* template < typename T, typename... Ts, typename... As, typename = std::enable_if_t< TypeListSubsetOf, BuiltinTypes>::Value || (TypeListAllDeluxeTuple>::Value && - !std::is_same_v && - (true && ... && (!std::is_same_v)))>> + !std::is_same::value && + (true && ... && (!std::is_same::value)))>> AgentHandle createAgent(const std::string &Name, std::function, Optional...>( std::pair...)> &&F) noexcept; /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* receives master-input by \p MF and produces /// master-output. /// /// \tparam MT type of master-input the new *agent* handles /// \tparam T type of data the new *agent* outputs /// \tparam Ts types of master-output the new *agent* produces /// \tparam As types of inputs the new *agent* takes /// /// \note The type arguments \p MT, \p T, \p Ts, and \p As... must be either /// all built-in types or all instances of \c rosa::deluxe::DeluxeTuple. /// Moreover, none of \p T and \p As... can be \c /// rosa::deluxe::EmptyDeluxeTuple. Instantiation fails if these conditions /// do not hold. /// /// \param Name name of the new *agent* /// \param MF function for the new *agent* to process master-input /// values with /// \param F function for the new *agent* to process input values and /// generate output with /// /// \note \p F does not produce master-output for a given position if the /// corresponding type is \c rosa::deluxe::EmptyDeluxeTuple. It is not /// possible to disable master-output at any position by using built-in types. /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* - template , - BuiltinTypes>::Value || - (TypeListAllDeluxeTuple>::Value && - !std::is_same_v && - (true && ... && (!std::is_same_v)))>> + template < + typename MT, typename T, typename... Ts, typename... As, + typename = std::enable_if_t< + TypeListSubsetOf, + BuiltinTypes>::Value || + (TypeListAllDeluxeTuple>::Value && + !std::is_same::value && + (true && ... && (!std::is_same::value)))>> AgentHandle createAgent( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept; /// Returns the current execution policy of the referred \p Unit /// /// \see \c rosa::deluxe::DeluxeExecutionPolicy /// /// \note The referred \p Unit is either *sensor* or *agent*. /// /// \note The returned reference is valid only as long as \c /// rosa::deluxe::DeluxeContext::setExecutionPolicy() is not called with the /// *unit* referred by \p Unit and the *unit* is not destroyed. /// /// \param Unit the *unit* whose execution policy is to be obtained /// /// \return the \c rosa::deluxe::DeluxeExecutionPolicy from \p Unit if \p Unit /// is valid Optional getExecutionPolicy(AgentHandle Unit) const noexcept; /// Sets the current execution policy of the referred \p Unit to \p /// ExecutionPolicy. /// /// \see \c rosa::deluxe::DeluxeExecutionPolicy /// /// \note The referred \p Unit is either *sensor* or *agent*. /// /// \param Unit the *unit* whose execution policy is to be set /// \param ExecutionPolicy the new execution policy for \p Unit /// /// \return how successful setting \p ExecutionPolicy for \p Unit was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotUnit` | Referred \p Unit is not valid /// `UnsuitableExecutionPolicy` | \p ExecutionPolicy cannot handle \p Unit ErrorCode setExecutionPolicy( AgentHandle Unit, std::unique_ptr &&ExecutionPolicy) noexcept; /// Connects a *sensor* to an *agent* in the context of \p this object. /// /// \param Agent the *agent* to connect to /// \param Pos the index of slot of \p Agent to connect \p Sensor to /// \param Sensor the *sensor* to connect /// \param Description optional textual description of the connection /// /// \return how successfull connecting \p Sensor to \p Agent at slot /// index \p Pos was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotAgent` | Referred \p Agent is not \c rosa::deluxe::DeluxeAgent /// `NotSensor` | Referred \p Sensor is not \c rosa::deluxe::DeluxeSensor /// `WrongPosition` | \p Pos is not a valid input position of \p Agent /// `TypeMismatch` | Expected input type at position \p Pos of \p Agent is other thanthe output type of \p Sensor or expected master-input of \p Sensor is other than master-output at position \p Pos of \p Agent if any /// `AlreadyHasSlave` | \p Agent at position \p Pos already has a *slave* registered /// `AlreadyHasMaster` | \p Sensor already has a *master* registered ErrorCode connectSensor(AgentHandle Agent, const size_t Pos, AgentHandle Sensor, const std::string &Description = "") noexcept; /// Connectes two *agents* in the context of \p this object. /// /// \param Master the *agent* to connect to /// \param Pos the index of slot of \p Master to connect \p Slave to /// \param Slave the *agent* to connect /// \param Description optional textual description of the connection /// /// \return how succesfull connecting \p Slave to \p Master at slot /// index \p Pos was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotAgent` | Referred \p Master or \p Slave is not \c rosa::deluxe::DeluxeAgent /// `WrongPosition` | \p Pos is not a valid input position of \p Master /// `TypeMismatch` | Expected input type at position \p Pos of \p Master is other than the output type of \p Slave or expected master-input of \p Slave is other than master-output at position \p Pos of \p Master if any /// `AlreadyHasSlave` | \p Master at position \p Pos already has a *slave* registered /// `AlreadyHasMaster` | \p Slave already has a *master* registered ErrorCode connectAgents(AgentHandle Master, const size_t Pos, AgentHandle Slave, const std::string &Description = "") noexcept; /// Initializes \c this object and others managed by \p this object /// for setting up and performing simulation. /// /// \see \c rosa::deluxe::DeluxeContext::registerSensorValues, /// \c rosa::deluxe::DeluxeContext::simulate /// /// Need to clear simulation data sources from all the *sensors*. void initializeSimulation(void) noexcept; public: /// Registers a stream providing values for a *sensor* during /// simulation. /// /// \tparam Iterator type of iterator providing values for \p Sensor /// \tparam T type of values \p Sensor is operating on, always use /// default! /// /// \note Instantiation fails if type argument \p T is neither a built-in type /// nor an instance of \c rosa::deluxe::DeluxeTuple with at least one element. /// /// \param Sensor the *sensor* to register values for /// \param Start provides values for \p Sensor /// \param End denotes the end of stream of values /// \param Default value to be used when input stream is depleted /// during simulation /// /// \return how successful registering \p Source for \p Sensor /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `TypeMismatch` | \p Sensor generates values of a type other than /// \p T `NotSensor` | Referred \p Sensor is not \c /// rosa::deluxe::DeluxeSensor `AlreadyHasValueStream` | \p Sensor already has /// simulation data source set template < typename Iterator, typename T = typename Iterator::value_type, typename = std::enable_if_t::Value || (IsDeluxeTuple::Value && - !std::is_same_v)>> + !std::is_same::value)>> ErrorCode registerSensorValues(AgentHandle Sensor, Iterator &&Start, const Iterator &End, T Default = {}) noexcept; /// Performs the system contained by \p this object. /// /// The function performs \p NumCycles cycle of simulation. In each /// cycle, all the *agents* and *sensors* registered in \c /// rosa::deluxe::DeluxeContext::DeluxeUnits are trigged for /// execution. /// /// \param NumCycles number of cycles to perform /// /// \pre All the *sensors* in the system contained by \p this object /// generate their output from simulation data sources. void simulate(const size_t NumCycles) const noexcept; }; /// Anonymous namespace with helper features for implementing /// \c rosa::deluxe::DeluxeContext, consider it private. namespace { /// Maps any type \p T to \c rosa::deluxe::EmptyDeluxeTuple. template struct MapToEmptyDeluxeTuple { using Type = EmptyDeluxeTuple; }; /// Convenience template alias for \c MapToEmptyDeluxeTuple. template using empty_deluxe_t = typename MapToEmptyDeluxeTuple::Type; /// Converts a \c std::tuple of \c rosa::Optional built-in types into a /// corresponding \c std::tuple of \c rosa::Optional with each actual value /// wrapped in \c rosa::deluxe::DeluxeTuple. /// /// \tparam Ts types of the values /// \tparam S0 indices for accessing values in \p Values /// /// \param Values the \c std::tuple of \c rosa::Optional with built-in values /// /// \note The second argument provides indices statically as template arguments /// \p S0..., so its actual value is ignored. /// /// \return a \c std::tuple of \c rosa::Optional corresponding to \p Values /// with each actual value wrapped in \c rosa::deluxe::DeluxeTuple /// /// \pre Statically, all type arguments \p Ts... are built-in types and the /// provided indices \p S0... match the length of \p Ts...: \code /// TypeListSubsetOf, BuiltinTypes>::Value && /// sizeof...(Ts) == sizeof...(S0) /// \endcode template std::tuple>...> wrapBuiltinInDeluxeTuple(const std::tuple...> &Values, Seq) noexcept { STATIC_ASSERT((TypeListSubsetOf, BuiltinTypes>::Value), "not built-in types"); STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent type arguments"); return std::make_tuple(std::get(Values) ? Optional>( make_deluxe_tuple(*std::get(Values))) : Optional>()...); } } // End namespace template AgentHandle DeluxeContext::createSensorImpl(const std::string &Name, std::function)> &&MF, std::function &&F) noexcept { AgentHandle H = System->createSensor(Name, std::move(MF), std::move(F)); DeluxeUnits.emplace(H); return H; } template AgentHandle DeluxeContext::createSensor(const std::string &Name, std::function &&F) noexcept { auto EmptyMF = std::function)>( [](std::pair) {}); if constexpr (TypeListContains::Value) { using OutputType = DeluxeTuple; return createSensorImpl( Name, std::move(EmptyMF), std::function( [F{std::move(F)}](void) { return OutputType(F()); })); } else if constexpr (IsDeluxeTuple::Value && - !std::is_same_v) { + !std::is_same::value) { return createSensorImpl(Name, std::move(EmptyMF), std::move(F)); } else { ASSERT(false && "Unexpected type argument"); } } template AgentHandle DeluxeContext::createSensor(const std::string &Name, std::function)> &&MF, std::function &&F) noexcept { if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using MasterInputType = DeluxeTuple; using OutputType = DeluxeTuple; return createSensorImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { MF({std::get<0>(Arg.first), Arg.second}); }), std::function( [F{std::move(F)}](void) { return OutputType(F()); })); } else if constexpr (TypeListAllDeluxeTuple>::Value && - !std::is_same_v) { + !std::is_same::value) { return createSensorImpl(Name, std::move(MF), std::move(F)); } else { ASSERT(false && "Unexpected type arguments"); } } template AgentHandle DeluxeContext::createAgentImpl( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept { AgentHandle H = System->createAgent(Name, std::move(MF), std::move(F)); DeluxeUnits.emplace(H); return H; } template AgentHandle DeluxeContext::createAgent( const std::string &Name, std::function(std::pair...)> &&F) noexcept { using NoMasterOutputType = std::tuple>...>; auto EmptyMF = std::function)>( [](std::pair) { return NoMasterOutputType(); }); if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using OutputType = DeluxeTuple; return createAgentImpl( Name, std::move(EmptyMF), std::function< std::tuple, Optional>...>( std::pair, bool>...)>( [F{std::move(F)}](std::pair, bool>... Args) { const auto Result = F({std::get<0>(Args.first), Args.second}...); return std::tuple_cat( wrapBuiltinInDeluxeTuple(std::tuple(Result), seq_t<1>()), NoMasterOutputType()); })); } else if constexpr (TypeListAllDeluxeTuple>::Value && - !std::is_same_v && + !std::is_same::value && (true && ... && - (!std::is_same_v))) { + (!std::is_same::value))) { return createAgentImpl( Name, std::move(EmptyMF), std::function, Optional>...>( std::pair...)>( [F{std::move(F)}](std::pair... Args) { const auto Result = F(Args...); return std::tuple_cat(std::tuple(Result), NoMasterOutputType()); })); } else { ASSERT(false && "Unexpected type arguments"); } } template AgentHandle DeluxeContext::createAgent( const std::string &Name, std::function)> &&MF, std::function(std::pair...)> &&F) noexcept { using NoMasterOutputType = std::tuple>...>; if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using MasterInputType = DeluxeTuple; using OutputType = DeluxeTuple; return createAgentImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { MF({std::get<0>(Arg.first), Arg.second}); return NoMasterOutputType(); }), std::function< std::tuple, Optional>...>( std::pair, bool>...)>( [F{std::move(F)}](std::pair, bool>... Args) { const auto Result = F({std::get<0>(Args.first), Args.second}...); return std::tuple_cat( wrapBuiltinInDeluxeTuple(std::tuple(Result), seq_t<1>()), NoMasterOutputType()); })); } else if constexpr (TypeListAllDeluxeTuple>::Value && - !std::is_same_v && + !std::is_same::value && (true && ... && - (!std::is_same_v))) { + (!std::is_same::value))) { return createAgentImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { MF(Arg); return NoMasterOutputType(); }), std::function, Optional>...>( std::pair...)>( [F{std::move(F)}](std::pair... Args) { const auto Result = F(Args...); return std::tuple_cat(std::tuple(Result), NoMasterOutputType()); })); } else { ASSERT(false && "Unexpected type arguments"); } } template AgentHandle DeluxeContext::createAgent( const std::string &Name, std::function, Optional...>( std::pair...)> &&F) noexcept { if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using MasterOutputType = std::tuple>...>; using OutputType = DeluxeTuple; return createAgentImpl( Name, std::function)>( [](std::pair) { return MasterOutputType(); }), std::function< std::tuple, Optional>...>( std::pair, bool>...)>( [F{std::move(F)}](std::pair, bool>... Args) { const auto Result = F({std::get<0>(Args.first), Args.second}...); return wrapBuiltinInDeluxeTuple(Result, seq_t<1 + sizeof...(Ts)>()); })); } else if constexpr (TypeListAllDeluxeTuple< TypeList>::Value && - !std::is_same_v && + !std::is_same::value && (true && ... && - (!std::is_same_v))) { + (!std::is_same::value))) { using MasterOutputType = std::tuple...>; return createAgentImpl( Name, std::function)>( [](std::pair) { return MasterOutputType(); }), std::function, Optional...>( std::pair...)>( [F{std::move(F)}](std::pair... Args) { const auto Output = F(Args...); return Output; })); } else { ASSERT(false && "Unexpected type arguments"); } } template AgentHandle DeluxeContext::createAgent( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept { if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using MasterInputType = DeluxeTuple; using MasterOutputType = std::tuple>...>; using OutputType = DeluxeTuple; return createAgentImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { const auto Result = MF({std::get<0>(Arg.first), Arg.second}); return wrapBuiltinInDeluxeTuple(Result, seq_t()); }), std::function< std::tuple, Optional>...>( std::pair, bool>...)>( [F{std::move(F)}](std::pair, bool>... Args) { const auto Result = F({std::get<0>(Args.first), Args.second}...); return wrapBuiltinInDeluxeTuple(Result, seq_t<1 + sizeof...(Ts)>()); })); } else if constexpr (TypeListAllDeluxeTuple< TypeList>::Value && - !std::is_same_v && + !std::is_same::value && (true && ... && - (!std::is_same_v))) { + (!std::is_same::value))) { using MasterOutputType = std::tuple...>; return createAgentImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { const auto Output = MF(Arg); return Output; }), std::function, Optional...>( std::pair...)>( [F{std::move(F)}](std::pair... Args) { const auto Output = F(Args...); return Output; })); } else { ASSERT(false && "Unexpected type arguments"); } } template DeluxeContext::ErrorCode DeluxeContext::registerSensorValues(AgentHandle Sensor, Iterator &&Start, const Iterator &End, T Default) noexcept { // Get the type of values provided by \p Iterator. STATIC_ASSERT((std::is_same::value), "type mismatch"); // Make sure preconditions are met. if (!System->isDeluxeSensor(Sensor)) { DCRETERROR(ErrorCode::NotSensor); } auto S = System->getDeluxeSensor(Sensor); ASSERT(S); // Sanity check. if (S->simulationDataSourceIsSet()) { DCRETERROR(ErrorCode::AlreadyHasValueStream); } if constexpr (TypeListContains::Value) { if (S->OutputType != TypeToken::Value) { DCRETERROR(ErrorCode::TypeMismatch); } // Register input stream. // \note Need to capture parameters by value so having local copies. S->registerSimulationDataSource(std::function(void)>([= ](void) mutable noexcept->DeluxeTuple { if (Start != End) { LOG_TRACE_STREAM << "Reading next value for sensor '" << S->FullName << "': " << *Start << '\n'; return make_deluxe_tuple(*Start++); } else { LOG_TRACE_STREAM << "Providing default value for sensor '" << S->FullName << "': " << Default << '\n'; return make_deluxe_tuple(Default); } })); } else if constexpr (IsDeluxeTuple::Value && - !std::is_same_v) { + !std::is_same::value) { if (S->OutputType != T::TT) { DCRETERROR(ErrorCode::TypeMismatch); } // Register input stream. // \note Need to capture parameters by value so having local copies. S->registerSimulationDataSource( std::function([=](void) mutable noexcept->T { if (Start != End) { LOG_TRACE_STREAM << "Reading next value for sensor '" << S->FullName << "': " << *Start << '\n'; return *Start++; } else { LOG_TRACE_STREAM << "Providing default value for sensor '" << S->FullName << "': " << Default << '\n'; return Default; } })); } else { ASSERT(false && "Unexpected type argument"); } return ErrorCode::NoError; } } // End namespace deluxe } // End namespace rosa // Undef local macro if not used in the corresponding implementation. #ifndef ROSA_LIB_DELUXE_DELUXECONTEXT_CPP #undef DCRETERROR #endif #endif // ROSA_DELUXE_DELUXECONTEXT_HPP diff --git a/include/rosa/deluxe/DeluxeSensor.hpp b/include/rosa/deluxe/DeluxeSensor.hpp index 71d6a0f..7a66ba2 100644 --- a/include/rosa/deluxe/DeluxeSensor.hpp +++ b/include/rosa/deluxe/DeluxeSensor.hpp @@ -1,659 +1,659 @@ //===-- 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" #include "rosa/deluxe/DeluxeTuple.hpp" /// Local helper macros to deal with built-in types. /// ///@{ /// Creates function name for member functions in \c rosa::deluxe::DeluxeSensor. /// /// \param N name suffix to use #define DSMASTERHANDLERNAME(N) handleMaster_##N /// Defines member functions for handling messages from *master* in /// \c rosa::deluxe::DeluxeSensor. /// /// \see \c DeluxeSensorMasterInputHandlers /// /// \note No pre- and post-conditions are validated directly by these functions, /// they rather rely on \c rosa::deluxe::DeluxeSensor::saveMasterInput to do /// that. /// /// \param T the type of input to handle /// \param N name suffix for the function identifier #define DSMASTERHANDLERDEFN(T, N) \ void DSMASTERHANDLERNAME(N)(atoms::Master, id_t MasterId, token_size_t Pos, \ T Value) noexcept { \ saveMasterInput(MasterId, Pos, Value); \ } /// Convenience macro for \c DSMASTERHANDLERDEFN with identical arguments. /// /// \see \c DSMASTERHANDLERDEFN /// /// This macro can be used instead of \c DSMASTERHANDLERDEFN 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 DSMASTERHANDLERDEF(T) DSMASTERHANDLERDEFN(T, T) /// Results in a \c THISMEMBER reference to a member function defined by /// \c DSMASTERHANDLERDEFN. /// /// Used in the constructor of \c rosa::deluxe::DeluxeSensor to initialize super /// class \c rosa::Agent with member function defined by \c DSMASTERHANDLERDEFN. /// /// \see \c DSMASTERHANDLERDEFN, \c THISMEMBER /// /// \param N name suffix for the function identifier #define DSMASTERHANDLERREF(N) THISMEMBER(DSMASTERHANDLERNAME(N)) ///@} 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; the actual value in /// \c rosa::deluxe::DeluxeSensor::MasterInputNextPos is valid with respect to /// the corresponding types. /// /// \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 { /// 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 \c rosa::deluxe::DeluxeExecutionPolicy that controls the execution of /// \c this object. std::unique_ptr ExecutionPolicy; public: /// The type of values produced by \p this object. /// /// That is the types of values \p this object sends to its *master* in a /// \c rosa::deluxe::DeluxeTuple. /// /// \see \c rosa::deluxe::DeluxeSensor::master const Token OutputType; /// The type of values \p this object processes from its *master*. /// /// That is the types of values \p this object receives from its *master* in a /// \c rosa::deluxe::DeluxeTuple. /// /// \see \c rosa::deluxe::DeluxeSensor::master const Token MasterInputType; private: /// Indicates which element of the master-input is expected from the *master*. /// /// The *master* is supposed to send one \c rosa::deluxe::DeluxeTuple value /// element by element in their order of definition. This member field tells /// the element at which position should be received next. /// /// \p this object is supposed to be triggered only when a complete /// master-input has been received, that is the field should hold the value /// `0`. /// /// \see \c rosa::deluxe::DeluxeSensor::handleTrigger /// \c rosa::deluxe::DeluxeSensor::saveMasterInput token_size_t MasterInputNextPos; /// Indicates whether the input value from the *master* has been changed since /// the last trigger received from the system. /// /// The flag is reset to \c false upon handling a trigger and then set to \c /// true by \c rosa::deluxe::DeluxeSensor::saveMasterInput when storig a new /// input value in \c rosa::deluxe::DeluxeSensor::MasterInputValue. bool MasterInputChanged; /// Stores the actual input value from *master*. /// /// \note The type of the stored value matches the types indicated by \c /// rosa::deluxe::DeluxeSensor::MasterInputType. const std::unique_ptr MasterInputValue; /// 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 and master-input processing function are /// captured in lambda expressions that are in turn wrapped in \c /// std::function objects. The lambda expression calls a processing function, /// either to handle master-input or obtain the next sensory value from data /// source. The next sensory value is sent it to *master* by calling \c /// rosa::deluxe::DeluxeSensor::sendToMaster. Also, the flag \c /// rosa::deluxe::DeluxeSensor::MasterInputChanged is reset when the current /// value is passed to the master-input processing function. The function \c /// rosa::deluxe::DeluxeSensor::handleTrigger needs only to call the proper /// function object. /// Processes master-input. /// /// \ingroup DeluxeSensorTriggerHandlers /// /// The function is called upon the sensor is trigged by the system. const H MFP; /// Produces the next sensory value 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; /// Produces the next sensory value 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; /// Tells the unique identifier of the *master* of \p this object, if any /// registered. /// /// \return the unique identifier of the *master* /// /// \pre A *master* is registered for \p this object: \code /// Master /// \endcode id_t masterId(void) const noexcept; /// Wraps a master-input processing function into a trigger handler. /// /// \see \c rosa::deluxe::DeluxeSensor::MFP and \c DeluxeSensorTriggerHandlers /// /// \tparam Ts types of elements of master-input processed by \p MF /// \tparam S0 indices for accessing master-input values /// /// \param MF function that processes master-input /// /// \note The second argument provides indices statically as template /// arguments \p S0..., so its actual value is ignored. /// /// \note A master-input type of \c rosa::deluxe::EmptyDeluxeTuple indicates /// that \p this object does not receive master-input, \p MF is never called /// if \p Ts is empty. /// /// \return trigger handler function based on \p MF /// /// \pre Statically, the indices match the elements: \code /// sizeof...(Ts) == sizeof...(S0) /// \endcode Dynamically, \p Ts... match \c /// rosa::deluxe::DeluxeSensor::MasterInputType: \code /// MasterInputType == DeluxeTuple::TT /// \endcode template H triggerHandlerFromProcessingFunction( std::function, bool>)> &&MF, Seq) noexcept; /// Wraps a data source function into a trigger handler. /// /// \see \c rosa::deluxe::DeluxeSensor::FP, \c /// rosa::deluxe::DeluxeSensor::SFP, and \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 /// /// \return trigger handler function based on \p F /// /// \pre Statically, the type agument \p T is an instance of \c /// rosa::deluxe::DeluxeTuple: \code /// IsDeluxeTuple::Value /// \endcode Dynamically, \p T matches \c /// rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == T::TT /// \endcode template H triggerHandlerFromDataSource(std::function &&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 \p F and \p MF do not potentially throw exception. /// /// \tparam MT type of master-input handled by \p MF /// \tparam T type of data to operate on /// /// \note Instantiation fails if any of the type arguments \p MT and \p T is /// not an instance of \c rosa::deluxe::DeluxeTuple or \p T is \c /// rosa::deluxe::EmptyDeluxeTuple. /// /// \note If \p MT is \c rosa::deluxe::EmptyDeluxeTuple, the constructed /// object does not receive master-input. /// /// \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 MF function to process master-input values with /// \param F function to generate the next value with during normal operation /// /// \pre Statically, \p MT and \p T are instances of \c /// rosa::deluxe::DeluxeTuple and \p T contains at least one element:\code /// TypeListAllDeluxeTuple>::Value && T::Length > 0 /// \endcode /// Dynamically, the instance is created as of kind /// \c rosa::deluxe::atoms::SensorKind: /// \code /// Kind == rosa::deluxe::atoms::SensorKind /// \endcode /// /// \see \c rosa::deluxe::DeluxeTuple template < typename MT, typename T, typename = std::enable_if_t< TypeListAllDeluxeTuple>::Value && (T::Length > 0)>> DeluxeSensor(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, std::function)> &&MF, std::function &&F) noexcept; /// Destroys \p this object. ~DeluxeSensor(void) noexcept; /// Returns the current execution policy of \p this object. /// /// \see \c rosa::deluxe::DeluxeExecutionPolicy /// /// \note The returned reference is valid only as long as \c /// rosa::deluxe::DeluxeSensor::setExecutionPolicy() is not called and \p this /// object is not destroyed. /// /// \return \c rosa::deluxe::DeluxeSensor::ExecutionPolicy const DeluxeExecutionPolicy &executionPolicy(void) const noexcept; /// Sets the current execution policy of \p this object to \p EP. /// /// \see \c rosa::deluxe::DeluxeExecutionPolicy /// /// \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*. /// /// \note Any call to \c rosa::deluxe::DeluxeSensor::registerMaster should be /// paired with a corresponding call of \c /// rosa::deluxe::DeluxeAgent::registerSlave, which validates that /// input/output types of master and slave matches. /// /// \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 Ts types of elements of values provided by \p SF /// /// \param SF function to generate value with /// /// \pre \p Ts... match \c rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == TypeToken::Value /// \endcode template void registerSimulationDataSource( std::function(void)> &&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. /// /// The elements from \p Value are sent one by one in separate messages to the /// *master*. /// /// \tparam Ts types of the elements in \p Value /// \tparam S0 indices for accessing elements of \p Value /// /// \param Value value to send /// /// \note The second argument provides indices statically as template /// arguments \p S0..., so its actual value is ignored. /// /// \pre Statically, the indices match the elements: \code /// sizeof...(Ts) == sizeof...(S0) /// \endcode Dynamically, \p Ts match \c /// rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == TypeToken::Value /// \endcode template void sendToMaster(const DeluxeTuple &Value, Seq) noexcept; /// Handles master-input and generates the next sensory value upon trigger /// from the system. /// /// Executes \c rosa::deluxe::DeluxeSensor::MFP for processing master-input /// and data generating function \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. /// /// \pre Master-input is supposed to be completely received upon triggering: /// \code /// MasterInputNextPos == 0 /// \endcode void handleTrigger(atoms::Trigger) noexcept; /// Stores a new input value from the *master*. /// /// The function stores \p Value at position \p Pos in \c /// rosa::deluxe::DeluxeSensor::MasterInputValue and also sets the /// flag \c rosa::deluxe::DeluxeSensor::MasterInputChanged. The function also /// takes care of checking and updating \c /// rosa::deluxe::DeluxeSensor::MasterInputNextPos: increments its value and /// resets it to `0` when the last element is received. /// /// \note Utilized by member functions of group \c /// DeluxeSensorMasterInputHandlers. /// /// \tparam T type of input to store /// /// \param Id unique identifier of the *master* /// \param Pos position of the value in the \c rosa::deluxe::DeluxeTuple /// \param Value the input value to store /// /// \pre The *master* with \p Id is registered, \p Pos is the expected /// position of master-input, and the input from the *master* at position \p /// Pos is expected to be of type \p T: \code /// Master && masterId() == Id && Pos == MasterInputNextPos && /// typeAtPositionOfToken(MasterInputType, Pos) == TypeNumberOf::Value /// \endcode template void saveMasterInput(id_t Id, token_size_t Pos, T Value) noexcept; /// \defgroup DeluxeSensorMasterInputHandlers Master-input handlers of /// rosa::deluxe::DeluxeSensor /// /// Definition of member functions handling messages from the *master* with /// different types of input /// /// A *slave* generally needs to be prepared to deal with values of any /// built-in type to handle messages from its *master*. Each type requires a /// separate message handler, which are implemented by these functions. The /// functions instantiate \c rosa::deluxe::DeluxeSensor::saveMasterInput 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 /// DSMASTERHANDLERDEF. /// /// \note Keep these definitions in sync with \c rosa::BuiltinTypes. /// ///@{ DSMASTERHANDLERDEF(AtomValue) DSMASTERHANDLERDEF(int16_t) DSMASTERHANDLERDEF(int32_t) DSMASTERHANDLERDEF(int64_t) DSMASTERHANDLERDEF(int8_t) DSMASTERHANDLERDEFN(long double, long_double) DSMASTERHANDLERDEFN(std::string, std__string) DSMASTERHANDLERDEF(uint16_t) DSMASTERHANDLERDEF(uint32_t) DSMASTERHANDLERDEF(uint64_t) DSMASTERHANDLERDEF(uint8_t) DSMASTERHANDLERDEF(unit_t) DSMASTERHANDLERDEF(bool) DSMASTERHANDLERDEF(double) DSMASTERHANDLERDEF(float) /// @} }; template DeluxeSensor::H DeluxeSensor::triggerHandlerFromProcessingFunction( std::function, bool>)> &&MF, Seq) noexcept { using MT = DeluxeTuple; STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent arguments"); ASSERT(MasterInputType == MT::TT); return [ this, MF ](void) noexcept { // Do not do anything for master-input type \c // rosa::deluxe::EmptyDeluxeTuple. - if (!std::is_same_v) { + if (!std::is_same::value) { LOG_TRACE_STREAM << "DeluxeSensor " << FullName << " handles master-input." << std::endl; // The assert must hold if \p this object was successfuuly constructed. ASSERT((true && ... && (static_cast(static_cast(S0)) == S0))); const auto MasterInputArg = std::make_pair( // Get all elements of the tuple in a fold expression. DeluxeTuple(*static_cast( MasterInputValue->pointerTo(static_cast(S0)))...), MasterInputChanged); MasterInputChanged = false; MF(MasterInputArg); } }; } template DeluxeSensor::H DeluxeSensor::triggerHandlerFromDataSource(std::function &&F, bool inSimulation) noexcept { STATIC_ASSERT(IsDeluxeTuple::Value, "not tuple type argument"); ASSERT(OutputType == T::TT); return [ this, F, inSimulation ](void) noexcept { // Get value and send it to master only if \p ExecutionPolicy allows it. if (ExecutionPolicy->shouldProcess({})) { LOG_TRACE_STREAM << "DeluxeSensor " << Name << " obtains next value." << std::endl; sendToMaster(F(), seq_t()); } else { LOG_TRACE_STREAM << "DeluxeSensor " << Name << " skips next value." << std::endl; 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, std::function)> &&MF, std::function &&F) noexcept : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger), DSMASTERHANDLERREF(AtomValue), DSMASTERHANDLERREF(int16_t), DSMASTERHANDLERREF(int32_t), DSMASTERHANDLERREF(int64_t), DSMASTERHANDLERREF(int8_t), DSMASTERHANDLERREF(long_double), DSMASTERHANDLERREF(std__string), DSMASTERHANDLERREF(uint16_t), DSMASTERHANDLERREF(uint32_t), DSMASTERHANDLERREF(uint64_t), DSMASTERHANDLERREF(uint8_t), DSMASTERHANDLERREF(unit_t), DSMASTERHANDLERREF(bool), DSMASTERHANDLERREF(double), DSMASTERHANDLERREF(float)), ExecutionPolicy(DeluxeExecutionPolicy::decimation(1)), OutputType(T::TT), MasterInputType(MT::TT), MasterInputChanged(false), MasterInputValue(new typename TokenizedStorageForTypeList< typename UnwrapDeluxeTuple::Type>::Type()), MFP(triggerHandlerFromProcessingFunction(std::move(MF), seq_t())), FP(triggerHandlerFromDataSource(std::move(F), false)), SFP(nullptr) { ASSERT(Kind == atoms::SensorKind); LOG_TRACE_STREAM << "DeluxeSensor " << FullName << " is created." << std::endl; ASSERT(inv()); } template void DeluxeSensor::registerSimulationDataSource( std::function(void)> &&SF) noexcept { ASSERT(OutputType == TypeToken::Value); SFP = triggerHandlerFromDataSource(std::move(SF), true); ASSERT(inv()); } template void DeluxeSensor::sendToMaster(const DeluxeTuple &Value, Seq) noexcept { STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent arguments"); ASSERT(OutputType == TypeToken::Value); // The assert must hold if \p this object was successfuuly constructed. ASSERT((true && ... && (static_cast(static_cast(S0)) == S0))); // Create a static constant array for these indices to be available as lvalue // references when creating messages below. \c S0... when used directly in a // fold expression is a temporary value, which would result in \c // rosa::Message instances being created with rvalue references. Further, all // other values would to copied into a temporary variable for making them /// available as rvalue references (they are constant lvalue references here). static constexpr std::array Indices{{S0...}}; LOG_TRACE_STREAM << "DeluxeSensor " << FullName << "(" << Id << ") sends to master(" << static_cast(Master && *Master) << "): " << Value << std::endl; // There is a handle and the referred *master* is in a valid state. if (Master && *Master) { // Handle each element of the tuple in a fold expression. (Master->sendMessage(Message::create(atoms::Slave::Value, Id, Indices[S0], std::get(Value))), ...); } ASSERT(inv()); } template void DeluxeSensor::saveMasterInput(id_t Id, token_size_t Pos, T Value) noexcept { ASSERT(Master && masterId() == Id && Pos == MasterInputNextPos && typeAtPositionOfToken(MasterInputType, Pos) == TypeNumberOf::Value); LOG_TRACE_STREAM << "DeluxeSensor " << FullName << "(" << Id << ") saves value from master: (" << static_cast(Pos) << ") " << Value << std::endl; // Save value. *static_cast(MasterInputValue->pointerTo(Pos)) = Value; // Update position of next value. if (++MasterInputNextPos == lengthOfToken(MasterInputType)) { MasterInputNextPos = 0; } // Set flag. MasterInputChanged = true; } } // End namespace deluxe } // End namespace rosa #undef DSMASTERHANDLEREF #undef DSMASTERHANDLEDEF #undef DSMASTERHANDLEDEFN #undef DSMASTERHANDLENAME #endif // ROSA_DELUXE_DELUXESENSOR_HPP diff --git a/include/rosa/deluxe/DeluxeTuple.hpp b/include/rosa/deluxe/DeluxeTuple.hpp index 2fcd525..e57d29b 100644 --- a/include/rosa/deluxe/DeluxeTuple.hpp +++ b/include/rosa/deluxe/DeluxeTuple.hpp @@ -1,358 +1,359 @@ //===-- rosa/deluxe/DeluxeTuple.hpp -----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeTuple.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2019 /// /// \brief Facilities for handling multiple input/output values for connections /// in the *deluxe interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXETUPLE_HPP #define ROSA_DELUXE_DELUXETUPLE_HPP #include "rosa/support/sequence.hpp" #include "rosa/support/type_token.hpp" #include #include namespace rosa { namespace deluxe { /// A tuple to manage multiple input/output values in the *deluxe interface*. /// /// \tparam Ts types of elements of the tuple /// /// \note The template may be instantiated only with built-in types and the /// number of those type may not exceed the capacity of a \c rosa::Token. template struct DeluxeTuple : public std::tuple { // Statically enforce that the class template is instantiated only with // built-in types. STATIC_ASSERT((TypeListSubsetOf, BuiltinTypes>::Value), "not built-in types"); // Statically enforce that the class template is instantiated with not too // many types. // \note Instantiation would fail on \c rosa::deluxe::DeluxeTuple::TT if there // are too any types; this assertion is for more readable error reporting. STATIC_ASSERT(sizeof...(Ts) <= token::MaxTokenizableListSize, "Too many types"); /// How many elements the instance has. static constexpr token_size_t Length = sizeof...(Ts); /// What types the class contains. /// /// Type information encoded as \c rosa::Token. static constexpr Token TT = TypeToken::Value; /// Default constructor, zero-initializes elements. DeluxeTuple(void) = default; /// Constructor, initializes the underlying \c std::tuple with lvalue /// references. /// /// \param Args value references to the values to store DeluxeTuple(const std::decay_t &... Args) : std::tuple(Args...) {} /// Constructor, initializes the underlying \c std::tuple with rvalue /// references. /// /// \param Args rvalue references to the values to store DeluxeTuple(std::decay_t &&... Args) : std::tuple(std::move(Args)...) {} /// Default copy-constructor. DeluxeTuple(const DeluxeTuple &) = default; /// Default move-constructor. DeluxeTuple(DeluxeTuple &&) = default; /// Default copy-assignment. DeluxeTuple &operator=(const DeluxeTuple &) = default; /// Default move-assignment. DeluxeTuple &operator=(DeluxeTuple &&) = default; private: /// Dumps \p this object to a given \c std::ostream. /// /// \note Provides implementation for \c rosa::deluxe::DeluxeTuple::dump. /// /// \tparam S0 Indices for accessing elements. /// /// \param [in,out] OS output stream to dump to /// /// \note The second argument provides indices statically as template /// arguments \p S0..., so its actual value is ignored. /// /// \pre Statically, \p S0... matches number of types \p this object was /// created: \code /// sizeof...(S0) == sizeof...(Ts) /// \endcode template void dump(std::ostream &OS, Seq) const noexcept; public: /// Dumps \p this object to a given \c std::ostream. /// /// \param [in,out] OS output stream to dump to void dump(std::ostream &OS) const noexcept; }; template template void DeluxeTuple::dump(std::ostream &OS, Seq) const noexcept { STATIC_ASSERT(sizeof...(S0) == sizeof...(Ts), "inconsistent type arguments"); // Convert value to std::string with std::to_string except for a value of // std::string that does not need conversion. auto dump_to_string = [](const auto &V) { - if constexpr (std::is_same_v, std::string>) { + if constexpr (std::is_same, std::string>::value) { return V; } else { return std::to_string(V); } }; OS << "{"; (OS << ... << (" " + dump_to_string(std::get(*this)))); OS << " }"; } template void DeluxeTuple::dump(std::ostream &OS) const noexcept { dump(OS, seq_t()); } /// Type alias for a \c rosa::deluxe::DeluxeTuple that contains no elements. using EmptyDeluxeTuple = DeluxeTuple<>; /// Template specialization for \c rosa::deluxe::EmptyDeluxeTuple. template <> struct DeluxeTuple<> : public std::tuple<> { /// How many elements the instance has. static constexpr token_size_t Length = 0; /// What types the class contains. /// /// Type information encoded as \c rosa::Token. static constexpr Token TT = TypeToken<>::Value; /// Constructor, initializes the underlying \c std::tuple. DeluxeTuple(void) : std::tuple<>() {} /// Default copy-constructor. DeluxeTuple(const DeluxeTuple &) = default; // Default move-constructor. DeluxeTuple(DeluxeTuple &&) = default; /// Default copy-assignment. DeluxeTuple &operator=(const DeluxeTuple &) = default; // Default move-assignment, DeluxeTuple &operator=(DeluxeTuple &&) = default; /// Dumps \p this object to a given \c std::ostream. /// /// \param [in,out] OS output stream to dump to static void dump(std::ostream &OS) noexcept; }; /// Creates a \c rosa::deluxe::DeluxeTuple instance from the given lvalues /// references. /// /// \tparam Ts types of elements of the tuple /// /// \see \c rosa::deluxe::DeluxeTuple /// /// \param Args values to store in the tuple /// /// \return an instance of \c rosa::deluxe::DeluxeTuple with \p Args as /// elements template inline DeluxeTuple make_deluxe_tuple(const Ts &... Args) noexcept { return DeluxeTuple(Args...); } /// Creates a \c rosa::deluxe::DeluxeTuple instance from the given rvalue /// references. /// /// \tparam Ts types of elements of the tuple /// /// \see \c rosa::deluxe::DeluxeTuple /// /// \param Args values to store in the tuple /// /// \return an instance of \c rosa::deluxe::DeluxeTuple with \p Args as /// elements template inline DeluxeTuple make_deluxe_tuple(Ts&&... Args) noexcept { return DeluxeTuple(std::move(Args)...); } /// \defgroup UnwrapDeluxeTuple Implementation of /// rosa::deluxe::UnwrapDeluxeTuple /// /// \brief Unwraps element types from an instance of \c /// rosa::deluxe::DeluxeTuple into a \c rosa::TypeList /// /// Types can be unwrapped from a \c rosa::deluxe::DeluxeTuple instance as \code /// typename UnwrapDeluxeTuple::Type /// \endcode /// /// For example, the following expression evaluates to `true`: \code -/// std::is_same_v>::Type, -/// TypeList> +/// std::is_same>::Type, +/// TypeList>::value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam Tuple \c rosa::deluxe::DeluxeTuple to unwrap template struct UnwrapDeluxeTuple; /// Implementation of the template for \c rosa::deluxe::DeluxeTuple instances. template struct UnwrapDeluxeTuple> { using Type = TypeList; }; ///@} /// \defgroup TypeListUnwrapDeluxeTuple Implementation of /// \c rosa::deluxe::TypeListUnwrapDeluxeTuple /// /// \brief Unwraps element types from instances of \c /// rosa::deluxe::DeluxeTuple in a \c rosa::TypeList. /// /// Types can be unwrapped from \c rosa::deluxe::DeluxeTuple instances as \code /// typename TypeListUnwrapDeluxeTuple::Type /// \endcode /// /// For example, the following expression evaluates to `true`: \code -/// std::is_same_v< +/// std::is_same< /// typename TypeListUnwrapDeluxeTuple, /// T3>>::Type, -/// TypeList> +/// TypeList +/// >::value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to check template struct TypeListUnwrapDeluxeTuple; /// Specialization for \c rosa::EmptyTypeList. template <> struct TypeListUnwrapDeluxeTuple { using Type = EmptyTypeList; }; /// Specialization for the case when the first type in \p List is an instance of /// \c rosa::deluxe::DeluxeTuple. template struct TypeListUnwrapDeluxeTuple, Ts...>> { using Type = typename TypeListConcat< typename UnwrapDeluxeTuple>::Type, typename TypeListUnwrapDeluxeTuple>::Type>::Type; }; /// Implementation for a general first type in \p List. template struct TypeListUnwrapDeluxeTuple> { using Type = typename TypeListPush< T, typename TypeListUnwrapDeluxeTuple>::Type>::Type; }; ///@} /// \defgroup IsDeluxeTuple Implementation of \c rosa::deluxe::IsDeluxeTuple /// /// \brief Tells if a type is an instance of \c rosa::deluxe::DeluxeTuple. /// /// Whether a type \c T is an instance of \c rosa::deluxe::DeluxeTuple can be /// checked as \code /// IsDeluxeTuple::Value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam T type to check template struct IsDeluxeTuple; /// Specialization for the case when the type is an instance of \c /// rosa::deluxe::DeluxeTuple. template struct IsDeluxeTuple> { static constexpr bool Value = true; }; /// Implementation for a general case of type \p T. template struct IsDeluxeTuple { static constexpr bool Value = false; }; ///@} /// \defgroup TypeListAllDeluxeTuple Implementation of /// \c rosa::deluxe::TypeListAllDeluxeTuple /// /// \brief Tells if all types in a \c rosa::TypeList is an instance of \c /// rosa::deluxe::DeluxeTuple. /// /// Whether a \c rosa::TypeList \c List contains instances of \c /// rosa::deluxe::DeluxeTuple only can be checked as \code /// TypeListAllDeluxeTuple::Value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to check template struct TypeListAllDeluxeTuple; /// Specialization for \c rosa::EmptyTypeList. template <> struct TypeListAllDeluxeTuple { static constexpr bool Value = true; }; /// Implementation for the general case when there is at leasst one element in /// the list. template struct TypeListAllDeluxeTuple> { static constexpr bool Value = IsDeluxeTuple::Value && TypeListAllDeluxeTuple>::Value; }; ///@} } // End namespace deluxe } // End namespace rosa namespace std { /// Dumps a \c rosa::deluxe::Deluxe instance to a given \c std::ostream. /// /// \param [in,out] OS output stream to dump to /// \param Tuple \c rosa::deluxe::Deluxe to dump /// /// \return \p OS after dumping \p Tuple to it template ostream &operator<<(ostream &OS, const rosa::deluxe::DeluxeTuple &Tuple) { Tuple.dump(OS); return OS; } } // End namespace std #endif // ROSA_DELUXE_DELUXETUPLE_HPP diff --git a/include/rosa/support/tokenized_storages.hpp b/include/rosa/support/tokenized_storages.hpp index 32b73b2..dca7f4e 100755 --- a/include/rosa/support/tokenized_storages.hpp +++ b/include/rosa/support/tokenized_storages.hpp @@ -1,620 +1,620 @@ //===-- rosa/support/tokenized_storages.hpp ---------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/tokenized_storages.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Definition of storage helper template for storing values in a /// type-safe way based on type tokens. /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_TOKENIZED_STORAGES_HPP #define ROSA_SUPPORT_TOKENIZED_STORAGES_HPP #include "rosa/support/type_token.hpp" #include #include namespace rosa { /// Defines a simple interface for storing and accessing values of different /// types. /// /// While the interface provides features to access values and know their /// types, it is the users responsibility to use particular values according to /// their actual types. No facilities for type-safe access of values is /// provided by the class. /// /// \see \c rosa::TokenizedStorage for a type-safe specialization of the /// interface. class AbstractTokenizedStorage { protected: /// Protected constructor restricts instantiation for derived classes. AbstractTokenizedStorage(void) noexcept = default; public: /// No copying and moving of \c rosa::AbstractTokenizedStorage instances. ///@{ AbstractTokenizedStorage(const AbstractTokenizedStorage&) = delete; AbstractTokenizedStorage &operator=(const AbstractTokenizedStorage&) = delete; AbstractTokenizedStorage(AbstractTokenizedStorage&& Other) = delete; AbstractTokenizedStorage &operator=(AbstractTokenizedStorage&&) = delete; ///@} /// Destroys \p this object. virtual ~AbstractTokenizedStorage(void) noexcept = default; /// Tells how many values are stored in \p this object. /// /// \return number of values stored in \p this object virtual size_t size(void) const noexcept = 0; /// Tells the type of the value stored at a position. /// /// \param Pos the index of the value whose type is to returned /// /// \return \c rosa::TypeNumber for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < size() /// \endcode virtual TypeNumber typeAt(const token_size_t Pos) const noexcept = 0; /// Provides an untyped pointer for the value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < size() /// \endcode virtual void *pointerTo(const token_size_t Pos) noexcept = 0; /// Provides a constant untyped pointer for the value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return constant untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < Offsets.size() /// \endcode virtual const void *pointerTo(const token_size_t Pos) const noexcept = 0; }; /// Template class storing values and providing dynamic type-safe access to /// them in a lightweight way based on type tokens. /// /// \see rosa/support/type_token.hpp /// /// \tparam Types types whose values are to be stored template class TokenizedStorage; /// \defgroup TokenizedStorageForTypeList Implementation of /// rosa::TokenizedStorageForTypeList /// /// \brief Transforms a \c rosa::TypeList instance to the corresponding /// \c rosa::TokenizedStorage instance. /// /// A \c rosa::TypeList \c List instance can be turned into a corresponding \c /// rosa::TokenizedStorage instance as \code /// typename TokenizedStorageForTypeList::Type /// \endcode /// /// For example, the following expression evaluates to `true`: \code -/// std::is_same_v>::Type, -/// TokenizedStorage> +/// std::is_same>::Type, +/// TokenizedStorage>::value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to transform template struct TokenizedStorageForTypeList; /// Implementation of the template for \c rosa::TypeList instances. template struct TokenizedStorageForTypeList> { using Type = TokenizedStorage; }; ///@} /// Nested namespace with implementation for \c rosa::TokenizedStorage, consider /// it private. namespace { /// Initializes a pre-allocated memory area with values from constant lvalue /// references. /// /// \tparam Types types whose values are to be stored /// /// \param Arena pre-allocated memory area to store values to /// \param Ts the values to store in \p Arena /// /// \note \p Arena needs to be a valid pointer to a memory area big enough for /// values of \p Types. template inline void createArenaElements(void *const Arena, const Types &... Ts) noexcept; /// \defgroup createLvalueArenaElement Implementation of creating lvalue arena elements /// /// Stores values from constant lvalue references into a pre-allocated memory /// area. /// /// \note To be used by the implementation of \c createArenaElements. /// /// \todo Document these functions. ///@{ /// \note This terminal case is used for both constant lvalue references and /// value references. template inline void createArenaElement(void *const, const std::vector &Offsets) { ASSERT(Pos == Offsets.size()); } template inline void createArenaElement(void *const Arena, const std::vector &Offsets, const Type &T, const Types &... Ts) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); new (static_cast(static_cast(static_cast(Arena) + Offsets[Pos]))) Type(T); createArenaElement(Arena, Offsets, Ts...); } template inline void createArenaElement(void *const Arena, const std::vector &Offsets, const AtomConstant &, const Types &... Ts) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); *static_cast( static_cast(static_cast(Arena) + Offsets[Pos])) = V; createArenaElement(Arena, Offsets, Ts...); } ///@} /// Implementation of the template. /// /// \tparam Types types of values to store /// /// \param Arena pre-allocated memory area to store values to /// \param Ts values to store in \p Arena /// /// \pre \p Arena is not \p nullptr. template inline void createArenaElements(void *const Arena, const Types &... Ts) noexcept { ASSERT(Arena != nullptr); createArenaElement<0>(Arena, TokenizedStorage::Offsets, Ts...); } /// Initializes a pre-allocated memory area with values from rvalue references. /// /// \tparam Types types whose values are to be stored /// /// \param Arena pre-allocated memory area to store values to /// \param Ts the values to store in \p Arena /// /// \note \p Arena needs to be a valid pointer to a memory area big enough for /// values of \p Types. template inline void createArenaElements(void *const Arena, Types &&... Ts) noexcept; /// \defgroup createRvalueArenaElement Implementation of creating rvalue arena elements /// /// Stores values from rvalue references into a pre-allocated memory area. /// /// \note To be used by the implementation of \c createArenaElements. /// /// \todo Document these functions. ///@{ template inline void createArenaElement(void *const Arena, const std::vector &Offsets, Type &&T, Types &&... Ts) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); new (static_cast(static_cast( static_cast(Arena) + Offsets[Pos]))) Type(std::move(T)); createArenaElement(Arena, Offsets, std::move(Ts)...); } template inline void createArenaElement(void *const Arena, const std::vector &Offsets, AtomConstant &&, Types &&... Ts) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); *static_cast( static_cast(static_cast(Arena) + Offsets[Pos])) = V; createArenaElement(Arena, Offsets, std::move(Ts)...); } ///@} /// Implementation of the template. /// /// \tparam Types types of values to store /// /// \param Arena pre-allocated memory area to store values to /// \param Ts values to store in \p Arena /// /// \pre \p Arena is not \c nullptr. template inline void createArenaElements(void *const Arena, Types &&... Ts) noexcept { ASSERT(Arena != nullptr); createArenaElement<0>(Arena, TokenizedStorage::Offsets, std::move(Ts)...); } /// Destroys values allocated by \c createArenaElements. /// /// \tparam Types types whose values are stored in \p Arena /// /// \param Arena the memory area to destroy values from /// /// \note \p Arena needs to be a valid pointer to a memory area where values of /// \p Types are stored. template inline void destroyArenaElements(void *const Arena) noexcept; /// \defgroup destroyArenaElement Implementation of destroying arena elements /// /// Destroys values from a memory area. /// /// \note To be used by the implementation of \c destroyArenaElements. /// /// \todo Document these functions. ///@{ template inline void destroyArenaElement(void *const, const std::vector &Offsets) noexcept { ASSERT(Pos == Offsets.size()); } template inline void destroyArenaElement(void *const Arena, const std::vector &Offsets) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); static_cast( static_cast(static_cast(Arena) + Offsets[Pos])) ->~Type(); destroyArenaElement(Arena, Offsets); } ///@} /// Implementation of the template. /// /// \tparam Types types of values to destroy /// /// \param Arena the memory area to destroy values from /// /// \pre \p Arena is not \c nullptr. template inline void destroyArenaElements(void *const Arena) noexcept { ASSERT(Arena != nullptr); destroyArenaElement<0, Types...>(Arena, TokenizedStorage::Offsets); } } // End namespace /// Implementation of the template \c rosa::TokenizedStorage as a /// specialization of \c rosa::AbstractTokenizedStorage. /// /// The class provides facilities for storing values and providing type-safe /// access to them. /// /// \tparam Types types of values to store template class TokenizedStorage : public AbstractTokenizedStorage { public: /// \c rosa::Token for the stored values. static constexpr Token ST = TypeToken...>::Value; /// Byte offsets to access stored values in \c rosa::TokenizedStorage::Arena. static const std::vector Offsets; private: /// A BLOB storing all the values one after the other. void *const Arena; /// Generates byte offsets for accessing values stored in /// \c rosa::TokenizedStorage::Arena. /// /// \return \c std::vector containing byte offsets for accessing values stored /// in \c rosa::TokenizedStorage::Arena static std::vector offsets(void) noexcept { Token T = ST; // Need a mutable copy. const token_size_t N = lengthOfToken(T); // Number of types encoded in \c T. std::vector O(N); // Allocate vector of proper size. // Do nothing for 0 elements. if (N > 0) { token_size_t I = 0; // Start indexing from position \c 0. O[0] = 0; // First offset is always \c 0. while (I < N - 1) { ASSERT(I + 1 < O.size() && lengthOfToken(T) == N - I); // Calculate next offset based on the previous one. // \note The offset of the last value is stored at `O[N - 1]`, which is // set when `I == N - 2`. Hence the limit of the loop. O[I + 1] = O[I] + sizeOfHeadOfToken(T); dropHeadOfToken(T), ++I; } ASSERT(I + 1 == O.size() && lengthOfToken(T) == 1); } return O; } public: /// Creates an instance with default values. /// /// \note This constructor requires that all actual template arguments \p /// Types... are default constructible. TokenizedStorage(void) noexcept : Arena(::operator new(sizeOfValuesOfToken(ST))) { ASSERT(Arena != nullptr); // Sanity check. createArenaElements(Arena, Types()...); } /// Creates an instance from constant lvalue references. /// /// \param Ts values to store TokenizedStorage(const std::decay_t &... Ts) noexcept : Arena(::operator new(sizeOfValuesOfToken(ST))) { ASSERT(Arena != nullptr); // Sanity check. createArenaElements(Arena, Ts...); } /// Creates an instance from rvalue references. /// /// \param Ts values to store TokenizedStorage(std::decay_t &&... Ts) noexcept : Arena(::operator new(sizeOfValuesOfToken(ST))) { ASSERT(Arena != nullptr); // Sanity check. createArenaElements(Arena, std::move(Ts)...); } /// No copying and moving of \c rosa::TokenizedStorage instances. /// /// \note This restriction may be relaxed as moving should be easy to /// implement, only requires the possiblity to validate Arena pointer. ///@{ TokenizedStorage(const TokenizedStorage&) = delete; TokenizedStorage &operator=(const TokenizedStorage&) = delete; TokenizedStorage(TokenizedStorage&& Other) = delete; TokenizedStorage &operator=(TokenizedStorage&&) = delete; ///@} // Destroys \p this object. ~TokenizedStorage(void) { destroyArenaElements...>(Arena); ::operator delete(Arena); } /// Tells how many values are stored in \p this object. /// /// \return number of values stored in \p this object size_t size(void) const noexcept override { return Offsets.size(); } /// Tells the type of the value stored at a position. /// /// \param Pos the index of the value whose type is to returned /// /// \return \c rosa::TypeNumber for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < size() /// \endcode TypeNumber typeAt(const token_size_t Pos) const noexcept override { ASSERT(Pos < size()); Token TT = ST; dropNOfToken(TT, Pos); return headOfToken(TT); } /// Provides an untyped pointer for the value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < size() /// \endcode void *pointerTo(const token_size_t Pos) noexcept override { ASSERT(Pos < size()); return static_cast(Arena) + Offsets[Pos]; } /// Provides a constant untyped pointer for the value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return constant untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < Offsets.size() /// \endcode const void *pointerTo(const token_size_t Pos) const noexcept override { ASSERT(Pos < size()); return static_cast(Arena) + Offsets[Pos]; } /// Tells if the value stored at a given index is of a given type. /// /// \note Any \c rosa::AtomConstant is encoded in \c rosa::Token as /// the \c rosa::AtomValue wrapped into it. /// /// \tparam T type to match against /// /// \param Pos index the type of the value at is to be matched against \p Type /// /// \return if the value at index \p Pos of type \p T /// /// \pre \p Pos is a valid index:\code /// Pos < Offsets.size() /// \endcode template bool isTypeAt(const size_t Pos) const noexcept { ASSERT(Pos < size()); Token TT = ST; dropNOfToken(TT, Pos); return isHeadOfTokenTheSameType(TT); } /// Gives a reference of a value of a given type stored at a given index. /// /// \note The constant variant of the function relies on this implementation, /// the function may not modify \p this object! /// /// \tparam T type to give a reference of /// /// \param Pos index to set the reference for /// /// \return reference of \p T for the value stored at index \p Pos /// /// \pre \p Pos is a valid index and the value at index \p Pos is of type /// \p T: /// \code /// Pos < Size && isTypeAt(Pos) /// \endcode template T &valueAt(const token_size_t Pos) noexcept { ASSERT(Pos < size() && isTypeAt(Pos)); return *static_cast(pointerTo(Pos)); } /// Gives a constant reference of a value of a given type stored at a given /// index. /// /// \tparam T type to give a reference of /// /// \param Pos index to set the reference for /// /// \return constant reference of \p T for the value stored at index \p Pos /// /// \pre \p Pos is a valid index and the value at index \p Pos is of type /// \p T: /// \code /// Pos < Size && isTypeAt(Pos) /// \endcode template const T &valueAt(const token_size_t Pos) const noexcept { // \note Just use the non-const implementation as that does not modify // \p this object. return const_cast(this)->valueAt(Pos); } }; // Implementation of the static member field \c rosa::TokenizedStorage::Offsets. template const std::vector TokenizedStorage::Offsets = TokenizedStorage::offsets(); /// Specialization of the template \c rosa::TokenizedStorage for storing /// nothing. /// /// \note The specialization implements the interface defined by \c /// rosa::AbstractTokenizedStorage but most of the functions cannot be called /// because nothing is stored in instances of the class. template <> class TokenizedStorage<> : public AbstractTokenizedStorage { public: /// \c rosa::Token for the stored values. static constexpr Token ST = TypeToken<>::Value; /// Byte offsets to access stored values in \c rosa::TokenizedStorage::Arena. static const std::vector Offsets; /// Creates an instance. TokenizedStorage(void) noexcept {} /// No copying and moving of \c rosa::TokenizedStorage instances. /// /// \note This restriction may be relaxed as moving should be easy to /// implement, only requires the possiblity to validate Arena pointer. ///@{ TokenizedStorage(const TokenizedStorage &) = delete; TokenizedStorage &operator=(const TokenizedStorage &) = delete; TokenizedStorage(TokenizedStorage &&Other) = delete; TokenizedStorage &operator=(TokenizedStorage &&) = delete; ///@} // Destroys \p this object. ~TokenizedStorage(void) {} /// Tells how many values are stored in \p this object. /// /// \return `0` size_t size(void) const noexcept override { return 0; } /// Tells the type of the value stored at a position. /// /// \pre Do not call. TypeNumber typeAt(const token_size_t) const noexcept override { ASSERT(false); return TypeNumber(0); } /// Provides an untyped pointer for the value stored at a position. /// /// \pre Do not call. void *pointerTo(const token_size_t) noexcept override { ASSERT(false); return nullptr; } /// Provides a constant untyped pointer for the value stored at a position. /// /// \pre Do not call. const void *pointerTo(const token_size_t) const noexcept override { ASSERT(false); return nullptr; } /// Tells if the value stored at a given index is of a given type. /// /// \pre Do not call. template bool isTypeAt(const size_t) const noexcept { ASSERT(false); return false; } /// Gives a reference of a value of a given type stored at a given index. /// /// \tparam T type to give a reference of /// \pre Do not call. template T &valueAt(const token_size_t) noexcept { ASSERT(false); return *static_cast(nullptr); } /// Gives a constant reference of a value of a given type stored at a given /// index. /// /// \tparam T type to give a reference of /// /// \pre Do not call. template const T &valueAt(const token_size_t) const noexcept { // \note Just use the non-const implementation as that does not modify // \p this object. return *static_cast(nullptr); } }; } // End namespace rosa #endif // ROSA_SUPPORT_TOKENIZED_STORAGES_HPP