diff --git a/.gitignore b/.gitignore index 722d5e7..48bf31e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .vscode +build* +CMakeLists.txt.user \ No newline at end of file diff --git a/examples/agent-functionalities/agent-functionalities.cpp b/examples/agent-functionalities/agent-functionalities.cpp index 0266565..0766f28 100644 --- a/examples/agent-functionalities/agent-functionalities.cpp +++ b/examples/agent-functionalities/agent-functionalities.cpp @@ -1,190 +1,188 @@ //===-- examples/agent-functionalities/agent-functionalities.cpp *-- C++-*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file examples/agent-functionalities/agent-functionalities.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief A simple example on defining \c rosa::Agent instances using /// \c rosa::agent::Functionality object as components. /// //===----------------------------------------------------------------------===// // Make sure M_PI is available, needed for _WIN32 #define _USE_MATH_DEFINES #include #include "rosa/agent/Abstraction.hpp" +#include "rosa/agent/Confidence.hpp" #include "rosa/agent/FunctionAbstractions.hpp" #include "rosa/agent/RangeConfidence.hpp" -#include "rosa/agent/Confidence.hpp" #include "rosa/config/version.h" #include "rosa/core/Agent.hpp" #include "rosa/core/MessagingSystem.hpp" #include "rosa/support/log.h" #include "rosa/support/terminal_colors.h" #include using namespace rosa; using namespace rosa::agent; using namespace rosa::terminal; // We use pi as float rather than double, which M_PI is. constexpr float Pi = (float) M_PI; /// A dummy wrapper for testing \c rosa::MessagingSystem. /// /// \note Since we test \c rosa::MessagingSystem directly here, we need to get /// access to its protected members. That we do by imitating to be a decent /// subclass of \c rosa::MessagingSystem, while calling protected member /// functions on an object of a type from which we actually don't inherit. struct SystemTester : protected MessagingSystem { template static AgentHandle createMyAgent(MessagingSystem *S, const std::string &Name, Funs &&... Fs) { return ((SystemTester *)S)->createAgent(Name, std::move(Fs)...); } static void destroyMyAgent(MessagingSystem *S, const AgentHandle &H) { ((SystemTester *)S)->destroyUnit(unwrapAgent(H)); } }; /// A special \c rosa::Agent with its own state. class MyAgent : public Agent { public: using Tick = AtomConstant; private: enum class Categories { Bad, Normal, Good }; static const std::map CategoryNames; - History H; + StaticLengthHistory H; Confidence C; RangeAbstraction A; PartialFunction L; RangeConfidence RCL; RangeConfidence RCS; public: void handler(Tick, uint8_t V) noexcept { // Record \p V to the \c rosa::agent::History, then print state info. H << V; ASSERT(H.entry() == V); // Sanity check. LOG_INFO_STREAM << "\nNext value: " << PRINTABLE(V) << ", confidence: " << C(H) << ", category: " << CategoryNames.at(A(H.entry())) << ", partial: " << int(L(H.entry())) << ", range-confidence-linear: "; - std::map res_lin = RCL(H.entry()); - for (auto i : res_lin){ - LOG_INFO_STREAM << " " << CategoryNames.at(i.first) - << " " << i.second << "," ; + std::map ResLin = RCL(H.entry()); + for (auto Con : ResLin) { + LOG_INFO_STREAM << " " << CategoryNames.at(Con.first) << " " << Con.second + << ","; } LOG_INFO_STREAM << " range-confidence-sine: "; - std::map res_sine = RCS(H.entry()); - for (auto i : res_sine){ - LOG_INFO_STREAM << " " << CategoryNames.at(i.first) - << " " << i.second << "," ; + std::map ResSine = RCS(H.entry()); + for (auto Con : ResSine) { + LOG_INFO_STREAM << " " << CategoryNames.at(Con.first) << " " << Con.second + << ","; } LOG_INFO_STREAM << '\n'; - } MyAgent(const AtomValue Kind, const rosa::id_t Id, const std::string &Name, MessagingSystem &S) : Agent(Kind, Id, Name, S, THISMEMBER(handler)), H(), C(5, 20, 1), A({{{(uint8_t)10, (uint8_t)14}, Categories::Normal}, {{(uint8_t)15, (uint8_t)17}, Categories::Good}, {{(uint8_t)18, (uint8_t)19}, Categories::Normal}}, Categories::Bad), L({{{0, 2}, std::make_shared>(0, 1)}, {{2, 4}, std::make_shared>(2, 0)}, {{4, 6}, std::make_shared>(6, -1)}}, 0), RCL({ {Categories::Bad, PartialFunction({ {{0.f, 3.f}, std::make_shared> (0.f, 1.f/3)}, {{3.f, 6.f}, std::make_shared> (1.f, 0.f)}, {{6.f, 9.f}, std::make_shared> (3.f, -1.f/3)}, },0)}, {Categories::Normal, PartialFunction({ {{6.f, 9.f}, std::make_shared> (-2.f, 1.f/3)}, {{9.f, 12.f}, std::make_shared> (1.f, 0.f)}, {{12.f, 15.f}, std::make_shared> (5.f, -1.f/3)}, },0)}, {Categories::Good, PartialFunction({ {{12.f, 15.f}, std::make_shared> (-4.f, 1.f/3)}, {{15.f, 18.f}, std::make_shared> (1.f, 0.f)}, {{18.f, 21.f}, std::make_shared> (7.f, -1.f/3)}, },0)} }), RCS({ {Categories::Bad, PartialFunction({ {{0.f, 3.f}, std::make_shared> (Pi/3, 0.5f, -Pi/2, 0.5f)}, {{3.f, 6.f}, std::make_shared>(1.f, 0.f)}, {{6.f, 9.f}, std::make_shared> (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)}, },0)}, {Categories::Normal, PartialFunction({ {{6.f, 9.f}, std::make_shared> (Pi/3, 0.5f, -Pi/2, 0.5f)}, {{9.f, 12.f}, std::make_shared>(1.f, 0.f)}, {{12.f, 15.f}, std::make_shared> (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)}, },0)}, {Categories::Good, PartialFunction({ {{12.f, 15.f}, std::make_shared> (Pi/3, 0.5f, -Pi/2, 0.5f)}, {{15.f, 18.f}, std::make_shared>(1.f, 0.f)}, {{18.f, 21.f}, std::make_shared> (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)}, },0)} }, true){} }; const std::map MyAgent::CategoryNames{ {Categories::Bad, "Bad"}, {Categories::Normal, "Normal"}, {Categories::Good, "Good"}}; int main(void) { - LOG_INFO_STREAM << library_string() << " -- " << Color::Red - << "agent-functionalities example" << Color::Default - << '\n'; + LOG_INFO_STREAM << library_string() << " -- " << Color::Red + << "agent-functionalities example" << Color::Default << '\n'; std::unique_ptr S = MessagingSystem::createSystem("Sys"); MessagingSystem *SP = S.get(); AgentHandle A = SystemTester::createMyAgent(SP, "MyAgent"); - std::vector Vs{0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 13, - 15, 14, 15, 16, 19, 20, 21}; + std::vector Vs{0, 1, 2, 3, 4, 5, 6, 7, 9, 10, + 11, 13, 15, 14, 15, 16, 19, 20, 21}; for (auto I = Vs.begin(); I != Vs.end(); ++I) { A.send(MyAgent::Tick::Value, *I); } SystemTester::destroyMyAgent(SP, A); return 0; } diff --git a/include/rosa/agent/Confidence.hpp b/include/rosa/agent/Confidence.hpp index 3d07606..35b7496 100644 --- a/include/rosa/agent/Confidence.hpp +++ b/include/rosa/agent/Confidence.hpp @@ -1,204 +1,202 @@ //===-- rosa/agent/Confidence.hpp -------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/Confidence.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Definition of *confidence* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_CONFIDENCE_HPP #define ROSA_AGENT_CONFIDENCE_HPP #include "rosa/agent/History.hpp" #include "rosa/support/debug.hpp" #include namespace rosa { namespace agent { /// Confidence validator. /// /// Checks the plausibility of given values by validating if a valid region /// contains them. It also capable of checking consistency by validating the /// rate of change recorded by a \c rosa::agent::History object against a /// maximal absolute valid rate of change. /// /// \tparam T type of values to validate /// /// \note The template is defined only for arithmetic types. /// /// \note The lower bound is inclusive and the upper bound is exclusive. /// /// \invariant The bounds are defined in a meaningful way:\code /// LowerBound <= UpperBound /// \endcode template class Confidence : public Functionality { // Make sure the actual type argument is an arithmetic type. STATIC_ASSERT(std::is_arithmetic::value, "not arithmetic Confidence"); public: /// Unsigned type corresponding to \p T. using UT = unsigned_t; /// The minimal value of type \p T. /// \note Not exist \c V of type \p T such that `V < Min`. static constexpr T Min = std::is_integral::value ? std::numeric_limits::min() : std::numeric_limits::lowest(); /// The maximal value of type \p T. /// \note Not exist \c V of type \p T such that `V > Max`. static constexpr T Max = (std::is_integral::value || !std::numeric_limits::has_infinity) ? std::numeric_limits::max() : std::numeric_limits::infinity(); /// The maximal value of type \c UT. /// \note Not exist \c V of type \c UT such that `V > UnsignedMax`. static constexpr UT UnsignedMax = (std::is_integral::value || !std::numeric_limits::has_infinity) ? std::numeric_limits::max() : std::numeric_limits::infinity(); private: /// The inclusive lower bound for plausibility check. T LowerBound; /// The exclusive upper bound for plausibility check. T UpperBound; /// The maximal absolute rate of change for consistency check. UT ChangeRate; public: /// Creates an instance by setting the validator variables. /// /// \param LowerBound the lower bound for plausability check /// \param UpperBound the upper bound for plausability check /// \param ChangeRate maximal absolute rate of change for consistency check /// /// \pre The bounds are defined in a meaningful way:\code /// LowerBound <= UpperBound /// \endcode Confidence(const T LowerBound = Min, const T UpperBound = Max, const UT ChangeRate = UnsignedMax) noexcept - : LowerBound(LowerBound), - UpperBound(UpperBound), - ChangeRate(ChangeRate) { + : LowerBound(LowerBound), UpperBound(UpperBound), ChangeRate(ChangeRate) { // Make sure Confidence is created in a valid state. if (LowerBound > UpperBound) { ROSA_CRITICAL("Confidence with LowerBound higher than UpperBound"); } } /// Destroys \p this object. ~Confidence(void) = default; /// Gives a snapshot of the current state of the validator variables. /// /// \param [out] LowerBound to copy \c rosa::agent::Confidence::LowerBound /// into /// \param [out] UpperBound to copy \c rosa::agent::Confidence::UpperBound /// into /// \param [out] ChangeRate to copy \c rosa::agent::Confidence::ChangeRate /// into void getParameters(T &LowerBound, T &UpperBound, UT &ChangeRate) const noexcept { // Copy members to the given references. LowerBound = this->LowerBound; UpperBound = this->UpperBound; ChangeRate = this->ChangeRate; } /// Sets the lower bound for plausability check. /// /// Beyond setting the lower bound, the function also adjusts the upper bound /// to the given lower bound if the new lower bound would be higher than the /// upper bound. /// /// \param LowerBound the new lower bound to set void setLowerBound(const T LowerBound) noexcept { // Adjust UpperBound if necessary, then set LowerBound. if (UpperBound < LowerBound) { UpperBound = LowerBound; } this->LowerBound = LowerBound; } /// Sets the upper bound for plausability check. /// /// Beyond setting the upper bound, the function also adjusts the lower bound /// to the given upper bound if the new upper bound would be lower than the /// lower bound. /// /// \param UpperBound the new upper bound to set void setUpperBound(const T UpperBound) noexcept { // Adjust LowerBound if necessary, then set UpperBound. if (UpperBound < LowerBound) { LowerBound = UpperBound; } this->UpperBound = UpperBound; } /// Sets the maximal rate of change for consistency check. /// /// \param ChangeRate the new rate of change to set void setChangeRate(const UT ChangeRate) noexcept { // Set ChangeRate. this->ChangeRate = ChangeRate; } /// Tells the binary confidence on the plausibility of a value. /// /// \param V value to check /// /// \return whether \c V is within the range defined by /// \c rosa::agent::Confidence::LowerBound and /// \c rosa::agent::Confidence::UpperBound. bool operator()(const T V) const noexcept { // Return if \c V is plausible. return LowerBound <= V && V < UpperBound; } /// Tells the binary confidence on the plausibility and consistency of the /// last value recorded by a \c rosa::agent::History instance. /// /// Consistency of the last value is checked by validating the difference with /// its preceding entry. /// /// \note The \c rosa::agent::History instance needs to store values of type /// \p T. /// /// \note An empty \c rosa::agent::History instance results in full /// confidence. /// /// \tparam N number of values \p H is able to store /// \tparam P retention policy followed by \p H when capacity is reached /// /// \param H *history* whose last entry to check template - bool operator()(const History &H) const noexcept { + bool operator()(const StaticLengthHistory &H) const noexcept { if (H.empty()) { // No entry to validate. return true; } else { // Validate the last entry and the one step average absolute difference. return (*this)(H.entry()) && H.averageAbsDiff(1) <= ChangeRate; } } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_CONFIDENCE_HPP diff --git a/include/rosa/agent/FunctionAbstractions.hpp b/include/rosa/agent/FunctionAbstractions.hpp index 9a7127a..59200de 100644 --- a/include/rosa/agent/FunctionAbstractions.hpp +++ b/include/rosa/agent/FunctionAbstractions.hpp @@ -1,224 +1,222 @@ //===-- rosa/agent/FunctionAbstractions.hpp ---------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/FunctionAbstractions.hpp /// /// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at) /// /// \date 2019 /// /// \brief Definition of *FunctionAbstractions* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP #define ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP -#include "rosa/agent/Functionality.h" #include "rosa/agent/Abstraction.hpp" +#include "rosa/agent/Functionality.h" #include "rosa/support/debug.hpp" #include -#include #include #include +#include namespace rosa { namespace agent { /// Implements \c rosa::agent::Abstraction as a linear function, /// y = Coefficient * X + Intercept. /// /// \note This implementation is supposed to be used to represent a linear /// function from an arithmetic domain to an arithmetic range. This is enforced /// statically. /// /// \tparam D type of the functions domain /// \tparam R type of the functions range -template class LinearFunction : - public Abstraction{ +template +class LinearFunction : public Abstraction { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), - "LinearFunction not arithmetic T"); + "LinearFunction not arithmetic T"); STATIC_ASSERT((std::is_arithmetic::value), - "LinearFunction not to arithmetic"); + "LinearFunction not to arithmetic"); + protected: /// The Intercept of the linear function const D Intercept; /// The Coefficient of the linear function const D Coefficient; public: /// Creates an instance. /// /// \param Intercept the intercept of the linear function /// \param Coefficient the coefficient of the linear function LinearFunction(D Intercept, D Coefficient) noexcept - : Abstraction(Intercept), - Intercept(Intercept), + : Abstraction(Intercept), Intercept(Intercept), Coefficient(Coefficient) {} /// Destroys \p this object. ~LinearFunction(void) = default; /// Checks wether the Abstraction evaluates to default at the given position /// As LinearFunctions can be evaluated everythwere, this is always false /// /// \param V the value at which to check if the function falls back to it's /// default value. /// /// \return false bool isDefaultAt(const D &V) const noexcept override { (void)V; return false; } /// Evaluates the linear function /// /// \param X the value at which to evaluate the function /// /// \return Coefficient*X + Intercept virtual R operator()(const D &X) const noexcept override { - return Intercept + X*Coefficient; + return Intercept + X * Coefficient; } }; /// Implements \c rosa::agent::Abstraction as a sine function, /// y = Amplitude * sin(Frequency * X + Phase) + Average. /// /// \note This implementation is supposed to be used to represent a sine /// function from an arithmetic domain to an arithmetic range. This is enforced /// statically. /// /// \tparam D type of the functions domain /// \tparam R type of the functions range -template class SineFunction : - public Abstraction{ +template +class SineFunction : public Abstraction { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), - "SineFunction not arithmetic T"); + "SineFunction not arithmetic T"); STATIC_ASSERT((std::is_arithmetic::value), - "SineFunction not to arithmetic"); + "SineFunction not to arithmetic"); + protected: /// The frequency of the sine wave const D Frequency; /// The Ampiltude of the sine wave const D Amplitude; /// The Phase-shift of the sine wave const D Phase; /// The y-shift of the sine wave const D Average; public: /// Creates an instance. /// /// \param Frequency the frequency of the sine wave /// \param Amplitude the amplitude of the sine wave /// \param Phase the phase of the sine wave /// \param Average the average of the sine wave SineFunction(D Frequency, D Amplitude, D Phase, D Average) noexcept - : Abstraction(Average), - Frequency(Frequency), - Amplitude(Amplitude), - Phase(Phase), - Average(Average) {} + : Abstraction(Average), Frequency(Frequency), Amplitude(Amplitude), + Phase(Phase), Average(Average) {} /// Destroys \p this object. ~SineFunction(void) = default; /// Checks wether the Abstraction evaluates to default at the given position /// As SineFunctions can be evaluated everythwere, this is always false /// /// \param V the value at which to check if the function falls back to it's /// default value. /// /// \return false bool isDefaultAt(const D &V) const noexcept override { (void)V; return false; } /// Evaluates the sine function /// /// \param X the value at which to evaluate the function /// \return the value of the sine-function at X virtual R operator()(const D &X) const noexcept override { - return Amplitude*sin(Frequency * X + Phase) + Average; + return Amplitude * sin(Frequency * X + Phase) + Average; } }; /// Implements \c rosa::agent::Abstraction as a partial function from a domain // /to a range. /// /// \note This implementation is supposed to be used to represent a partial /// function from an arithmetic domain to an arithmetic range. This is enforced /// statically. /// /// A partial function is defined as a list of abstractions, where each /// abstraction is associated a range in which it is defined. These ranges must /// be mutually exclusive. /// /// \tparam D type of the functions domain /// \tparam R type of the functions range template class PartialFunction : public Abstraction { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), "abstracting not arithmetic"); STATIC_ASSERT((std::is_arithmetic::value), - "abstracting not to arithmetic"); + "abstracting not to arithmetic"); private: /// A \c rosa::agent::RangeAbstraction RA is used to represent the association /// from ranges to Abstractions. /// This returns the Abstraction that is defined for any given value, or /// a default Abstraction if no Abstraction is defined for that value. RangeAbstraction>> RA; public: /// Creates an instance by Initializing the underlying \c Abstraction. /// /// \param Map the mapping to do abstraction according to /// \param Default abstraction to abstract to by default /// /// \pre Each key defines a valid range such that `first <= second` and /// there are no overlapping ranges defined by the keys. - PartialFunction(const std::map, - std::shared_ptr>> &Map, - const R Default) + PartialFunction( + const std::map, std::shared_ptr>> &Map, + const R Default) : Abstraction(Default), - RA(Map, std::shared_ptr> - (new Abstraction(Default))) { + RA(Map, + std::shared_ptr>(new Abstraction(Default))) { } /// Destroys \p this object. ~PartialFunction(void) = default; /// Checks wether the Abstraction evaluates to default at the given position /// /// \param V the value at which to check if the function falls back to it's /// default value. /// /// \return false if the value falls into a defined range and the Abstraction /// defined for that range does not fall back to it's default value. bool isDefaultAt(const D &V) const noexcept override { return RA.isDefaultAt(V) ? true : RA(V)->isDefaultAt(V); } /// Searches for an Abstraction for the given value and executes it for that /// value, if such an Abstraction is found. The default Abstraction is /// evaluated otherwise. /// /// \param V value to abstract /// /// \return the abstracted value based on the set mapping R operator()(const D &V) const noexcept override { return RA(V)->operator()(V); } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP diff --git a/include/rosa/agent/History.hpp b/include/rosa/agent/History.hpp index 1097ad0..5593cb7 100644 --- a/include/rosa/agent/History.hpp +++ b/include/rosa/agent/History.hpp @@ -1,292 +1,528 @@ //===-- rosa/agent/History.hpp ----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/History.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Definition of *history* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_HISTORY_HPP #define ROSA_AGENT_HISTORY_HPP #include "rosa/agent/Functionality.h" #include "rosa/config/config.h" #include "rosa/support/debug.hpp" #include "rosa/support/type_helper.hpp" #include +#include namespace rosa { namespace agent { /// Retention policies defining what a \c rosa::agent::History instance should /// do when the number of recorded entries reached its capacity. enum class HistoryPolicy { SRWF, ///< Stop Recording When Full -- no new entry is recorded when full - FIFO ///< First In First Out -- overwrite the earliest entry with a new one + FIFO, ///< First In First Out -- overwrite the earliest entry with a new one + LIFO ///< Last In First Out -- overwrite the latest entry with a new one }; -/// Implements *history* by recording and storing values. -/// -/// \note Not thread-safe implementation, which should not be a problem as any -/// instance of \c rosa::agent::Functionality is an internal component of a -/// \c rosa::Agent, which is the basic unit of concurrency. -/// -/// \tparam T type of values to store -/// \tparam N number of values to store at most -/// \tparam P retention policy to follow when capacity is reached -/// -/// \invariant The size of the underlying \c std::array is `N + 1`:\code -/// max_size() == N + 1 && N == max_size() - 1 -/// \endcode -template -class History : public Functionality, private std::array { - - // Bring into scope inherited functions that are used. - using std::array::max_size; - using std::array::operator[]; - - /// The index of the first data element in the circular buffer. - size_t Data; - - /// The index of the first empty slot in the circular buffer. - size_t Space; +template class History : public Functionality { public: - /// Creates an instances by initializing the indices for the circular buffer. - History(void) noexcept : Data(0), Space(0) {} + History(void) noexcept {} /// Destroys \p this object. - ~History(void) = default; + virtual ~History(void) = default; /// Tells the retention policy applied to \p this object. /// /// \return \c rosa::agent::History::P - static constexpr HistoryPolicy policyOfHistory(void) noexcept { return P; } + static constexpr HistoryPolicy policy(void) noexcept { return P; } /// Tells how many entries may be recorded by \c this object. /// /// \note The number of entries that are actually recorded may be smaller. /// - /// \return \c rosa::agent::History::N - static constexpr size_t lengthOfHistory(void) noexcept { return N; } + /// \return The max number of entries that may be recorded + virtual size_t maxLength(void) const noexcept = 0; /// Tells how many entries are currently recorded by \p this object. /// /// \return number of entries currently recorded by \p this object. /// /// \post The returned value cannot be larger than the capacity of \p this /// object:\code /// 0 <= numberOfEntries() && numberOfEntries <= lengthOfHistory() /// \endcode - size_t numberOfEntries(void) const noexcept { - return Data <= Space ? Space - Data : max_size() - Data + Space; - } + virtual size_t numberOfEntries(void) const noexcept = 0; /// Tells if \p this object has not recorded anything yet. /// /// \return if \p this object has no entries recorded bool empty(void) const noexcept { return numberOfEntries() == 0; } + /// Tells if the history reached it's maximum length + /// + /// \return if the history reached it's maximum length. + bool full(void) const noexcept { return numberOfEntries() == maxLength(); } + /// Gives a constant lvalue reference to an entry stored in \p this object. /// /// \note The recorded entries are indexed starting from the latest one. /// /// \param I the index at which the stored entry to take from /// /// \pre \p I is a valid index:\code - /// 0 <= I && I <= numberOfEntries() + /// 0 <= I && I < numberOfEntries() /// \endcode - const T &entry(const size_t I = 0) const noexcept { - ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. - // Position counted back from the last recorded entry. - typename std::make_signed::type Pos = Space - (1 + I); - // Actual index wrapped around to the end of the buffer if negative. - return (*this)[Pos >= 0 ? Pos : max_size() + Pos]; - } + virtual const T &entry(const size_t I = 0) const noexcept = 0; + + /// Removes all entries recorded in \p this object. + virtual void clear() noexcept = 0; private: - /// Tells if the circular buffer is full. + /// Pushes a new entry into the history. /// - /// \return if the circular buffer is full. - bool full(void) const noexcept { return numberOfEntries() == N; } - - /// Pushes a new entry into the circular buffer. + /// \note The earliest entry gets overwritten if the history is full. /// - /// \note The earliest entry gets overwritten if the buffer is full. + /// \param V value to push into the history + virtual void pushBack(const T &V) noexcept = 0; + + /// Replaces the most recent entry in the history. /// - /// \param V value to push into the buffer - void pushBack(const T &V) noexcept { - // Store value to the first empty slot and step Space index. - (*this)[Space] = V; - Space = (Space + 1) % max_size(); - if (Data == Space) { - // Buffer was full, step Data index. - Data = (Data + 1) % max_size(); - } - } + /// \param V value to replace the most current value with + virtual void replaceFront(const T &V) noexcept = 0; public: /// Adds a new entry to \p this object and tells if the operation was /// successful. /// /// \note Success of the operation depends on the actual policy. /// /// \param V value to store /// /// \return if \p V was successfully stored bool addEntry(const T &V) noexcept { switch (P) { default: ROSA_CRITICAL("unkown HistoryPolicy"); + case HistoryPolicy::LIFO: + if (full()) { + replaceFront(V); + return true; + } case HistoryPolicy::SRWF: if (full()) { return false; } // \note Fall through to FIFO which unconditionally pushes the new entry. case HistoryPolicy::FIFO: // FIFO and SRWF not full. pushBack(V); return true; } } /// Tells the trend set by the entries recorded by \p this object. /// /// The number of steps to go back when calculating the trend is defined as /// argument to the function. /// /// \note The number of steps that can be made is limited by the number of /// entries recorded by \p this object. /// /// \note The function is made a template only to be able to use /// \c std::enable_if. /// /// \tparam X always use the default! /// /// \param D number of steps to go back in *history* /// /// \return trend set by analyzed entries /// /// \pre Statically, \p this object stores signed arithmetic values:\code /// std::is_arithmetic::value && std::is_signed::value /// \endcode Dynamically, \p D is a valid number of steps to take:\code - /// 0 <= D && D < N + /// 0 <= D && D < lengthOfHistory() /// \endcode template typename std::enable_if< std::is_arithmetic::value && std::is_signed::value, X>::type - trend(const size_t D = N - 1) const noexcept { + trend(const size_t D) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); - ASSERT(0 <= D && D < N); // Boundary check. + ASSERT(0 <= D && D < maxLength()); // Boundary check. if (numberOfEntries() < 2 || D < 1) { // No entries for computing trend. return {}; // Zero element of \p T } else { // Here at least two entries. // \c S is the number of steps that can be done. const size_t S = std::min(numberOfEntries() - 1, D); size_t I = S; // Compute trend with linear regression. size_t SumIndices = 0; T SumEntries = {}; T SumSquareEntries = {}; T SumProduct = {}; while (I > 0) { // \note Indexing for the regression starts in the past. const size_t Index = S - I; const T Entry = entry(--I); SumIndices += Index; SumEntries += Entry; SumSquareEntries += Entry * Entry; SumProduct += Entry * Index; } return (SumProduct * S - SumEntries * SumIndices) / (SumSquareEntries * S - SumEntries * SumEntries); } } /// Tells the average absolute difference between consecutive entries recorded /// by \p this object /// The number of steps to go back when calculating the average is defined as /// argument to the function. /// /// \note The number of steps that can be made is limited by the number of /// entries recorded by \p this object. /// /// \note The function is made a template only to be able to use /// \c std::enable_if. /// /// \tparam X always use the default! /// /// \param D number of steps to go back in *history* /// /// \pre Statically, \p this object stores arithmetic values:\code /// std::is_arithmetic::value /// \endcode Dynamically, \p D is a valid number of steps to take:\code - /// 0 <= D && D < N + /// 0 <= D && D < lengthOfHistory() /// \endcode template typename std::enable_if::value, size_t>::type - averageAbsDiff(const size_t D = N - 1) const noexcept { + averageAbsDiff(const size_t D) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); - ASSERT(0 <= D && D < N); // Boundary check. + ASSERT(0 <= D && D < maxLength()); // Boundary check. if (numberOfEntries() < 2 || D < 1) { // No difference to average. return {}; // Zero element of \p T } else { // Here at least two entries. // \c S is the number of steps that can be done. const size_t S = std::min(numberOfEntries() - 1, D); // Sum up differences as non-negative values only, hence using an // unsigned variable for that. size_t Diffs = {}; // Init to zero. // Count down entry indices and sum up all the absolute differences. size_t I = S; T Last = entry(I); while (I > 0) { T Next = entry(--I); Diffs += Last < Next ? Next - Last : Last - Next; Last = Next; } // Return the average of the summed differences. return Diffs / S; } } + + /// Tells the average of all entries recorded by \p this object + /// + /// \tparam R type of the result + template R average() const noexcept { + R Average = 0; + for (size_t I = 0; I < numberOfEntries(); I++) { + Average += entry(I); + } + Average /= numberOfEntries(); + return Average; + } +}; + +/// Implements *history* by recording and storing values. +/// The length of the underlying std::array is static and must be set at +/// compile-time +/// +/// \note Not thread-safe implementation, which should not be a problem as any +/// instance of \c rosa::agent::Functionality is an internal component of a +/// \c rosa::Agent, which is the basic unit of concurrency. +/// +/// \tparam T type of values to store +/// \tparam N number of values to store at most +/// \tparam P retention policy to follow when capacity is reached +/// +/// \invariant The size of the underlying \c std::array is `N + 1`:\code +/// max_size() == N + 1 && N == max_size() - 1 +/// \endcode +template +class StaticLengthHistory : public History, private std::array { + + // Bring into scope inherited functions that are used. + using std::array::max_size; + using std::array::operator[]; + + /// The index of the first data element in the circular buffer. + size_t Data; + + /// The index of the first empty slot in the circular buffer. + size_t Space; + +public: + using History::policy; + using History::empty; + using History::full; + using History::addEntry; + using History::trend; + using History::averageAbsDiff; + + /// Creates an instances by initializing the indices for the circular buffer. + StaticLengthHistory(void) noexcept : Data(0), Space(0) {} + + /// Destroys \p this object. + ~StaticLengthHistory(void) override = default; + + /// Tells how many entries may be recorded by \c this object. + /// + /// \note The number of entries that are actually recorded may be smaller. + /// + /// \return \c rosa::agent::History::N + size_t maxLength(void) const noexcept override { return N; } + + /// Tells how many entries are currently recorded by \p this object. + /// + /// \return number of entries currently recorded by \p this object. + /// + /// \post The returned value cannot be larger than the capacity of \p this + /// object:\code + /// 0 <= numberOfEntries() && numberOfEntries <= lengthOfHistory() + /// \endcode + size_t numberOfEntries(void) const noexcept override { + return Data <= Space ? Space - Data : max_size() - Data + Space; + } + + /// Gives a constant lvalue reference to an entry stored in \p this object. + /// + /// \note The recorded entries are indexed starting from the latest one. + /// + /// \param I the index at which the stored entry to take from + /// + /// \pre \p I is a valid index:\code + /// 0 <= I && I < numberOfEntries() + /// \endcode + const T &entry(const size_t I = 0) const noexcept override { + ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. + // Position counted back from the last recorded entry. + typename std::make_signed::type Pos = Space - (1 + I); + // Actual index wrapped around to the end of the buffer if negative. + return (*this)[Pos >= 0 ? Pos : max_size() + Pos]; + } + + /// Removes all entries recorded in \p this object. + void clear() noexcept override { + Data = 0; + Space = 0; + } + +private: + /// Pushes a new entry into the circular buffer. + /// + /// \note The earliest entry gets overwritten if the buffer is full. + /// + /// \param V value to push into the buffer + void pushBack(const T &V) noexcept override { + // Store value to the first empty slot and step Space index. + (*this)[Space] = V; + Space = (Space + 1) % max_size(); + if (Data == Space) { + // Buffer was full, step Data index. + Data = (Data + 1) % max_size(); + } + } + + /// Replaces the most recent entry in the history. + /// + /// \param V value to replace the most current value with + void replaceFront(const T &V) noexcept override { + (*this)[(Space - 1) % max_size()] = V; + } }; /// Adds a new entry to a \c rosa::agent::History instance. /// /// \note The result of \c rosa::agent::History::addEntry is ignored. /// /// \tparam T type of values stored in \p H /// \tparam N number of values \p H is able to store /// \tparam P retention policy followed by \p H when capacity is reached /// /// \param H to add a new entry to /// \param V value to add to \p H /// /// \return \p H after adding \p V to it template -History &operator<<(History &H, const T &V) noexcept { +StaticLengthHistory &operator<<(StaticLengthHistory &H, + const T &V) noexcept { H.addEntry(V); return H; } +/// Implements *DynamicLengthHistory* by recording and storing values. +/// +/// \note Not thread-safe implementation, which should not be a problem as any +/// instance of \c rosa::agent::Functionality is an internal component of a +/// \c rosa::Agent, which is the basic unit of concurrency. +/// +/// \tparam T type of values to store +/// \tparam P retention policy to follow when capacity is reached +template +class DynamicLengthHistory : public History, private std::vector { + + // Bring into scope inherited functions that are used. + using std::vector::erase; + using std::vector::begin; + using std::vector::end; + using std::vector::size; + using std::vector::max_size; + using std::vector::resize; + using std::vector::push_back; + using std::vector::pop_back; + using std::vector::operator[]; + + /// The current length of the DynamicLengthHistory. + size_t Length; + +public: + using History::policy; + using History::empty; + using History::full; + using History::addEntry; + using History::trend; + using History::averageAbsDiff; + + /// Creates an instances by setting an initial length + DynamicLengthHistory(size_t Length) noexcept : Length(Length) { + this->resize(Length); + } + + /// Destroys \p this object. + ~DynamicLengthHistory(void) override = default; + + /// Tells how many entries may be recorded by \c this object. + /// + /// \note The number of entries that are actually recorded may be smaller. + /// + /// \return \c rosa::agent::DynamicLengthHistory::N + size_t maxLength(void) const noexcept override { return Length; } + + /// Tells how many entries are currently recorded by \p this object. + /// + /// \return number of entries currently recorded by \p this object. + /// + /// \post The returned value cannot be larger than the capacity of \p this + /// object:\code + /// 0 <= numberOfEntries() && numberOfEntries <= + /// lengthOfHistory() \endcode + size_t numberOfEntries(void) const noexcept { return size(); } + + /// Gives a constant lvalue reference to an entry stored in \p this object. + /// + /// \note The recorded entries are indexed starting from the latest one. + /// + /// \param I the index at which the stored entry to take from + /// + /// \pre \p I is a valid index:\code + /// 0 <= I && I < numberOfEntries() + /// \endcode + const T &entry(const size_t I = 0) const noexcept override { + ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. + return this->operator[](size() - I - 1); + } + + /// Removes all entries recorded in \p this object. + void clear() noexcept override { erase(begin(), end()); } + +private: + /// Pushes a new entry into the circular buffer. + /// + /// \note The earliest entry gets overwritten if the buffer is full. + /// + /// \param V value to push into the buffer + void pushBack(const T &V) noexcept override { + if (full()) { + erase(begin()); + } + push_back(V); + } + + /// Replaces the most recent entry in the history. + /// + /// \param V value to replace the most current value with + void replaceFront(const T &V) noexcept override { + (void)pop_back(); + push_back(V); + } + +public: + /// Resizes the History length. If the new length is smaller than the number + /// of currently stored values, values are deleted according to the + /// HistoryPolicy. + /// + /// @param NewLength The new Length of the History. + void setLength(size_t NewLength) noexcept { + Length = NewLength; + if (NewLength < numberOfEntries()) { + switch (P) { + default: + ROSA_CRITICAL("unkown HistoryPolicy"); + case HistoryPolicy::LIFO: + case HistoryPolicy::SRWF: + // Delete last numberOfEntries() - NewLength items from the back + erase(begin() + NewLength, end()); + break; + case HistoryPolicy::FIFO: + // Delete last numberOfEntries() - NewLength items from the front + erase(begin(), begin() + (numberOfEntries() - NewLength)); + break; + } + } + this->resize(Length); + } +}; + +/// Adds a new entry to a \c rosa::agent::DynamicLengthHistory instance. +/// +/// \note The result of \c rosa::agent::DynamicLengthHistory::addEntry is +/// ignored. +/// +/// \tparam T type of values stored in \p H +/// \tparam P retention policy followed by \p H when capacity is reached +/// +/// \param H to add a new entry to +/// \param V value to add to \p H +/// +/// \return \p H after adding \p V to it +template +DynamicLengthHistory &operator<<(DynamicLengthHistory &H, + const T &V) noexcept { + H.addEntry(V); + return H; +} } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_HISTORY_HPP diff --git a/include/rosa/agent/RangeConfidence.hpp b/include/rosa/agent/RangeConfidence.hpp index 0d8732e..60a53c1 100644 --- a/include/rosa/agent/RangeConfidence.hpp +++ b/include/rosa/agent/RangeConfidence.hpp @@ -1,109 +1,108 @@ //===-- rosa/agent/RangeConfidence.hpp --------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/RangeConfidence.hpp /// /// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at) /// /// \date 2019 /// /// \brief Definition of *RangeConfidence* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_RANGECONFIDENCE_HPP #define ROSA_AGENT_RANGECONFIDENCE_HPP -#include "rosa/agent/Functionality.h" #include "rosa/agent/Abstraction.hpp" #include "rosa/agent/FunctionAbstractions.hpp" +#include "rosa/agent/Functionality.h" #include "rosa/support/debug.hpp" #include -#include #include #include +#include namespace rosa { namespace agent { /// Evaluates a map of ID's to Abstractions at a given value and returns the /// results as a map from ID's to results of the corresponding Abstraction /// /// \note This implementation is supposed to be used to abstract ranges of /// arithmetic types into maps whose values are of another arithmetic type, /// which is statically enforced. /// /// \tparam D type to abstract from /// \tparam I type the type of the ID's /// \tparam R type of the range template class RangeConfidence : protected Abstraction>, - private std::map>{ + private std::map> { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), "abstracting not arithmetic"); STATIC_ASSERT((std::is_arithmetic::value), - "abstracting not to arithmetic"); + "abstracting not to arithmetic"); private: /// Wether to include default results in the result-map or not bool IgnoreDefaults; public: /// Creates an instance by Initializing the underlying \c Abstraction and /// \c std::map. /// /// \param Abstractions the Abstractions to be evaluated /// \param IgnoreDefaults wether to include default results in the result-map /// or not (defaults to false). RangeConfidence(const std::map> &Abstractions, - bool IgnoreDefaults = false) - : Abstraction>({}), - std::map>(Abstractions), - IgnoreDefaults(IgnoreDefaults){ - } + bool IgnoreDefaults = false) + : Abstraction>({}), std::map>( + Abstractions), + IgnoreDefaults(IgnoreDefaults) {} /// Destroys \p this object. ~RangeConfidence(void) = default; /// Checks wether all Abstractions evaluate to default at the given position /// /// \param V the value at which to check if the functions falls back to it's /// default value. /// /// \return true, if all Abstractions evaluate to default bool isDefaultAt(const D &V) const noexcept override { - for (auto const& p : ((std::map>)*this)){ - if(!p.second.isDefaultAt(V)) - return false; - } + for (auto const &P : ((std::map>)*this)) { + if (!P.second.isDefaultAt(V)) + return false; + } return true; } /// All Abstractions stored in the underlying \c std::map are evaluated for /// the given value. Their results are stored in another map, with /// corresponding keys. /// If IgnoreDefaults is set, Abstractions that default for that value are not /// evaluated and inserted into the resulting \c std::map /// /// \param V value to abstract /// /// \return a \c std::map containing the results of the stored Abstractions, /// indexable by the key's the Abstractions are associated with std::map operator()(const D &V) const noexcept override { - std::map ret; - for (auto const& p : ((std::map>)*this)){ - if(!IgnoreDefaults || !p.second.isDefaultAt(V)) - ret.insert(std::pair(p.first, p.second(V))); + std::map Ret; + for (auto const &P : ((std::map>)*this)) { + if (!IgnoreDefaults || !P.second.isDefaultAt(V)) + Ret.insert(std::pair(P.first, P.second(V))); } - return ret; + return Ret; } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_RANGECONFIDENCE_HPP diff --git a/include/rosa/core/Invoker.hpp b/include/rosa/core/Invoker.hpp index 365e133..10786aa 100644 --- a/include/rosa/core/Invoker.hpp +++ b/include/rosa/core/Invoker.hpp @@ -1,257 +1,258 @@ //===-- rosa/core/Invoker.hpp -----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/core/Invoker.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Facilities for providing actual arguments for functions as /// \c rosa::Messageobjects. /// //===----------------------------------------------------------------------===// #ifndef ROSA_CORE_INVOKER_HPP #define ROSA_CORE_INVOKER_HPP #include "rosa/core/MessageMatcher.hpp" #include "rosa/support/log.h" #include "rosa/support/sequence.hpp" #include #include namespace rosa { /// Wraps a function and provides a simple interface to invoke the stored /// function by passing actual arguments as a \c rosa::Message object. /// /// \note A \c rosa::Invoker instance is supposed to be owned by a /// \c rosa::MessageHandler instance, and not being used directly from user /// code. class Invoker { protected: /// Creates an instance. /// /// \note Protected constructor restricts instantiation to derived classes. Invoker(void) noexcept; public: /// Destroys \p this object. virtual ~Invoker(void); /// Possible results of an invocation. enum class Result { NoMatch, ///< The wrapped function could not be invoked Invoked ///< The wrapped function has been invoked }; /// Type alias for a smart-pointer for \c rosa::Invoker. using invoker_t = std::unique_ptr; /// Type alias for \c rosa::Invoker::Result. using result_t = Result; /// Tells if a \c rosa::Message object can be used to invoke the function /// wrapped in \p this object. /// /// \param Msg \c rosa::Message to check /// /// \return whether \p Msg can be used to invoke the wrapped function virtual bool match(const Message &Msg) const noexcept = 0; /// Tries to invoke the wrapped function with a \c rosa::Message object. /// /// The wrapped function is invoked if the actual \c rosa::Message object can /// be used to invoke it. /// /// \param Msg \c rosa::Message to try to invoke the wrapped function with /// /// \return whether the wrapped function could be invoked with \p Msg virtual result_t operator()(const Message &Msg) const noexcept = 0; /// Instantiates an implementation of \c rosa::Invoker with the given /// function. /// /// \note As there is no empty \c rosa::Message, no \c rosa::Invoker wraps a /// function without any argument. /// /// \todo Enforce F does not potentially throw exception. /// /// \tparam T type of the first mandatory argument /// \tparam Ts types of any further arguments /// /// \param F function to wrap /// /// \return new \c rosa::Invoker::invoker_t object created from the given /// function template static invoker_t wrap(std::function &&F) noexcept; /// Convenience template alias for casting callable stuff to function objects /// for wrapping. /// /// \tparam Ts types of arguments /// /// \todo Should make it possible to avoid using an explicit conversion for /// the arguments of wrap. template using F = std::function; /// Convenience template for preparing non-static member functions into /// function objects for wrapping. /// /// \tparam C type whose non-static member the function is /// \tparam Ts types of arguments /// /// \see \c THISMEMBER template static inline F M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept; }; /// Convenience preprocessor macro for the typical use of \c rosa::Invoker::M. /// It can be used inside a class to turn a non-static member function into a /// function object capturing this pointer, so using the actual object when /// handling a \c rosa::Message. /// /// \param FUN the non-static member function to wrap /// /// \note Inside the class \c MyClass, use\code /// THISMEMBER(fun) /// \endcode instead of\code /// Invoker::M(this, &MyClass::fun) /// \endcode #define THISMEMBER(FUN) \ Invoker::M(this, &std::decay::type::FUN) /// Nested namespace with implementation of \c rosa::Invoker and helper /// templates, consider it private. namespace { /// \defgroup InvokerImpl Implementation for rosa::Invoker /// /// Implements the \c rosa::Invoker interface for functions with different /// signatures. /// ///@{ /// Declaration of \c rosa::InvokerImpl implementing \c rosa::Invoker. /// /// \tparam Fun function to wrap template class InvokerImpl; /// Implementation of \c rosa::InvokerImpl for \c std::function. /// /// \tparam T type of the first mandatory argument /// \tparam Ts types of further arguments /// /// \note As there is no empty \c rosa::Message, no \c rosa::Invoker wraps a /// function without any argument, i.e., no /// \c std::function. template class InvokerImpl> final : public Invoker { /// Type alias for the stored function. using function_t = std::function; /// Type alias for correctly typed argument-tuples as obtained from /// \c rosa::Message. using args_t = std::tuple; /// Alias for \c rosa::MessageMatcher for the arguments of the stored /// function. using Matcher = MsgMatcher; /// The wrapped function. const function_t F; /// Invokes \c InvokerImpl::F by unpacking arguments from a \c std::tuple with /// the help of the actual template arguments. /// /// \tparam S sequence of numbers indexing \c std::tuple for arguments /// /// \param Args arguments to invoke \c InvokerImpl::F with /// /// \pre the length of \p S and size of \p Args are matching:\code /// sizeof...(S) == std::tuple_size::value /// \endcode template inline void invokeFunction(Seq, const args_t &Args) const noexcept; public: /// Creates an instance. /// /// \param F function to wrap /// /// \pre \p F is valid:\code /// bool(F) /// \endcode InvokerImpl(function_t &&F) noexcept : F(F) { ASSERT(bool(F)); // Sanity check. } /// Destroys \p this object. ~InvokerImpl(void) = default; /// Tells if a \c rosa::Message object can be used to invoke the function /// wrapped in \p this object. /// /// \param Msg \c rosa::Message to check /// /// \return whether \p Msg can be used to invoke the wrapped function bool match(const Message &Msg) const noexcept override { return Matcher::doesStronglyMatch(Msg); - }; + } /// Tries to invoke the wrapped function with a \c rosa::Message object. /// /// The wrapped function is invoked if the actual \c rosa::Message object can /// be used to invoke it. /// /// \param Msg \c rosa::Message to try to invoke the wrapped function with /// /// \return whether the wrapped function could be invoked with \p Msg result_t operator()(const Message &Msg) const noexcept override { if (match(Msg)) { LOG_TRACE("Invoking with matching arguments"); invokeFunction(seq_t(), Matcher::extractedValues(Msg)); return result_t::Invoked; } else { LOG_TRACE("Tried to invoke with non-matching arguments"); return result_t::NoMatch; } } }; template template void InvokerImpl>::invokeFunction( Seq, const args_t &Args) const noexcept { - ASSERT(sizeof...(S) == std::tuple_size::value); // Sanity check. + STATIC_ASSERT(sizeof...(S) == std::tuple_size::value, + "wrong number of type parameters"); F(std::get(Args)...); } ///@} } // End namespace template Invoker::invoker_t Invoker::wrap(std::function &&F) noexcept { return std::unique_ptr( new InvokerImpl>(std::move(F))); } template Invoker::F Invoker::M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept { return [ O, Fun ](Ts... Vs) noexcept->void { (O->*Fun)(Vs...); }; } } // End namespace rosa #endif // ROSA_CORE_INVOKER_HPP diff --git a/include/rosa/core/SystemBase.hpp b/include/rosa/core/SystemBase.hpp index a5ca0d2..4b6720e 100644 --- a/include/rosa/core/SystemBase.hpp +++ b/include/rosa/core/SystemBase.hpp @@ -1,138 +1,138 @@ //===-- rosa/core/SystemBase.hpp -----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/core/SystemBase.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Base implementation of the \c rosa::System interface. /// //===----------------------------------------------------------------------===// #ifndef ROSA_CORE_SYSTEMBASE_HPP #define ROSA_CORE_SYSTEMBASE_HPP #include "rosa/core/System.hpp" #include namespace rosa { /// Base implementation of the \c rosa::System interface. /// /// This implementation provides only equality checking and *name* for /// \c rosa::System, identifiers for \c rosa::Unit instances, and marking the /// \c rosa::System cleaned for destruction. /// /// \note Actual implementations of \c rosa::System and derived interfaces are /// supposed to inherit from this implementation. class SystemBase : public System { protected: /// Creates an instance. /// /// \note Protected constructor restrict instantiation for subclasses. /// /// \param Name name of the new instance SystemBase(const std::string &Name) noexcept; public: /// Destroys \p this object. /// /// \pre \p this object is marked cleaned:\code /// isSystemCleaned() /// \endcode ~SystemBase(void); /// Tells whether \p this object is the same as \p Other. /// /// Two \c rosa::System instances are considered equal if they share a common /// \c rosa::SystemBase::Name member field. That should do among various /// subclasses. /// /// \param Other another \c rosa::System instance to compare to /// /// \return whether \p this object and \p Other is the same bool operator==(const System &Other) const noexcept override; protected: /// The textual name of \p this object implementing \c rosa::System. const std::string Name; private: /// Number of \c rosa::Unit instances constructed by \p this object. /// /// \note Should never be decremented! std::atomic UnitCount; /// Indicates that \p this object has been cleaned and is ready for /// destruction. /// /// The field is initialized as \c false and can be set by /// \c rosa::SystemBase::markCleaned. /// /// \note Subclasses must set the flag upon destructing their instances, which /// indicates to the destructor of the base-class that all the managed /// resources has been properly released. std::atomic SystemIsCleaned; public: /// Tells the name of \p this object /// /// \note The returned reference remains valid as long as \p this object is /// not destroyed. /// /// \return reference to \c rosa::SystemBase::Name const std::string &name(void) const noexcept override; protected: /// Tells the next unique identifier to be used for a newly created /// \c rosa::Unit. /// /// The functions takes the current value of the internal counter /// \c rosa::SystemBase::UnitCount and then increments it. /// /// \note This is the only function modifying /// \c rosa::SystemBase::UnitCount. /// /// \return \c rosa::id_t which is unique within the context of \p this /// object. id_t nextId(void) noexcept override; /// Tells if \p this object has been marked cleaned and is ready for /// destruction. /// /// \return if \p this object is marked clean. bool isSystemCleaned(void) const noexcept override; /// Marks \p this object cleaned by setting /// \c rosa::SystemBase::SystemIsCleaned. /// /// \note Can be called only once when the System does not have any live /// \c rosa::Unit instances. /// /// \pre \p this object has not yet been marked as cleaned and it has no /// \c rosa::Unit instances registered:\code /// !isSystemCleaned() && empty() /// \endcode /// /// \post \p this object is marked cleaned:\code /// isSystemCleaned() /// \endcode void markCleaned(void) noexcept override; /// Tells the number of \c rosa::Unit instances constructed in the context of /// \p this object so far, including those being already destroyed. /// /// \return current value of \c rosa::SystemBase::UnitCount that is the number /// of \c rosa::Unit instances created so far size_t numberOfConstructedUnits(void) const noexcept override; }; } // End namespace rosa -#endif // ROSA_LIB_CORE_SYSTEMBASE_HPP +#endif // ROSA_CORE_SYSTEMBASE_HPP diff --git a/include/rosa/deluxe/DeluxeAgent.hpp b/include/rosa/deluxe/DeluxeAgent.hpp index a75d727..7a94a57 100644 --- a/include/rosa/deluxe/DeluxeAgent.hpp +++ b/include/rosa/deluxe/DeluxeAgent.hpp @@ -1,1478 +1,1479 @@ //===-- 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)); + STATIC_ASSERT(sizeof...(As) == sizeof...(S0), + "wrong number of type parameters"); 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::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::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/DeluxeSystem.hpp b/include/rosa/deluxe/DeluxeSystem.hpp old mode 100755 new mode 100644 index f39ce5a..bc51cdf --- a/include/rosa/deluxe/DeluxeSystem.hpp +++ b/include/rosa/deluxe/DeluxeSystem.hpp @@ -1,240 +1,240 @@ //===-- rosa/deluxe/DeluxeSystem.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeSystem.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Specialization of \c rosa::MessagingSystem for the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXESYSTEM_HPP #define ROSA_DELUXE_DELUXESYSTEM_HPP #include "rosa/core/MessagingSystem.hpp" #include "rosa/deluxe/DeluxeAgent.hpp" #include "rosa/deluxe/DeluxeSensor.hpp" namespace rosa { namespace deluxe { /// Implements and extends the \c rosa::MessagingSystem interface to be /// used by \c rosa::deluxe::DeluxeContext. /// /// The class is a specialization of \c rosa::MessagingSystem, where objects /// of two specialized subtypes of \c rosa::Agent, \c rosa::deluxe::DeluxeSensor /// and \c rosa::deluxe::DeluxeAgent, constitute a system. The class extends the /// \c rosa::MessagingSystem interface with features required to implement the /// *deluxe interface*. /// /// \see rosa::deluxe::DeluxeContext class DeluxeSystem : public MessagingSystem { friend class DeluxeContext; friend class DeluxeExecutionPolicy; public: /// Returns an object implementing the \c rosa::deluxe::DeluxeSystem /// interface. /// /// \param Name name of the new instance /// /// \return \c std::unique_ptr for the new instance of /// \c rosa::DeluxeSystem static std::unique_ptr createSystem(const std::string &Name) noexcept; protected: /// Creates a new instance. /// /// \note Protected constructor restricts instantiation for subclasses. DeluxeSystem(void) noexcept = default; public: /// Creates a \c rosa::deluxe::DeluxeSensor instance owned by \p this object /// and returns a \p rosa::AgentHandle for it. /// /// \tparam MT type of master-input the new \c rosa::deluxe::DeluxeSensor /// receives /// \tparam T type of data the new \c rosa::deluxe::DeluxeSensor operates on /// /// \note Type arguments \p MT and \p T must be instances of \c /// rosa::deluxe::DeluxeTuple. /// /// \param Name name of the new \c rosa::deluxe::DeluxeSensor /// \param MF function to process master-input values /// \param F function to generate the next value with during normal operation /// /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor. /// /// \return \c rosa::AgentHandle for new \c rosa::deluxe::DeluxeSensor template AgentHandle createSensor(const std::string &Name, std::function)> &&MF, std::function &&F) noexcept; /// Creates a \c rosa::deluxe::DeluxeAgent instance owned by \p this object /// and returns a \c rosa::AgentHandle for it. /// /// \tparam MT type of master-input the new \c rosa::deluxe::DeluxeAgent /// receives /// \tparam T type of data the new \c rosa::deluxe::DeluxeAgent outputs /// \tparam Ts types of master-output the new \c rosa::deluxe::DeluxeAgent /// produces /// \tparam As types of inputs the new \c rosa::deluxe::DeluxeAgent takes /// /// \note Type arguments \p MT, \p T, \p Ts..., and \p As... must be /// instances of \c rosa::deluxe::DeluxeTuple. /// /// \param Name name of the new \c rosa::deluxe::DeluxeAgent /// \param MF function for the new \c rosa::deluxe::DeluxeAgent to process /// master-input values and generate master-output with /// \param F function for the new \c rosa::deluxe::DeluxeAgent to process /// input values and generate output and master-output with /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for new \c rosa::deluxe::DeluxeAgent template AgentHandle createAgent( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept; protected: /// Tells whether a \c rosa::AgentHandle refers to a /// \c rosa::deluxe::DeluxeSensor owned by \p this object. /// /// \param H \c rosa::AgentHandle to check /// /// \return whether \p H refers to a \c rosa::deluxe::DeluxeSensor owned by /// \p this object virtual bool isDeluxeSensor(const AgentHandle &H) const noexcept = 0; /// Extracts a const qualified \c rosa::deluxe::DeluxeSensor reference from a /// const qualified \c rosa::AgentHandle if possible. /// /// The function returns a \c rosa::Optional object containing a const /// qualified reference to a \c rosa::deluxe::DeluxeSensor object extracted /// from a const qualified \c rosa::AgentHandle instance if the referred /// object is of type \c rosa::deluxeDeluxeSensor and owned by \p this object. /// The returned \c rosa::Optional object is empty otherwise. /// /// \see rosa::deluxe::DeluxeSystem::isDeluxeSensor /// /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeSensor /// from /// /// \return const qualified reference to \c rosa::deluxe::DeluxeSensor if /// \p H refers to an object which is of that type and is owned by \p this /// object Optional getDeluxeSensor(const AgentHandle &H) const noexcept; /// Extracts a \c rosa::deluxe::DeluxeSensor reference from a /// \c rosa::AgentHandle if possible. /// /// The function returns a \c rosa::Optional object containing a reference to /// a \c rosa::deluxe::DeluxeSensor object extracted from a /// \c rosa::AgentHandle instance if the referred object is of type /// \c rosa::deluxeDeluxeSensor and owned by \p this object. The returned /// \c rosa::Optional object is empty otherwise. /// /// \see rosa::deluxe::DeluxeSystem::isDeluxeSensor /// /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeSensor /// from /// /// \return reference to \c rosa::deluxe::DeluxeSensor if \p H refers to an /// object which is of that type and is owned by \p this object Optional getDeluxeSensor(AgentHandle &H) const noexcept; /// Tells whether a \c rosa::AgentHandle refers to a /// \c rosa::deluxe::DeluxeAgent owned by \p this object. /// /// \param H \c rosa::AgentHandle to check /// /// \return whether \p H refers to a \c rosa::deluxe::DeluxeAgent owned by /// \p this object virtual bool isDeluxeAgent(const AgentHandle &H) const noexcept = 0; /// Extracts a const qualified \c rosa::deluxe::DeluxeAgent reference from a /// const qualified \c rosa::AgentHandle if possible. /// /// The function returns a \c rosa::Optional object containing a const /// qualified reference to a \c rosa::deluxe::DeluxeAgent object extracted /// from a const qualified \c rosa::AgentHandle instance if the referred /// object is of type \c rosa::deluxeDeluxeAgent and owned by \p this object. /// The returned \c rosa::Optional object is empty otherwise. /// /// \see rosa::deluxe::DeluxeSystem::isDeluxeAgent /// /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeAgent /// from /// /// \return const qualified reference to \c rosa::deluxe::DeluxeAgent if \p H /// refers to an object which is of that type and is owned by \p this object Optional getDeluxeAgent(const AgentHandle &H) const noexcept; /// Extracts a \c rosa::deluxe::DeluxeAgent reference from a /// \c rosa::AgentHandle if possible. /// /// The function returns a \c rosa::Optional object containing a reference to /// a \c rosa::deluxe::DeluxeAgent object extracted from a /// \c rosa::AgentHandle instance if the referred object is of type /// \c rosa::deluxeDeluxeAgent and owned by \p this object. The returned /// \c rosa::Optional object is empty otherwise. /// /// \see rosa::deluxe::DeluxeSystem::isDeluxeAgent /// /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeAgent /// from /// /// \return reference to \c rosa::deluxe::DeluxeAgent if \p H refers to an /// object which is of that type and is owned by \p this object Optional getDeluxeAgent(AgentHandle &H) const noexcept; }; template AgentHandle DeluxeSystem::createSensor(const std::string &Name, std::function)> &&MF, std::function &&F) noexcept { Agent &DS = createUnit( [&](const id_t Id, MessagingSystem &S) { return new DeluxeSensor(atoms::SensorKind, Id, Name, S, std::move(MF), std::move(F)); }); return {DS}; } template AgentHandle DeluxeSystem::createAgent( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept { Agent &DA = createUnit( [&](const id_t Id, DeluxeSystem &S) { return new DeluxeAgent(atoms::AgentKind, Id, Name, S, std::move(MF), std::move(F)); }); return {DA}; } } // End namespace deluxe } // End namespace rosa -#endif // ROSA_LIB_DELUXE_DELUXESYSTEM_HPP +#endif // ROSA_DELUXE_DELUXESYSTEM_HPP diff --git a/lib/core/MessagingSystemImpl.hpp b/lib/core/MessagingSystemImpl.hpp index 485711a..89a366b 100644 --- a/lib/core/MessagingSystemImpl.hpp +++ b/lib/core/MessagingSystemImpl.hpp @@ -1,109 +1,110 @@ //===-- core/MessagingSystemImpl.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file core/MessagingSystemImpl.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Declaration of a basic implementation of the \c rosa::MessagingSystem /// interface. /// //===----------------------------------------------------------------------===// #ifndef ROSA_LIB_CORE_MESSAGINGSYSTEMIMPL_HPP #define ROSA_LIB_CORE_MESSAGINGSYSTEMIMPL_HPP #include "SystemImpl.hpp" #include "rosa/core/MessagingSystem.hpp" namespace rosa { /// Implements \c rosa::MessagingSystem by extending \c rosa::SystemImpl with /// adding a simple implementation of sending messages: directly invoking /// \c rosa::Agent instances with given \c rosa::Message objects. /// /// \note Keep in mind that sending a \c rosa::Message object with this /// implementation translates into a direct function call. class MessagingSystemImpl : public MessagingSystem, public SystemImpl { /// Alies for the base-class \c rosa::SystemImpl. using Base = SystemImpl; public: /// Creates an instance. /// /// \param Name name of the new instance MessagingSystemImpl(const std::string &Name) noexcept; - /// \defgroup MessagingSystemImplCallForwarding Call forwardings of rosa::MessagingSystemImpl + /// \defgroup MessagingSystemImplCallForwarding Call forwardings of + /// rosa::MessagingSystemImpl /// /// \c rosa::MessagingSystemImpl call forwardings /// /// \note Simply forwarding calls to implementations provided by /// \c rosa::MessagingSystem::Base for the \c rosa::System interface. /// /// \todo How could we use the inherited implementations in a simpler way? ///@{ bool operator==(const System &Other) const noexcept override { return Base::operator==(Other); } protected: id_t nextId(void) noexcept override { return Base::nextId(); } bool isSystemCleaned(void) const noexcept override { return Base::isSystemCleaned(); } void markCleaned(void) noexcept override { Base::markCleaned(); } void registerUnit(Unit &U) noexcept override { Base::registerUnit(U); } void destroyUnit(Unit &U) noexcept override { Base::destroyUnit(U); } bool isUnitRegistered(const Unit &U) const noexcept override { return Base::isUnitRegistered(U); } public: const std::string &name(void) const noexcept override { return Base::name(); } size_t numberOfConstructedUnits(void) const noexcept override { return Base::numberOfConstructedUnits(); } size_t numberOfLiveUnits(void) const noexcept override { return Base::numberOfLiveUnits(); } bool empty(void) const noexcept override { return Base::empty(); } ///@} /// Sends a \c rosa::message_t instance to the \c rosa::Agent instance /// referred by a \c rosa::AgentHandle -- by directly invoking the /// \c rosa::Agent instance with the \c rosa::Message object. /// /// \note If the given \c rosa::Message object cannot be handled by the /// referred \c rosa::Agent instance, the \c rosa::Message object is simply /// ignored. /// /// \param H refers to the \c rosa::Agent instance to send to /// \param M message to send /// /// \pre The referred \c rosa::Agent instance is owned by \p this object and /// also registered: \code /// &unwrapSystem(H) == this && isUnitRegistered(unwrapAgent(H)) /// \endcode void send(const AgentHandle &H, message_t &&M) noexcept override; }; } // End namespace rosa #endif // ROSA_LIB_CORE_MESSAGINGSYSTEMIMPL_HPP