diff --git a/apps/sa-ews1/sa-ews1.cpp b/apps/sa-ews1/sa-ews1.cpp index 836e3d1..b47e00d 100644 --- a/apps/sa-ews1/sa-ews1.cpp +++ b/apps/sa-ews1/sa-ews1.cpp @@ -1,318 +1,318 @@ //===-- apps/sa-ews1/sa-ews1.cpp --------------------------------*- C++ -*-===// // // The RoSA Framework -- Application SA-EWS1 // //===----------------------------------------------------------------------===// /// /// \file apps/sa-ews1/sa-ews1.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief The application SA-EWS1 implements the case study from the paper: /// M. Götzinger, N. Taherinejad, A. M. Rahmani, P. Liljeberg, A. Jantsch, and /// H. Tenhunen: Enhancing the Early Warning Score System Using Data Confidence /// DOI: 10.1007/978-3-319-58877-3_12 //===----------------------------------------------------------------------===// #include "rosa/agent/Abstraction.hpp" #include "rosa/agent/Confidence.hpp" #include "rosa/config/version.h" #include "rosa/deluxe/DeluxeContext.hpp" #include "rosa/support/csv/CSVReader.hpp" #include "rosa/support/csv/CSVWriter.hpp" #include using namespace rosa; using namespace rosa::agent; using namespace rosa::deluxe; using namespace rosa::terminal; const std::string AppName = "SA-EWS1"; /// Paths for the CSV files for simulation. /// ///@{ const std::string HRCSVPath = "HR.csv"; const std::string BRCSVPath = "BR.csv"; const std::string SpO2CSVPath = "SpO2.csv"; const std::string BPSysCSVPath = "BPSys.csv"; const std::string BodyTempCSVPath = "BodyTemp.csv"; const std::string ScoreCSVPath = "Score.csv"; ///@} /// How many cycles of simulation to perform. const size_t NumberOfSimulationCycles = 16; /// Warning levels for abstraction. enum WarningScore { No = 0, Low = 1, High = 2, Emergency = 3 }; /// Helper function creating a deluxe agent for pre-processing sensory values. /// /// Received values are first validated for confidence. Values which the /// validator does not mark confident are ignored. Confident values are /// abstracted into a \c WarningScore value, which is the result of the /// processing function. /// /// \note The result, \c WarningScore, is returned as \c uint32_t because /// enumeration types are not integrated into built-in types. Hence, a master /// to these agents receives its input as \c uint32_t values, and may cast them /// to \c WarningScore explicitly. /// /// \tparam T type of values to receive from the sensor /// /// \param C the deluxe context to create the agent in /// \param Name name of the new agent /// \param CC confidence validator to use /// \param A abstraction to use /// /// \return handle for the new agent template AgentHandle createLowLevelAgent(std::unique_ptr &C, const std::string &Name, const Confidence &CC, const Abstraction &A) { using handler = std::function(std::pair)>; using result = Optional; return C->createAgent( Name, handler([&, Name](std::pair I) -> result { LOG_INFO_STREAM << "\n******\n" << Name << " " << (I.second ? "" : "") << " value: " << I.first << "\n******\n"; return (I.second && CC(I.first)) ? result(A(I.first)) : result(); })); } int main(void) { LOG_INFO_STREAM << '\n' << library_string() << " -- " << Color::Red << AppName << "app" << Color::Default << '\n' << Color::Yellow << "CSV files are read from and written to the current working directory." << Color::Default << '\n'; std::unique_ptr C = DeluxeContext::create(AppName); // // Create deluxe sensors. // LOG_INFO("Creating sensors."); // All sensors are created without defining a normal generator function, but // with the default value of the second argument. That, however, requires the // data type to be explicitly defined. This is good for simulation only. AgentHandle HRSensor = C->createSensor("HR Sensor"); AgentHandle BRSensor = C->createSensor("BR Sensor"); AgentHandle SpO2Sensor = C->createSensor("SpO2 Sensor"); AgentHandle BPSysSensor = C->createSensor("BPSys Sensor"); AgentHandle BodyTempSensor = C->createSensor("BodyTemp Sensor"); // // Create functionalities. // LOG_INFO("Creating Functionalities for Agents."); // // Define confidence validators. // // Lower bounds are inclusive and upper bounds are exclusive. Confidence HRConfidence(0, 501); Confidence BRConfidence(0, 301); Confidence SpO2Confidence(0, 101); Confidence BPSysConfidence(0,501); Confidence BodyTempConfidence(-60, nextRepresentableFloatingPoint(50.0f)); // // Define abstractions. // RangeAbstraction HRAbstraction( {{{0, 40}, Emergency}, {{40, 51}, High}, {{51, 60}, Low}, {{60, 100}, No}, {{100, 110}, Low}, {{110, 129}, High}, {{129, 200}, Emergency}}, Emergency); RangeAbstraction BRAbstraction({{{0, 9}, High}, {{9, 14}, No}, {{14, 20}, Low}, {{20, 29}, High}, {{29, 50}, Emergency}}, Emergency); RangeAbstraction SpO2Abstraction({{{1, 85}, Emergency}, {{85, 90}, High}, {{90, 95}, Low}, {{95, 100}, No}}, Emergency); RangeAbstraction BPSysAbstraction( {{{0, 70}, Emergency}, {{70, 81}, High}, {{81, 101}, Low}, {{101, 149}, No}, {{149, 169}, Low}, {{169, 179}, High}, {{179, 200}, Emergency}}, Emergency); RangeAbstraction BodyTempAbstraction( {{{0.f, 28.f}, Emergency}, {{28.f, 32.f}, High}, {{32.f, 35.f}, Low}, {{35.f, 38.f}, No}, {{38.f, 39.5f}, High}, {{39.5f, 100.f}, Emergency}}, Emergency); // // Create low-level deluxe agents with \c createLowLevelAgent. // LOG_INFO("Creating low-level agents."); AgentHandle HRAgent = createLowLevelAgent(C, "HR Agent", HRConfidence, HRAbstraction); AgentHandle BRAgent = createLowLevelAgent(C, "BR Agent", BRConfidence, BRAbstraction); AgentHandle SpO2Agent = createLowLevelAgent(C, "SpO2 Agent", SpO2Confidence, SpO2Abstraction); AgentHandle BPSysAgent = createLowLevelAgent(C, "BPSys Agent", BPSysConfidence, BPSysAbstraction); AgentHandle BodyTempAgent = createLowLevelAgent( C, "BodyTemp Agent", BodyTempConfidence, BodyTempAbstraction); // // Connect sensors to low-level agents. // LOG_INFO("Connect sensors to their corresponding low-level agents."); C->connectSensor(HRAgent, 0, HRSensor, "HR Sensor Channel"); C->connectSensor(BRAgent, 0, BRSensor, "BR Sensor Channel"); C->connectSensor(SpO2Agent, 0, SpO2Sensor, "SpO2 Sensor Channel"); C->connectSensor(BPSysAgent, 0, BPSysSensor, "BPSys Sensor Channel"); C->connectSensor(BodyTempAgent, 0, BodyTempSensor, "BodyTemp Sensor Channel"); // // Create a high-level deluxe agent. // LOG_INFO("Create high-level agent."); // The new agent logs its input values and results in the the sum of them. AgentHandle BodyAgent = C->createAgent( "Body Agent", std::function( std::pair, std::pair, std::pair, std::pair, std::pair)>( [](std::pair HR, std::pair BR, std::pair SpO2, std::pair BPSys, std::pair BodyTemp) -> Optional { LOG_INFO_STREAM << "\n*******\nBody Agent trigged with values:\n" << (HR.second ? "" : "") << " HR warning score: " << HR.first << "\n" << (BR.second ? "" : "") << " BR warning score: " << BR.first << "\n" << (SpO2.second ? "" : "") << " SpO2 warning score: " << SpO2.first << "\n" << (BPSys.second ? "" : "") << " BPSys warning score: " << BPSys.first << "\n" << (BodyTemp.second ? "" : "") << " BodyTemp warning score: " << BodyTemp.first << "\n******\n"; return {HR.first + BR.first + SpO2.first + BPSys.first + BodyTemp.first}; })); // // Connect low-level agents to the high-level agent. // LOG_INFO("Connect low-level agents to the high-level agent."); C->connectAgents(BodyAgent, 0, HRAgent, "HR Agent Channel"); C->connectAgents(BodyAgent, 1, BRAgent, "BR Agent Channel"); C->connectAgents(BodyAgent, 2, SpO2Agent, "SpO2 Agent Channel"); C->connectAgents(BodyAgent, 3, BPSysAgent, "BPSys Agent Channel"); C->connectAgents(BodyAgent, 4, BodyTempAgent, "BodyTemp Agent Channel"); // // For simulation output, create a logger agent writing the output of the // high-level agent into a CSV file. // LOG_INFO("Create a logger agent."); // Create CSV writer. std::ofstream ScoreCSV(ScoreCSVPath); csv::CSVWriter ScoreWriter(ScoreCSV); // The agent writes each new input value into a CSV file and produces nothing. AgentHandle LoggerAgent = C->createAgent( "Logger Agent", std::function(std::pair)>( [&ScoreWriter](std::pair Score) -> Optional { if (Score.second) { // The state of \p ScoreWriter is not checked, expecting good. ScoreWriter << Score.first; } return {}; })); // // Connect the high-level agent to the logger agent. // LOG_INFO("Connect the high-level agent to the logger agent."); C->connectAgents(LoggerAgent, 0, BodyAgent, "Body Agent Channel"); // // Do simulation. // LOG_INFO("Setting up and performing simulation."); // // Initialize deluxe context for simulation. // C->initializeSimulation(); // // Open CSV files and register them for their corresponding sensors. // // Type aliases for iterators. - using CSVInt = csv::CSVFlatIterator; - using CSVFloat = csv::CSVFlatIterator; + using CSVInt = csv::CSVIterator; + using CSVFloat = csv::CSVIterator; std::ifstream HRCSV(HRCSVPath); C->registerSensorValues(HRSensor, CSVInt(HRCSV), CSVInt()); std::ifstream BRCSV(BRCSVPath); C->registerSensorValues(BRSensor, CSVInt(BRCSV), CSVInt()); std::ifstream SpO2CSV(SpO2CSVPath); C->registerSensorValues(SpO2Sensor, CSVInt(SpO2CSV), CSVInt()); std::ifstream BPSysCSV(BPSysCSVPath); C->registerSensorValues(BPSysSensor, CSVInt(BPSysCSV), CSVInt()); std::ifstream BodyTempCSV(BodyTempCSVPath); C->registerSensorValues(BodyTempSensor, CSVFloat(BodyTempCSV), CSVFloat()); // // Simulate. // C->simulate(NumberOfSimulationCycles); return 0; } diff --git a/examples/CSVFiles/CMakeLists.txt b/examples/CSVFiles/CMakeLists.txt new file mode 100644 index 0000000..cfc92e7 --- /dev/null +++ b/examples/CSVFiles/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(csvfiles main.cpp) +ROSA_add_library_dependencies(csvfiles ROSAConfig) +ROSA_add_library_dependencies(csvfiles ROSADeluxe) diff --git a/examples/CSVFiles/main.cpp b/examples/CSVFiles/main.cpp new file mode 100644 index 0000000..b2c43ce --- /dev/null +++ b/examples/CSVFiles/main.cpp @@ -0,0 +1,352 @@ +//===-- examples/CSVFiles/main.cpp ------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file examples/basic-system/basic-system.cpp +/// +/// \author Edwin Willegger (edwin.willegger@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief A simple example on the basic \c rosa::csv, \c rosa::iterator and +/// \c rosa::writer classes. Focus is on the tuple impementations. +/// +//===----------------------------------------------------------------------===// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//includes for an complete example to read and write +//with sensors and agents. +#include "rosa/deluxe/DeluxeContext.hpp" + +#include "rosa/config/version.h" + +//includes to test the basic functionality +//to read and write tuples. +#include "rosa/support/csv/CSVReader.hpp" +#include "rosa/support/csv/CSVWriter.hpp" +#include "rosa/support/iterator/split_tuple_iterator.hpp" +#include "rosa/support/writer/split_tuple_writer.hpp" + +/// the name of the example +const std::string ExampleName = "csvfiles"; + +/// How many cycles of simulation to perform. +const size_t NumberOfSimulationCycles = 10; + +/// Paths for the CSV files for simulation. +/// input csv files +const std::string csvPath = "../examples/CSVFiles/"; +const std::string csvFileWithHeader = csvPath + "HR-New.csv"; +const std::string csvFileNoHeader = csvPath + "HR.csv"; +const std::string csvFileHeaderSemi = csvPath + "HR-New-Semicolon.csv"; +/// output csv files +const std::string csvFileWriteHea = csvPath + "csvwriter_noheader.csv"; +const std::string csvFileWriteNoHeaSplit = csvPath + "csvSplitwriter_noheader.csv"; + +using namespace rosa; + +/// +/// This function tests the basic CSVIterator capablities, and shows you +/// how you could work with this class. +/// +void testtupleCSVReader(void){ + + //different streams to get the csv data out of the files + //file contains header and valid data entries, delimter = ',' + std::ifstream file_header_data(csvFileWithHeader); + //file contains header and valid data entries, delimter = ',' + std::ifstream file_header_data_2(csvFileWithHeader); + //file contains header and valid data entries, delimter = ',' + std::ifstream file_header_data_3(csvFileWithHeader); + //file contains header and valid data entries, delimter = ',' + std::ifstream file_header_data_4(csvFileWithHeader); + //file contains header and valid data entries, delimter = ',' + std::ifstream file_header_data_5(csvFileWithHeader); + //file contains header and valid data entries, delimter = ',' + std::ifstream file_header_data_6(csvFileWithHeader); + //file contains no header an valid data entries, delimter = ',' + std::ifstream file2(csvFileNoHeader); + //file contains header and valid data entries, delimter = ';' + std::ifstream file3(csvFileHeaderSemi); + + csv::CSVIterator it(file_header_data); + + it.setDelimeter(','); + + it++; + it++; + //if you iterate over the end of file, the last values + //of the file will remain in the data structure but no + //error occurs. + it++; + it++; + + //------------------------------------------------------------------- + // a possiblity to get the data out of the iterator + std::tuple value = *it; + + // + // Show the value of one iterator + // + LOG_INFO( "Values are: "); + LOG_INFO(std::get<0>(value) ); + LOG_INFO(std::get<1>(value) ); + + + //-------------------------------------------------------------------- + //testing differnet parameters to the constructor + + //uncomment to see that it is not possible to iterate over an vector in the tuple. + //rosa::csv::CSVIterator> it2(file, 1); + + //try to skip a valid number of lines after the header + csv::CSVIterator it2_0(file_header_data_2, 1); + //try to skip a valid number of lines after the header, but you assume that the file has no header + //uncomment this line to crash the programm + //csv::CSVIterator it2_1(file_header_data_3, 0, csv::HeaderInformation::HasNoHeader); + + //try to skip a valid number of lines after the header, but you assume that the file has no header + //uncomment this line to crash the program + //csv::CSVIterator it2_2(file_header_data_4, 1, csv::HeaderInformation::HasNoHeader); + + //try to skip a valid number of lines of a file without header + csv::CSVIterator it2_3(file2, 1, csv::HeaderInformation::HasNoHeader); + + //try to skip a valid number of lines after the header, but with different delimeter + csv::CSVIterator it2_4(file3, 2, csv::HeaderInformation::HasHeader, ';'); + + // if you skip more lines than valid, you generate an infinte loop + //csv::CSVIterator it3(file_header_data_5, 500); + + //if you don't need data from all columns just select the number of columns you + //need. You get the data back from the first column (index 0) to the fourth column + //all values from the fifth column are ignored. + csv::CSVIterator it4(file_header_data_6); +} + +/// +/// This function tests the basic CSVTupleWriter capablities, and shows you +/// how you could work with this class. +/// +void testtupleCSVWriter(void){ + // + // Create output writer with an file + // + std::ofstream file_header_out(csvFileWriteHea); + csv::CSVTupleWriter wri(file_header_out); + + // + // Create test tuples + // + std::tuple values(5, 8.3, "hallo"); + std::tuple values2(3, 8.3, "end"); + + // + // Create test header lines for the test tuples + // + std::array header {"zero column", "first column", "second column"}; + + std::array headerWrong {"zero column", "first column", "second column", "third column"}; + + std::array headerWrongShort {"zero column", "first column"}; + + //if you uncomment this line than it would be possible for you to write the header into the stream + //in the next line. + //wri.write(values); + wri.writeHeader(header); + wri.write(values); + wri.write(values); + // it is not possible to write an additional header into the stream. + wri.writeHeader(header); + wri.write(values); + wri << values; + wri << values2; + + //uncomment this line to see, that you can't write a header with the too many elements. + //wri.writeHeader(headerWrong); + //uncomment this line to see, that you can't write a header with the too few elements. + //wri.writeHeader(headerWrongShort); + +} + +/// +/// This function tests the basic splitTupleIterator capablities, and shows you +/// how you could work with this class, this class is used if you want to split +/// a CSVIterator in separate parts. +/// +void testsplitTupleIterator(void) +{ + // + // Create deluxe context + // + std::unique_ptr C = + deluxe::DeluxeContext::create(ExampleName); + + // + // Create deluxe sensors. + // + LOG_INFO("Creating sensors."); + + + // All sensors are created without defining a normal generator function, but + // with the default value of the second argument. That, however, requires the + // data type to be explicitly defined. This is good for simulation only. + // Three different sensors were created, this is just a random number taken. + AgentHandle Elem0Sensor = C->createSensor("Element1 Sensor"); + AgentHandle Elem1Sensor = C->createSensor("Element2 Sensor"); + AgentHandle Elem2Sensor = C->createSensor("Element3 Sensor"); + + + // + // Initialize deluxe context for simulation. + // + C->initializeSimulation(); + + // Type aliases for iterators + using Iterator = rosa::csv::CSVIterator; + using IteratorValue = std::tuple; + + static_assert (std::is_same::value, "Iterator must provide tuples" ); + + // + // Open CSV file and register the columns to the corresponding sensors. + // + std::ifstream TestCSV(csvFileWithHeader); + + // + // Test data looks like: + // Element1, Element2, Element3, Element4, Element5 -- is the header line + // 3, 5, 8, 9.5, 17 -- first line of values + // 100, -8, 30, 18.8, 29 -- other line of values were also in the file + // 5, 20, -100, -200.1, -30 -- if you have less number of values than simulation rounds all values + // -- beyond your last value will be zero. + + //get element iterator ranges + auto [Elem0Range, Elem1Range, Elem2Range] = iterator::splitTupleIterator(Iterator(TestCSV), Iterator()); + + //dissect a range into begin and end iterators by structred bindings + auto[Elem0Begin, Elem0End] = Elem0Range; + + //deissect a range with functions + auto Elem1Begin = iterator::begin(Elem1Range); + auto Elem1End = iterator::end(Elem1Range); + + C->registerSensorValues(Elem0Sensor, std::move(Elem0Begin), Elem0End); + C->registerSensorValues(Elem1Sensor, std::move(Elem1Begin), Elem1End); + C->registerSensorValues(Elem2Sensor, std::move(iterator::begin(Elem2Range)), + iterator::end(Elem2Range)); + + // + // Simulate. + // + C->simulate(NumberOfSimulationCycles); + +} + + +/// +/// This function tests the basic splitTupleWriter capablities, and shows you +/// how you could work with this class, this class is used if you want to split +/// a CSVWriter in separate parts. +/// +void testsplitTupleWriter(void){ + // + // Create output writer with an file + // + std::ofstream file_header_out(csvFileWriteNoHeaSplit); + csv::CSVTupleWriter wri(file_header_out); + + // if you omit, the type definition in the template, than auto generated types were used, + // and they may not fit to the used CSVTupleWriter. + wri << std::make_tuple(1000, 50.6, "tuple_created"); + auto [T0Writer, T1Writer, T2Writer] = writer::splitTupleWriter(std::move(wri), + writer::IncompleteTuplePolicy::Ignore); + //writing elements in sequential order into the writer classes, but you can write the values into the writers in + //a random order. + T0Writer << (500); + T1Writer << (3.0); + T2Writer << "splitted writter"; + + T2Writer << "splitting is cool"; + T0Writer << (-30); + T1Writer << (-0.004); + + // you can also write more often values into a writer and later into the other writers + // all data will be processed correctly into the right order. + T0Writer << (1); + T0Writer << (2); + T1Writer << (-0.4); + T0Writer << (3); + T2Writer << "again"; + T0Writer << (4); + T1Writer << (-0.1); + T1Writer << (-0.2); + T2Writer << "and"; + T1Writer << (-0.3); + T2Writer << "splitting"; + T2Writer << "once again"; + + // again writing data of one tuple entry to the different writers in a random fashion. + T1Writer << (-0.004); + T2Writer << "splitting is cool"; + T0Writer << (-30); + +} + +int main(void){ + LOG_INFO_STREAM << library_string() << " -- " << terminal::Color::Red + << ExampleName << " example" << terminal::Color::Default << '\n'; + + // + // Testing CSVWriter. + // + LOG_INFO("Testing CSVWriter CSVTupleItrator implementation: "); + + testtupleCSVWriter(); + + // + // Testing CSVReader. + // + LOG_INFO("Testing CSVReader CSVTupleIterator implementation: "); + + testtupleCSVReader(); + + // + // Testing SplitTupleIterator. + // + LOG_INFO("Testing SplitTupleIterator: "); + + testsplitTupleIterator(); + + // + // Testing SplitTupleWriter. + // + LOG_INFO("Testing SplitTupleWriter: "); + testsplitTupleWriter(); + + + // + // info that user knows programm has finished. + // + LOG_INFO( "All tests finished."); + + + return 0; +} + diff --git a/include/rosa/deluxe/DeluxeAgent.hpp b/include/rosa/deluxe/DeluxeAgent.hpp index 7a94a57..3fde782 100644 --- a/include/rosa/deluxe/DeluxeAgent.hpp +++ b/include/rosa/deluxe/DeluxeAgent.hpp @@ -1,1479 +1,1453 @@ //===-- 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; + /// \note The position of a \c rosa::AbstractTokenizedStorage in the \c + /// std::vector indicates which argument of \p this object's processing + /// function the tuple is; and the position of the value in the \c + /// rosa::AbstractTokenizedStorage indicates which element of that tuple the + /// value is. See also \c rosa::deluxe::DeluxeAgent::DeluxeAgent. + const std::vector> 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 +/// Creates storages for data of types \p Ts... in a \c std::vector of \c /// rosa::TokenizedStorage. /// /// \note Utilized by \c rosa::deluxe::DeluxeAgnet::DeluxeAgent to initialize \c -/// rosa::deluxe::DeluxeAgent::InputStorageOffsets. +/// rosa::deluxe::DeluxeAgent::InputValues. That is due to not being able to use +/// an initializer list directly; the initializer list always copies but \c +/// std::unique_ptr is not copyable. /// -/// \tparam Ts types whose offsets to calculate -/// \tparam S0 indices for referring to positions in \p Ts... +/// \tparam Ts types to create storages for /// /// \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 +/// \return \c std::vector with pointers for the created storage objects /// /// \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) +/// rosa::deluxe::DeluxeTuple: \code +/// TypeListAllDeluxeTuple>::Value /// \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 +std::vector> +makeInputStorages(void) noexcept { + std::vector> InputStorages; + (InputStorages.push_back( + std::make_unique::Type>::Type>()), + ...); + return InputStorages; } /// 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))); + const auto &SlaveInput = InputValues[Pos]; + // Get all elements of the tuple in a fold expression. - return T(*static_cast(InputValues->pointerTo( - static_cast(StorageOffset + S0)))...); + return T(*static_cast( + SlaveInput->pointerTo(static_cast(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 { 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), + InputValues(makeInputStorages()), 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); + const 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; + *static_cast(InputValues[SlavePos]->pointerTo(Pos)) = Value; // Update position of next value. if (++InputNextPos[SlavePos] == lengthOfToken(InputTypes[SlavePos])) { InputNextPos[SlavePos] = 0; } // Set flag. InputChanged[SlavePos] = true; ASSERT(inv()); } template void DeluxeAgent::saveMasterInput(id_t Id, token_size_t Pos, T Value) noexcept { ASSERT(inv() && Master && masterId() == Id && Pos == MasterInputNextPos && typeAtPositionOfToken(MasterInputType, Pos) == TypeNumberOf::Value); LOG_TRACE_STREAM << "DeluxeAgent " << FullName << "(" << Id << ") saves value from master: (" << static_cast(Pos) << ") " << Value << std::endl; // Save value. *static_cast(MasterInputValue->pointerTo(Pos)) = Value; // Update position of next value. if (++MasterInputNextPos == lengthOfToken(MasterInputType)) { MasterInputNextPos = 0; } // Set flag. MasterInputChanged = true; ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa #undef DASLAVEHANDLEREF #undef DAMASTERHANDLEREF #undef DASLAVEHANDLEDEF #undef DAMASTERHANDLEDEF #undef DASLAVEHANDLEDEFN #undef DAMASTERHANDLEDEFN #undef DASLAVEHANDLENAME #undef DAMASTERHANDLENAME #endif // ROSA_DELUXE_DELUXEAGENT_HPP diff --git a/include/rosa/deluxe/DeluxeContext.hpp b/include/rosa/deluxe/DeluxeContext.hpp index a54a2da..9363c41 100644 --- a/include/rosa/deluxe/DeluxeContext.hpp +++ b/include/rosa/deluxe/DeluxeContext.hpp @@ -1,932 +1,935 @@ //===-- rosa/deluxe/DeluxeContext.hpp ---------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeContext.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Public interface for the *deluxe interface* for working with agent /// systems. /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXECONTEXT_HPP #define ROSA_DELUXE_DELUXECONTEXT_HPP #include "rosa/deluxe/DeluxeSystem.hpp" #include "rosa/support/types.hpp" #include #include #include /// Local helper macro to log and return a /// \c rosa::deluxe::DeluxeContext::ErrorCode value. /// /// Creates a debug message with the stringified value and returns the value. /// /// \param Err \c rosa::deluxe::DeluxeContext::ErrorCode value to log and /// return #define DCRETERROR(Err) \ { \ LOG_DEBUG(#Err); \ return Err; \ } namespace rosa { namespace deluxe { /// Defines the *deluxe interface*. /// /// \todo The classes \c rosa::deluxe::DeluxeSensor and \c /// rosa::deluxe::DeluxeAgent share some common features in relation to their /// *slave* role in the *deluxe interface*. But their definitions are completely /// independent. It could be investigated how to lift their common parts into a /// new *deluxe slave* class, which would serve as base for both, to avoid code /// duplication. class DeluxeContext { /// A system owned by \p this object. /// /// \note The reference is kept in a \c std::shared_ptr because of the member /// function \c rosa::deluxe::DeluxeContext::getSystem. std::shared_ptr System; /// References to all *sensors* and *agents* created by \p this object. std::set DeluxeUnits; public: /// Errors that may be resulted by some of the member functions of the class. enum struct ErrorCode { NoError, TypeMismatch, NotSensor, NotAgent, NotUnit, WrongPosition, AlreadyHasSlave, AlreadyHasMaster, AlreadyHasValueStream, UnsuitableExecutionPolicy }; /// Returns a new instance of \c rosa::deluxe::DeluxeContext. /// /// \param Name name of the underlying \c rosa::DeluxeSystem /// /// \return \c std::unique_ptr for the new instance of /// \c rosa::deluxe::DeluxeContext with a new, empty \c rosa::DeluxeSystem static std::unique_ptr create(const std::string &Name) noexcept; private: /// Creates a new instance. /// /// \note Private constructor restricts instantiation to member functions of /// the class. /// /// \param Name name of the underlying \c rosa::MessagingSystem DeluxeContext(const std::string &Name) noexcept; public: /// Destroys \p this object. ~DeluxeContext(void) noexcept; /// Returns a reference for the underlying \c rosa::MessagingSystem. /// /// \note One cannot do much with a \c rosa::MessagingSystem currently, this /// is for future use. /// /// \return reference for the underlying \c rosa::MessagingSystem. std::weak_ptr getSystem(void) const noexcept; private: /// Creates a new *sensor* in the context of \p this object. /// /// The new *sensor* handles master-input by \p MF. /// /// \tparam MT type of master-input the new *sensor* handles /// \tparam T type of data the new *sensor* operates on /// /// \note Instantiation fails if any of the type arguments \p MT and \p T /// is not an instance of \c rosa::deluxe::DeluxeTuple or \p T is \c /// rosa::deluxe::EmptyDeluxeTuple. /// /// \param Name name of the new *sensor* /// \param MF function for the new *sensors* to process master-input /// values with /// \param F function for the new *sensor* to generate the next value with /// during normal operation /// /// \note \p F is not used during simulation, in which case /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to /// register an alternative simulation data source with \c /// rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may /// safely keep relying on the default value of \p F as long as only /// simulation of the system is to be done. /// /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor. /// /// \return \c rosa::AgentHandle for the new *sensor* template >::Value && !std::is_same::value>> AgentHandle createSensorImpl(const std::string &Name, std::function)> &&MF, std::function &&F) noexcept; public: /// Creates a new *sensor* in the context of \p this object. /// /// The new *sensor* does not receive master-input. /// /// \tparam T type of data the new *sensor* operates on /// /// \note Instantiation fails if type argument \p T is neither a built-in type /// nor an instance of \c rosa::deluxe::DeluxeTuple with at least one element. /// /// \param Name name of the new *sensor* /// \param F function for the new *sensor* to generate the next value with /// during normal operation /// /// \note \p F is not used during simulation, in which case /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to register /// an alternative simulation data source with /// \c rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may /// safely keep relying on the default value of \p F as long as only /// simulation of the system is to be done. /// /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor. /// /// \return \c rosa::AgentHandle for the new *sensor* template ::Value || (IsDeluxeTuple::Value && !std::is_same::value)>> AgentHandle createSensor( const std::string &Name, std::function &&F = [](void) { return T(); }) noexcept; /// Creates a new *sensor* in the context of \p this object. /// /// The new *sensor* handles master-input by \p MF. /// /// \tparam MT type of master-input the new *sensor* handles /// \tparam T type of data the new *sensor* operates on /// /// \note The type arguments \p MT and \p T must be either all built-in types /// or all instances of \c rosa::deluxe::DeluxeTuple. Moreover, \p T cannot be /// \c rosa::deluxe::EmptyDeluxeTuple. Instantiation fails if these conditions /// do not hold. /// /// \param Name name of the new *sensor* /// \param MF function for the new *sensors* to process master-input /// values with \param F function for the new *sensor* to generate /// the next value with during normal operation /// /// \note \p F is not used during simulation, in which case /// \c rosa::deluxe::DeluxeContext::registerSensorValues is used to /// register an alternative simulation data source with \c /// rosa::deluxe::DeluxeSensor::registerSimulationDataSource. One may /// safely keep relying on the default value of \p F as long as only /// simulation of the system is to be done. /// /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor. /// /// \return \c rosa::AgentHandle for the new *sensor* template , BuiltinTypes>::Value || (TypeListAllDeluxeTuple>::Value && !std::is_same::value)>> AgentHandle createSensor( const std::string &Name, std::function)> &&MF, std::function &&F = [](void) { return T(); }) noexcept; private: /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* receives master-input by \p MF and produces /// master-output. /// /// \tparam MT type of master-input the new *agent* handles /// \tparam T type of data the new *agent* outputs /// \tparam Ts types of master-output the new *agent* produces /// \tparam As types of inputs the new *agent* takes /// /// \note Instantiation fails if any of the type arguments \p MT, \p T, \p /// Ts..., and \p As... is not an instance of \c rosa::deluxe::DeluxeTuple or /// any of \p T and \p As... is \c rosa::deluxe::EmptyDeluxeTuple. /// /// \param Name name of the new *agent* /// \param MF function for the new *agent* to process master-input /// values with \param F function for the new *agent* to process /// input values and generate output with /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* template >::Value && !std::is_same::value && (true && ... && (!std::is_same::value))>> AgentHandle createAgentImpl( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept; public: /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* neither receives master-input nor produces /// master-output. /// /// \tparam T type of data the new *agent* outputs /// \tparam As types of inputs the new *agent* takes /// /// \note The type arguments \p T and \p As... must be either all built-in /// types or all instances of \c rosa::deluxe::DeluxeTuple. Moreover, none of /// them can be \c rosa::deluxe::EmptyDeluxeTuple. Instantiation fails if /// these conditions do not hold. /// /// \param Name name of the new *agent* /// \param F function for the new *agent* to process input values and /// generate output with /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* template < typename T, typename... As, typename = std::enable_if_t< TypeListSubsetOf, BuiltinTypes>::Value || (TypeListAllDeluxeTuple>::Value && !std::is_same::value && (true && ... && (!std::is_same::value)))>> AgentHandle createAgent(const std::string &Name, std::function(std::pair...)> &&F) noexcept; /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* receives master-input by \p MF but does not /// produce master-output. /// /// \tparam MT type of master-input the new *agent* handles /// \tparam T type of data the new *agent* outputs /// \tparam As types of inputs the new *agent* takes /// /// \note The type arguments \p MT, \p T, and \p As... must be either all /// built-in types or all instances of \c rosa::deluxe::DeluxeTuple. Moreover, /// none of \p T and \p As... can be \c rosa::deluxe::EmptyDeluxeTuple. /// Instantiation fails if these conditions do not hold. /// /// \param Name name of the new *agent* /// \param MF function for the new *agent* to process master-input /// values with /// \param F function for the new *agent* to process input values and /// generate output with /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* template < typename MT, typename T, typename... As, typename = std::enable_if_t< TypeListSubsetOf, BuiltinTypes>::Value || (TypeListAllDeluxeTuple>::Value && !std::is_same::value && (true && ... && (!std::is_same::value)))>> AgentHandle createAgent(const std::string &Name, std::function)> &&MF, std::function(std::pair...)> &&F) noexcept; /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* does not receive master-input but produces /// master-output. /// /// \tparam T type of data the new *agent* outputs /// \tparam Ts types of master-output the new *agent* produces /// \tparam As types of inputs the new *agent* takes /// /// \note The type arguments \p T, \p Ts, and \p As... must be either all /// built-in types or all instances of \c rosa::deluxe::DeluxeTuple. Moreover, /// none of \p T and \p As... can be \c rosa::deluxe::EmptyDeluxeTuple. /// Instantiation fails if these conditions do not hold. /// /// \param Name name of the new *agent* /// \param F function for the new *agent* to process input values and /// generate output with /// /// \note \p F does not produce master-output for a given position if the /// corresponding type is \c rosa::deluxe::EmptyDeluxeTuple. It is not /// possible to disable master-output at any position by using built-in types. /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* template < typename T, typename... Ts, typename... As, typename = std::enable_if_t< TypeListSubsetOf, BuiltinTypes>::Value || (TypeListAllDeluxeTuple>::Value && !std::is_same::value && (true && ... && (!std::is_same::value)))>> AgentHandle createAgent(const std::string &Name, std::function, Optional...>( std::pair...)> &&F) noexcept; /// Creates a new *agent* in the context of \p this object. /// /// The new *agent* receives master-input by \p MF and produces /// master-output. /// /// \tparam MT type of master-input the new *agent* handles /// \tparam T type of data the new *agent* outputs /// \tparam Ts types of master-output the new *agent* produces /// \tparam As types of inputs the new *agent* takes /// /// \note The type arguments \p MT, \p T, \p Ts, and \p As... must be either /// all built-in types or all instances of \c rosa::deluxe::DeluxeTuple. /// Moreover, none of \p T and \p As... can be \c /// rosa::deluxe::EmptyDeluxeTuple. Instantiation fails if these conditions /// do not hold. /// /// \param Name name of the new *agent* /// \param MF function for the new *agent* to process master-input /// values with /// \param F function for the new *agent* to process input values and /// generate output with /// /// \note \p F does not produce master-output for a given position if the /// corresponding type is \c rosa::deluxe::EmptyDeluxeTuple. It is not /// possible to disable master-output at any position by using built-in types. /// /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for the new *agent* template < typename MT, typename T, typename... Ts, typename... As, typename = std::enable_if_t< TypeListSubsetOf, BuiltinTypes>::Value || (TypeListAllDeluxeTuple>::Value && !std::is_same::value && (true && ... && (!std::is_same::value)))>> AgentHandle createAgent( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept; /// Returns the current execution policy of the referred \p Unit /// /// \see \c rosa::deluxe::DeluxeExecutionPolicy /// /// \note The referred \p Unit is either *sensor* or *agent*. /// /// \note The returned reference is valid only as long as \c /// rosa::deluxe::DeluxeContext::setExecutionPolicy() is not called with the /// *unit* referred by \p Unit and the *unit* is not destroyed. /// /// \param Unit the *unit* whose execution policy is to be obtained /// /// \return the \c rosa::deluxe::DeluxeExecutionPolicy from \p Unit if \p Unit /// is valid Optional getExecutionPolicy(AgentHandle Unit) const noexcept; /// Sets the current execution policy of the referred \p Unit to \p /// ExecutionPolicy. /// /// \see \c rosa::deluxe::DeluxeExecutionPolicy /// /// \note The referred \p Unit is either *sensor* or *agent*. /// /// \param Unit the *unit* whose execution policy is to be set /// \param ExecutionPolicy the new execution policy for \p Unit /// /// \return how successful setting \p ExecutionPolicy for \p Unit was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotUnit` | Referred \p Unit is not valid /// `UnsuitableExecutionPolicy` | \p ExecutionPolicy cannot handle \p Unit ErrorCode setExecutionPolicy( AgentHandle Unit, std::unique_ptr &&ExecutionPolicy) noexcept; /// Connects a *sensor* to an *agent* in the context of \p this object. /// /// \param Agent the *agent* to connect to /// \param Pos the index of slot of \p Agent to connect \p Sensor to /// \param Sensor the *sensor* to connect /// \param Description optional textual description of the connection /// /// \return how successfull connecting \p Sensor to \p Agent at slot /// index \p Pos was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotAgent` | Referred \p Agent is not \c rosa::deluxe::DeluxeAgent /// `NotSensor` | Referred \p Sensor is not \c rosa::deluxe::DeluxeSensor /// `WrongPosition` | \p Pos is not a valid input position of \p Agent /// `TypeMismatch` | Expected input type at position \p Pos of \p Agent is other thanthe output type of \p Sensor or expected master-input of \p Sensor is other than master-output at position \p Pos of \p Agent if any /// `AlreadyHasSlave` | \p Agent at position \p Pos already has a *slave* registered /// `AlreadyHasMaster` | \p Sensor already has a *master* registered ErrorCode connectSensor(AgentHandle Agent, const size_t Pos, AgentHandle Sensor, const std::string &Description = "") noexcept; /// Connectes two *agents* in the context of \p this object. /// /// \param Master the *agent* to connect to /// \param Pos the index of slot of \p Master to connect \p Slave to /// \param Slave the *agent* to connect /// \param Description optional textual description of the connection /// /// \return how succesfull connecting \p Slave to \p Master at slot /// index \p Pos was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotAgent` | Referred \p Master or \p Slave is not \c rosa::deluxe::DeluxeAgent /// `WrongPosition` | \p Pos is not a valid input position of \p Master /// `TypeMismatch` | Expected input type at position \p Pos of \p Master is other than the output type of \p Slave or expected master-input of \p Slave is other than master-output at position \p Pos of \p Master if any /// `AlreadyHasSlave` | \p Master at position \p Pos already has a *slave* registered /// `AlreadyHasMaster` | \p Slave already has a *master* registered ErrorCode connectAgents(AgentHandle Master, const size_t Pos, AgentHandle Slave, const std::string &Description = "") noexcept; /// Initializes \c this object and others managed by \p this object /// for setting up and performing simulation. /// /// \see \c rosa::deluxe::DeluxeContext::registerSensorValues, /// \c rosa::deluxe::DeluxeContext::simulate /// /// Need to clear simulation data sources from all the *sensors*. void initializeSimulation(void) noexcept; public: /// Registers a stream providing values for a *sensor* during /// simulation. /// /// \tparam Iterator type of iterator providing values for \p Sensor - /// \tparam T type of values \p Sensor is operating on, always use - /// default! + /// \tparam T type that can be matched to values \p Sensor is operating on, + /// always use default! /// /// \note Instantiation fails if type argument \p T is neither a built-in type - /// nor an instance of \c rosa::deluxe::DeluxeTuple with at least one element. + /// nor a tuple (i.e., can be converted to \c rosa::deluxe::DeluxeTuple). /// /// \param Sensor the *sensor* to register values for /// \param Start provides values for \p Sensor /// \param End denotes the end of stream of values /// \param Default value to be used when input stream is depleted /// during simulation /// /// \return how successful registering \p Source for \p Sensor /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success - /// `TypeMismatch` | \p Sensor generates values of a type other than - /// \p T `NotSensor` | Referred \p Sensor is not \c - /// rosa::deluxe::DeluxeSensor `AlreadyHasValueStream` | \p Sensor already has - /// simulation data source set - template < - typename Iterator, typename T = typename Iterator::value_type, - typename = std::enable_if_t::Value || - (IsDeluxeTuple::Value && - !std::is_same::value)>> + /// `TypeMismatch` | \p T does not match the type of values + /// generated by \p Sensor + /// `NotSensor` | Referred \p Sensor is not \c + /// rosa::deluxe::DeluxeSensor + /// `AlreadyHasValueStream` | \p Sensor already has simulation data source set + template ::Value || IsTuple::Value>> ErrorCode registerSensorValues(AgentHandle Sensor, Iterator &&Start, const Iterator &End, T Default = {}) noexcept; /// Performs the system contained by \p this object. /// /// The function performs \p NumCycles cycle of simulation. In each /// cycle, all the *agents* and *sensors* registered in \c /// rosa::deluxe::DeluxeContext::DeluxeUnits are trigged for /// execution. /// /// \param NumCycles number of cycles to perform /// /// \pre All the *sensors* in the system contained by \p this object /// generate their output from simulation data sources. void simulate(const size_t NumCycles) const noexcept; }; /// Anonymous namespace with helper features for implementing /// \c rosa::deluxe::DeluxeContext, consider it private. namespace { /// Maps any type \p T to \c rosa::deluxe::EmptyDeluxeTuple. template struct MapToEmptyDeluxeTuple { using Type = EmptyDeluxeTuple; }; /// Convenience template alias for \c MapToEmptyDeluxeTuple. template using empty_deluxe_t = typename MapToEmptyDeluxeTuple::Type; /// Converts a \c std::tuple of \c rosa::Optional built-in types into a /// corresponding \c std::tuple of \c rosa::Optional with each actual value /// wrapped in \c rosa::deluxe::DeluxeTuple. /// /// \tparam Ts types of the values /// \tparam S0 indices for accessing values in \p Values /// /// \param Values the \c std::tuple of \c rosa::Optional with built-in values /// /// \note The second argument provides indices statically as template arguments /// \p S0..., so its actual value is ignored. /// /// \return a \c std::tuple of \c rosa::Optional corresponding to \p Values /// with each actual value wrapped in \c rosa::deluxe::DeluxeTuple /// /// \pre Statically, all type arguments \p Ts... are built-in types and the /// provided indices \p S0... match the length of \p Ts...: \code /// TypeListSubsetOf, BuiltinTypes>::Value && /// sizeof...(Ts) == sizeof...(S0) /// \endcode template std::tuple>...> wrapBuiltinInDeluxeTuple(const std::tuple...> &Values, Seq) noexcept { STATIC_ASSERT((TypeListSubsetOf, BuiltinTypes>::Value), "not built-in types"); STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent type arguments"); return std::make_tuple(std::get(Values) ? Optional>( make_deluxe_tuple(*std::get(Values))) : Optional>()...); } } // End namespace template AgentHandle DeluxeContext::createSensorImpl(const std::string &Name, std::function)> &&MF, std::function &&F) noexcept { AgentHandle H = System->createSensor(Name, std::move(MF), std::move(F)); DeluxeUnits.emplace(H); return H; } template AgentHandle DeluxeContext::createSensor(const std::string &Name, std::function &&F) noexcept { auto EmptyMF = std::function)>( [](std::pair) {}); if constexpr (TypeListContains::Value) { using OutputType = DeluxeTuple; return createSensorImpl( Name, std::move(EmptyMF), std::function( [F{std::move(F)}](void) { return OutputType(F()); })); } else if constexpr (IsDeluxeTuple::Value && !std::is_same::value) { return createSensorImpl(Name, std::move(EmptyMF), std::move(F)); } else { ASSERT(false && "Unexpected type argument"); } } template AgentHandle DeluxeContext::createSensor(const std::string &Name, std::function)> &&MF, std::function &&F) noexcept { if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using MasterInputType = DeluxeTuple; using OutputType = DeluxeTuple; return createSensorImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { MF({std::get<0>(Arg.first), Arg.second}); }), std::function( [F{std::move(F)}](void) { return OutputType(F()); })); } else if constexpr (TypeListAllDeluxeTuple>::Value && !std::is_same::value) { return createSensorImpl(Name, std::move(MF), std::move(F)); } else { ASSERT(false && "Unexpected type arguments"); } } template AgentHandle DeluxeContext::createAgentImpl( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept { AgentHandle H = System->createAgent(Name, std::move(MF), std::move(F)); DeluxeUnits.emplace(H); return H; } template AgentHandle DeluxeContext::createAgent( const std::string &Name, std::function(std::pair...)> &&F) noexcept { using NoMasterOutputType = std::tuple>...>; auto EmptyMF = std::function)>( [](std::pair) { return NoMasterOutputType(); }); if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using OutputType = DeluxeTuple; return createAgentImpl( Name, std::move(EmptyMF), std::function< std::tuple, Optional>...>( std::pair, bool>...)>( [F{std::move(F)}](std::pair, bool>... Args) { const auto Result = F({std::get<0>(Args.first), Args.second}...); return std::tuple_cat( wrapBuiltinInDeluxeTuple(std::tuple(Result), seq_t<1>()), NoMasterOutputType()); })); } else if constexpr (TypeListAllDeluxeTuple>::Value && !std::is_same::value && (true && ... && (!std::is_same::value))) { return createAgentImpl( Name, std::move(EmptyMF), std::function, Optional>...>( std::pair...)>( [F{std::move(F)}](std::pair... Args) { const auto Result = F(Args...); return std::tuple_cat(std::tuple(Result), NoMasterOutputType()); })); } else { ASSERT(false && "Unexpected type arguments"); } } template AgentHandle DeluxeContext::createAgent( const std::string &Name, std::function)> &&MF, std::function(std::pair...)> &&F) noexcept { using NoMasterOutputType = std::tuple>...>; if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using MasterInputType = DeluxeTuple; using OutputType = DeluxeTuple; return createAgentImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { MF({std::get<0>(Arg.first), Arg.second}); return NoMasterOutputType(); }), std::function< std::tuple, Optional>...>( std::pair, bool>...)>( [F{std::move(F)}](std::pair, bool>... Args) { const auto Result = F({std::get<0>(Args.first), Args.second}...); return std::tuple_cat( wrapBuiltinInDeluxeTuple(std::tuple(Result), seq_t<1>()), NoMasterOutputType()); })); } else if constexpr (TypeListAllDeluxeTuple>::Value && !std::is_same::value && (true && ... && (!std::is_same::value))) { return createAgentImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { MF(Arg); return NoMasterOutputType(); }), std::function, Optional>...>( std::pair...)>( [F{std::move(F)}](std::pair... Args) { const auto Result = F(Args...); return std::tuple_cat(std::tuple(Result), NoMasterOutputType()); })); } else { ASSERT(false && "Unexpected type arguments"); } } template AgentHandle DeluxeContext::createAgent( const std::string &Name, std::function, Optional...>( std::pair...)> &&F) noexcept { if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using MasterOutputType = std::tuple>...>; using OutputType = DeluxeTuple; return createAgentImpl( Name, std::function)>( [](std::pair) { return MasterOutputType(); }), std::function< std::tuple, Optional>...>( std::pair, bool>...)>( [F{std::move(F)}](std::pair, bool>... Args) { const auto Result = F({std::get<0>(Args.first), Args.second}...); return wrapBuiltinInDeluxeTuple(Result, seq_t<1 + sizeof...(Ts)>()); })); } else if constexpr (TypeListAllDeluxeTuple< TypeList>::Value && !std::is_same::value && (true && ... && (!std::is_same::value))) { using MasterOutputType = std::tuple...>; return createAgentImpl( Name, std::function)>( [](std::pair) { return MasterOutputType(); }), std::function, Optional...>( std::pair...)>( [F{std::move(F)}](std::pair... Args) { const auto Output = F(Args...); return Output; })); } else { ASSERT(false && "Unexpected type arguments"); } } template AgentHandle DeluxeContext::createAgent( const std::string &Name, std::function...>(std::pair)> &&MF, std::function, Optional...>( std::pair...)> &&F) noexcept { if constexpr (TypeListSubsetOf, BuiltinTypes>::Value) { using MasterInputType = DeluxeTuple; using MasterOutputType = std::tuple>...>; using OutputType = DeluxeTuple; return createAgentImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { const auto Result = MF({std::get<0>(Arg.first), Arg.second}); return wrapBuiltinInDeluxeTuple(Result, seq_t()); }), std::function< std::tuple, Optional>...>( std::pair, bool>...)>( [F{std::move(F)}](std::pair, bool>... Args) { const auto Result = F({std::get<0>(Args.first), Args.second}...); return wrapBuiltinInDeluxeTuple(Result, seq_t<1 + sizeof...(Ts)>()); })); } else if constexpr (TypeListAllDeluxeTuple< TypeList>::Value && !std::is_same::value && (true && ... && (!std::is_same::value))) { using MasterOutputType = std::tuple...>; return createAgentImpl( Name, std::function)>( [MF{std::move(MF)}](std::pair Arg) { const auto Output = MF(Arg); return Output; }), std::function, Optional...>( std::pair...)>( [F{std::move(F)}](std::pair... Args) { const auto Output = F(Args...); return Output; })); } else { ASSERT(false && "Unexpected type arguments"); } } template DeluxeContext::ErrorCode DeluxeContext::registerSensorValues(AgentHandle Sensor, Iterator &&Start, const Iterator &End, T Default) noexcept { // Get the type of values provided by \p Iterator. STATIC_ASSERT((std::is_same::value), "type mismatch"); // Make sure preconditions are met. if (!System->isDeluxeSensor(Sensor)) { DCRETERROR(ErrorCode::NotSensor); } auto S = System->getDeluxeSensor(Sensor); ASSERT(S); // Sanity check. + if (S->simulationDataSourceIsSet()) { DCRETERROR(ErrorCode::AlreadyHasValueStream); } if constexpr (TypeListContains::Value) { if (S->OutputType != TypeToken::Value) { DCRETERROR(ErrorCode::TypeMismatch); } // Register input stream. // \note Need to capture parameters by value so having local copies. S->registerSimulationDataSource(std::function(void)>([= ](void) mutable noexcept->DeluxeTuple { if (Start != End) { LOG_TRACE_STREAM << "Reading next value for sensor '" << S->FullName << "': " << *Start << '\n'; return make_deluxe_tuple(*Start++); } else { LOG_TRACE_STREAM << "Providing default value for sensor '" << S->FullName << "': " << Default << '\n'; return make_deluxe_tuple(Default); } })); - } else if constexpr (IsDeluxeTuple::Value && - !std::is_same::value) { - if (S->OutputType != T::TT) { + } else if constexpr (IsTuple::Value) { + + using TT = matching_deluxe_tuple_t; + if (std::is_same::value || S->OutputType != TT::TT) { DCRETERROR(ErrorCode::TypeMismatch); } // Register input stream. // \note Need to capture parameters by value so having local copies. S->registerSimulationDataSource( - std::function([=](void) mutable noexcept->T { + std::function([=](void) mutable noexcept->TT { if (Start != End) { + const TT DV(*Start++); LOG_TRACE_STREAM << "Reading next value for sensor '" << S->FullName - << "': " << *Start << '\n'; - return *Start++; + << "': " << DV << '\n'; + return DV; } else { + static const TT DD(Default); LOG_TRACE_STREAM << "Providing default value for sensor '" - << S->FullName << "': " << Default << '\n'; - return Default; + << S->FullName << "': " << DD << '\n'; + return DD; } })); } else { ASSERT(false && "Unexpected type argument"); } return ErrorCode::NoError; } } // End namespace deluxe } // End namespace rosa // Undef local macro if not used in the corresponding implementation. #ifndef ROSA_LIB_DELUXE_DELUXECONTEXT_CPP #undef DCRETERROR #endif #endif // ROSA_DELUXE_DELUXECONTEXT_HPP diff --git a/include/rosa/deluxe/DeluxeTuple.hpp b/include/rosa/deluxe/DeluxeTuple.hpp index e57d29b..1e6b717 100644 --- a/include/rosa/deluxe/DeluxeTuple.hpp +++ b/include/rosa/deluxe/DeluxeTuple.hpp @@ -1,359 +1,419 @@ //===-- rosa/deluxe/DeluxeTuple.hpp -----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeTuple.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2019 /// /// \brief Facilities for handling multiple input/output values for connections /// in the *deluxe interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXETUPLE_HPP #define ROSA_DELUXE_DELUXETUPLE_HPP #include "rosa/support/sequence.hpp" #include "rosa/support/type_token.hpp" #include #include namespace rosa { namespace deluxe { /// A tuple to manage multiple input/output values in the *deluxe interface*. /// /// \tparam Ts types of elements of the tuple /// /// \note The template may be instantiated only with built-in types and the /// number of those type may not exceed the capacity of a \c rosa::Token. template struct DeluxeTuple : public std::tuple { // Statically enforce that the class template is instantiated only with // built-in types. STATIC_ASSERT((TypeListSubsetOf, BuiltinTypes>::Value), "not built-in types"); // Statically enforce that the class template is instantiated with not too // many types. // \note Instantiation would fail on \c rosa::deluxe::DeluxeTuple::TT if there - // are too any types; this assertion is for more readable error reporting. + // are too many types; this assertion is for more readable error reporting. STATIC_ASSERT(sizeof...(Ts) <= token::MaxTokenizableListSize, "Too many types"); /// How many elements the instance has. static constexpr token_size_t Length = sizeof...(Ts); /// What types the class contains. /// /// Type information encoded as \c rosa::Token. static constexpr Token TT = TypeToken::Value; /// Default constructor, zero-initializes elements. DeluxeTuple(void) = default; /// Constructor, initializes the underlying \c std::tuple with lvalue /// references. /// /// \param Args value references to the values to store DeluxeTuple(const std::decay_t &... Args) : std::tuple(Args...) {} /// Constructor, initializes the underlying \c std::tuple with rvalue /// references. /// /// \param Args rvalue references to the values to store DeluxeTuple(std::decay_t &&... Args) : std::tuple(std::move(Args)...) {} + /// Contructor, initializes the underlying \c std::tuple from another matching + /// \c std::tuple. + DeluxeTuple(const std::tuple &Args) : std::tuple(Args) {} + /// Default copy-constructor. DeluxeTuple(const DeluxeTuple &) = default; /// Default move-constructor. DeluxeTuple(DeluxeTuple &&) = default; /// Default copy-assignment. DeluxeTuple &operator=(const DeluxeTuple &) = default; /// Default move-assignment. DeluxeTuple &operator=(DeluxeTuple &&) = default; private: /// Dumps \p this object to a given \c std::ostream. /// /// \note Provides implementation for \c rosa::deluxe::DeluxeTuple::dump. /// /// \tparam S0 Indices for accessing elements. /// /// \param [in,out] OS output stream to dump to /// /// \note The second argument provides indices statically as template /// arguments \p S0..., so its actual value is ignored. /// /// \pre Statically, \p S0... matches number of types \p this object was /// created: \code /// sizeof...(S0) == sizeof...(Ts) /// \endcode template void dump(std::ostream &OS, Seq) const noexcept; public: /// Dumps \p this object to a given \c std::ostream. /// /// \param [in,out] OS output stream to dump to void dump(std::ostream &OS) const noexcept; }; template template void DeluxeTuple::dump(std::ostream &OS, Seq) const noexcept { STATIC_ASSERT(sizeof...(S0) == sizeof...(Ts), "inconsistent type arguments"); // Convert value to std::string with std::to_string except for a value of // std::string that does not need conversion. auto dump_to_string = [](const auto &V) { if constexpr (std::is_same, std::string>::value) { return V; } else { return std::to_string(V); } }; OS << "{"; (OS << ... << (" " + dump_to_string(std::get(*this)))); OS << " }"; } template void DeluxeTuple::dump(std::ostream &OS) const noexcept { dump(OS, seq_t()); } /// Type alias for a \c rosa::deluxe::DeluxeTuple that contains no elements. using EmptyDeluxeTuple = DeluxeTuple<>; /// Template specialization for \c rosa::deluxe::EmptyDeluxeTuple. template <> struct DeluxeTuple<> : public std::tuple<> { /// How many elements the instance has. static constexpr token_size_t Length = 0; /// What types the class contains. /// /// Type information encoded as \c rosa::Token. static constexpr Token TT = TypeToken<>::Value; /// Constructor, initializes the underlying \c std::tuple. DeluxeTuple(void) : std::tuple<>() {} /// Default copy-constructor. DeluxeTuple(const DeluxeTuple &) = default; // Default move-constructor. DeluxeTuple(DeluxeTuple &&) = default; /// Default copy-assignment. DeluxeTuple &operator=(const DeluxeTuple &) = default; // Default move-assignment, DeluxeTuple &operator=(DeluxeTuple &&) = default; /// Dumps \p this object to a given \c std::ostream. /// /// \param [in,out] OS output stream to dump to static void dump(std::ostream &OS) noexcept; }; /// Creates a \c rosa::deluxe::DeluxeTuple instance from the given lvalues /// references. /// /// \tparam Ts types of elements of the tuple /// /// \see \c rosa::deluxe::DeluxeTuple /// /// \param Args values to store in the tuple /// /// \return an instance of \c rosa::deluxe::DeluxeTuple with \p Args as /// elements template inline DeluxeTuple make_deluxe_tuple(const Ts &... Args) noexcept { return DeluxeTuple(Args...); } /// Creates a \c rosa::deluxe::DeluxeTuple instance from the given rvalue /// references. /// /// \tparam Ts types of elements of the tuple /// /// \see \c rosa::deluxe::DeluxeTuple /// /// \param Args values to store in the tuple /// /// \return an instance of \c rosa::deluxe::DeluxeTuple with \p Args as /// elements template inline DeluxeTuple make_deluxe_tuple(Ts&&... Args) noexcept { return DeluxeTuple(std::move(Args)...); } +/// Creates a \c rosa::deluxe::DeluxeTuple instance from the given \c std::tuple +/// reference. +/// +/// \tparam Ts types of elements of the tuple +/// +/// \see \c rosa::deluxe::DeluxeTuple +/// +/// \param Args values to store in the tuple +/// +/// \return an instance of \c rosa::deluxe::DeluxeTuple with \p Args as +/// elements +template +inline DeluxeTuple +make_deluxe_tuple(const std::tuple &Args) noexcept { + return DeluxeTuple(Args); +} + /// \defgroup UnwrapDeluxeTuple Implementation of /// rosa::deluxe::UnwrapDeluxeTuple /// /// \brief Unwraps element types from an instance of \c /// rosa::deluxe::DeluxeTuple into a \c rosa::TypeList /// /// Types can be unwrapped from a \c rosa::deluxe::DeluxeTuple instance as \code /// typename UnwrapDeluxeTuple::Type /// \endcode /// /// For example, the following expression evaluates to `true`: \code /// std::is_same>::Type, /// TypeList>::value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam Tuple \c rosa::deluxe::DeluxeTuple to unwrap template struct UnwrapDeluxeTuple; /// Implementation of the template for \c rosa::deluxe::DeluxeTuple instances. template struct UnwrapDeluxeTuple> { using Type = TypeList; }; ///@} -/// \defgroup TypeListUnwrapDeluxeTuple Implementation of -/// \c rosa::deluxe::TypeListUnwrapDeluxeTuple +///@} + +/// \defgroup IsTuple Implementation of \c rosa::deluxe::IsTuple /// -/// \brief Unwraps element types from instances of \c -/// rosa::deluxe::DeluxeTuple in a \c rosa::TypeList. +/// \brief Tells if a type is a tuple as in it can be converted to \c +/// rosa::deluxe::DeluxeTuple. /// -/// Types can be unwrapped from \c rosa::deluxe::DeluxeTuple instances as \code -/// typename TypeListUnwrapDeluxeTuple::Type -/// \endcode +/// \see \c rosa::deluxe::MatchingDeluxeTuple /// -/// For example, the following expression evaluates to `true`: \code -/// std::is_same< -/// typename TypeListUnwrapDeluxeTuple, -/// T3>>::Type, -/// TypeList -/// >::value +/// Whether a type \c T is a tuple can be checked as \code +/// IsTuple::Value /// \endcode ///@{ /// Declaration of the template. /// -/// \tparam List \c rosa::TypeList to check -template struct TypeListUnwrapDeluxeTuple; +/// \tparam T type to check +template struct IsTuple; -/// Specialization for \c rosa::EmptyTypeList. -template <> struct TypeListUnwrapDeluxeTuple { - using Type = EmptyTypeList; +/// Specialization for the case when the type is an instance of \c std::tuple. +template struct IsTuple> { + static constexpr bool Value = true; }; -/// Specialization for the case when the first type in \p List is an instance of -/// \c rosa::deluxe::DeluxeTuple. -template -struct TypeListUnwrapDeluxeTuple, Ts...>> { - using Type = typename TypeListConcat< - typename UnwrapDeluxeTuple>::Type, - typename TypeListUnwrapDeluxeTuple>::Type>::Type; +/// Specialization for the case when the type is an instance of \c std::tuple. +template struct IsTuple> { + static constexpr bool Value = true; }; -/// Implementation for a general first type in \p List. -template -struct TypeListUnwrapDeluxeTuple> { - using Type = typename TypeListPush< - T, typename TypeListUnwrapDeluxeTuple>::Type>::Type; -}; +/// Implementation for a general case of type \p T. +template struct IsTuple { static constexpr bool Value = false; }; ///@} /// \defgroup IsDeluxeTuple Implementation of \c rosa::deluxe::IsDeluxeTuple /// /// \brief Tells if a type is an instance of \c rosa::deluxe::DeluxeTuple. /// /// Whether a type \c T is an instance of \c rosa::deluxe::DeluxeTuple can be /// checked as \code /// IsDeluxeTuple::Value /// \endcode +/// +/// \note `!IsDeluxeTuple::Value || IsTuple::Value` ///@{ /// Declaration of the template. /// /// \tparam T type to check template struct IsDeluxeTuple; /// Specialization for the case when the type is an instance of \c /// rosa::deluxe::DeluxeTuple. template struct IsDeluxeTuple> { static constexpr bool Value = true; }; /// Implementation for a general case of type \p T. template struct IsDeluxeTuple { static constexpr bool Value = false; }; ///@} +/// \defgroup MatchingDeluxeTuple Implementation of \c +/// rosa::deluxe::MatchingDeluxeTuple +/// +/// \brief Gives the \c rosa::deluxe::DeluxeTuple type that matches a given +/// tuple type. +/// +/// The matching \c rosa::deluxe::DeluxeTuple type for a tuple type \p T can be +/// obtained as \code +/// typename MatchingDeluxeTuple::Type +/// \endcode +/// If \p T is \c rosa::deluxe::DeluxeTuple, the matching type is \p T itself. +/// If \p T is \c std::tuple, the matching type if \c rosa::deluxe::DeluxeTuple +/// with the same type parameters as \p T. +/// \c rosa::deluxe::MatchingDeluxeTuple is not defined for other type +/// parameters. +/// +/// \note The template is defined for type \p T only if +/// `rosa::deluxe::IsTuple::Value`. Values of such types can be used to +/// construct a new instance of the class \c rosa::deluxe::DeluxeTuple. +/// +///\see \c rosa::deluxe::IsTuple +///@{ + +/// Declaration of the template. +/// +/// \tparam T type to check +template struct MatchingDeluxeTuple; + +/// Specialization for the case when the type is an instance of \c +/// rosa::deluxe::DeluxeTuple. +template struct MatchingDeluxeTuple> { + using Type = DeluxeTuple; +}; + +/// Specialization for the case when the type is an instance of \c +/// std::tuple. +template struct MatchingDeluxeTuple> { + using Type = DeluxeTuple; +}; + +///@} + +/// Convenience template type alias for easy use of \c +/// rosa::deluxe::MatchingDeluxeTuple. +/// +/// Converts a tuple type to the matching \c rosa::deluxe::DeluxeTuple type. +/// +/// \tparam Tuple type to convert +template +using matching_deluxe_tuple_t = typename MatchingDeluxeTuple::Type; + /// \defgroup TypeListAllDeluxeTuple Implementation of /// \c rosa::deluxe::TypeListAllDeluxeTuple /// /// \brief Tells if all types in a \c rosa::TypeList is an instance of \c /// rosa::deluxe::DeluxeTuple. /// /// Whether a \c rosa::TypeList \c List contains instances of \c /// rosa::deluxe::DeluxeTuple only can be checked as \code /// TypeListAllDeluxeTuple::Value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to check template struct TypeListAllDeluxeTuple; /// Specialization for \c rosa::EmptyTypeList. template <> struct TypeListAllDeluxeTuple { static constexpr bool Value = true; }; /// Implementation for the general case when there is at leasst one element in /// the list. template struct TypeListAllDeluxeTuple> { static constexpr bool Value = IsDeluxeTuple::Value && TypeListAllDeluxeTuple>::Value; }; ///@} } // End namespace deluxe } // End namespace rosa namespace std { /// Dumps a \c rosa::deluxe::Deluxe instance to a given \c std::ostream. /// /// \param [in,out] OS output stream to dump to /// \param Tuple \c rosa::deluxe::Deluxe to dump /// /// \return \p OS after dumping \p Tuple to it template ostream &operator<<(ostream &OS, const rosa::deluxe::DeluxeTuple &Tuple) { Tuple.dump(OS); return OS; } } // End namespace std #endif // ROSA_DELUXE_DELUXETUPLE_HPP diff --git a/include/rosa/support/csv/CSVReader.hpp b/include/rosa/support/csv/CSVReader.hpp index 0491c17..5402018 100755 --- a/include/rosa/support/csv/CSVReader.hpp +++ b/include/rosa/support/csv/CSVReader.hpp @@ -1,382 +1,486 @@ //===-- rosa/support/csv/CSVReader.hpp --------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/csv/CSVReader.hpp /// -/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// \authors David Juhasz (david.juhasz@tuwien.ac.at), Edwin Willegger (edwin.willegger@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Facitilities to read CSV files. /// /// \note The implementation is based on the solution at /// https://stackoverflow.com/a/1120224 /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_CSV_CSVREADER_HPP #define ROSA_SUPPORT_CSV_CSVREADER_HPP #include "rosa/support/debug.hpp" +#include "rosa/support/sequence.hpp" #include #include #include +#include +#include +#include namespace rosa { namespace csv { +/// Indicating it the CSV file contains any header or not +enum class HeaderInformation { + HasHeader, + HasNoHeader +}; + /// Anonymous namespace providing implementation details for /// \c rosa::csv::CSVIterator, consider it private. namespace { -/// Provides facility for parsing values from one row CSV data. +/// Provides facility for parsing one value from a string. /// -/// \tparam T type of values to parse from the line +/// \tparam T type of value to parse /// \tparam IsSignedInt if \p T is a signed integral type, always use default /// \tparam IsUnsignedInt if \p T is an unsigned integral type, always use /// default /// \tparam IsFloat if \p T is a floating-point type, always use default /// \tparam IsString if \p T is \c std::string, always use default /// -/// \note Specializations of this `struct` are provided for arithmentic types +/// \note Specializations of this struct are provided for arithmentic types /// and \c std::string. -template ::value && - std::is_signed::value), +template ::value && std::is_signed::value), bool IsUnsignedInt = (std::is_integral::value && std::is_unsigned::value), bool IsFloat = std::is_floating_point::value, bool IsString = std::is_same::value> -struct CSVRowParser; +struct ValueParser { -/// Specialization for signed integral types. -/// -/// \tparam T type of values to parse from the line -/// -/// \pre \p T is a signed integral type:\code -/// std::is_integral::value && std::is_signed::value -/// \endcode -template struct CSVRowParser { - STATIC_ASSERT((std::is_integral::value && std::is_signed::value), - "wrong type"); // Sanity check. - - /// Parses a given row of CSV data into a given container. /// - /// \p Data is cleared and then filled with values parsed from \p LineStream. - /// Entries in the line are to be separated by commas, the character `,`. A - /// trailing comma results in an empty entry at the end of the line. No empty - /// entry should be present otherwise. /// - /// \note Parsed values are silently converted to type \p T. + /// \param Cell the \c std::string to parse /// - /// \param [in,out] LineStream the line to parse - /// \param [in,out] Data the container to store the parsed values - static void parse(std::stringstream &LineStream, std::vector &Data) { - std::string Cell; - Data.clear(); - while (std::getline(LineStream, Cell, ',')) { - Data.push_back(static_cast(std::stoll(Cell))); - } - // This checks for a trailing comma with no data after it. - if (!LineStream && Cell.empty()) { - // If there was a trailing comma then add an empty element. - Data.push_back(0); - } + /// \return the parsed value + /// + /// \note The function silently fails if cannot parse \p Cell for type \p T. + static T parse(const std::string &Cell) noexcept; +}; + +template +struct ValueParser { + STATIC_ASSERT((std::is_integral::value && std::is_signed::value), + "wrong type"); // Sanity check. + static T parse(const std::string &Cell) noexcept { + return static_cast(std::stoll(Cell)); } }; -/// Specialization for unsigned integral types. -/// -/// \tparam T type of values to parse from the line -/// -/// \pre \p T is an unsigned integral type:\code -/// std::is_integral::value && std::is_unsigned::value -/// \endcode -template struct CSVRowParser { +template +struct ValueParser { STATIC_ASSERT((std::is_integral::value && std::is_unsigned::value), "wrong type"); // Sanity check. - - /// Parses a given row of CSV data into a given container. - /// - /// \p Data is cleared and then filled with values parsed from \p LineStream. - /// Entries in the line are to be separated by commas, the character `,`. A - /// trailing comma results in an empty entry at the end of the line. No empty - /// entry should be present otherwise. - /// - /// \note Parsed values are silently converted to type \p T. - /// - /// \param [in,out] LineStream the line to parse - /// \param [in,out] Data the container to store the parsed values - static void parse(std::stringstream &LineStream, std::vector &Data) { - std::string Cell; - Data.clear(); - while (std::getline(LineStream, Cell, ',')) { - Data.push_back(static_cast(std::stoull(Cell))); - } - // This checks for a trailing comma with no data after it. - if (!LineStream && Cell.empty()) { - // If there was a trailing comma then add an empty element. - Data.push_back(0); - } + static T parse(const std::string &Cell) noexcept { + return static_cast(std::stoull(Cell)); } }; -/// Specialization for floating-point types. -/// -/// \tparam T type of values to parse from the line -/// -/// \pre \p T is a floating-point type:\code -/// std::is_floating_point::value -/// \endcode -template struct CSVRowParser { +template +struct ValueParser { STATIC_ASSERT((std::is_floating_point::value), "wrong type"); // Sanity check. - - /// Parses a given row of CSV data into a given container. - /// - /// \p Data is cleared and then filled with values parsed from \p LineStream. - /// Entries in the line are to be separated by commas, the character `,`. A - /// trailing comma results in an empty entry at the end of the line. No empty - /// entry should be present otherwise. - /// - /// \note Parsed values are silently converted to type \p T. - /// - /// \param [in,out] LineStream the line to parse - /// \param [in,out] Data the container to store the parsed values - static void parse(std::stringstream &LineStream, std::vector &Data) { - std::string Cell; - Data.clear(); - while (std::getline(LineStream, Cell, ',')) { - Data.push_back(static_cast(std::stold(Cell))); - } - // This checks for a trailing comma with no data after it. - if (!LineStream && Cell.empty()) { - // If there was a trailing comma then add an empty element. - Data.push_back(0); - } + static T parse(const std::string &Cell) noexcept { + return static_cast(std::stold(Cell)); } }; -/// Specialization for \c std::string. -/// -/// \tparam T type of values to parse from the line -/// -/// \pre \p T is \c std::string:\code -/// std::is_same::value -/// \endcode -template struct CSVRowParser { +template +struct ValueParser { STATIC_ASSERT((std::is_same::value), "wrong type"); // Sanity check. - - /// Parses a given row of CSV data into a given container. - /// - /// \p Data is cleared and then filled with values parsed from \p LineStream. - /// Entries in the line are to be separated by commas, the character `,`. A - /// trailing comma results in an empty entry at the end of the line. No empty - /// entry should be present otherwise. - /// - /// \param [in,out] LineStream the line to parse - /// \param [in,out] Data the container to store the parsed values - static void parse(std::stringstream &LineStream, std::vector &Data) { - std::string Cell; - Data.clear(); - while (std::getline(LineStream, Cell, ',')) { - Data.push_back(Cell); - } - // This checks for a trailing comma with no data after it. - if (!LineStream && Cell.empty()) { - // If there was a trailing comma then add an empty element. - Data.push_back(""); - } - } + static T parse(const std::string &Cell) noexcept { return Cell; } }; /// Parses and stores entries from a row of CSV data. /// -/// \tparam T type of values to parse and store, i.e. entries in the row +/// \tparam Ts types of values to parse and store, i.e. entries in the row /// /// \note The implementation relies on \c rosa::csv::CSVRowParser, which is -/// implemented only for `arithmetic` types -- signed and unsigned integral and -/// floating-point types -- and for \c std::string. Those are the valid values -/// for \p T. -template -class CSVRow { -public: - /// Gives a constant reference for an entry at a given position of the row. +/// implemented only for `arithmetic` types -- signed and unsigned integral +/// and floating-point types -- and for \c std::string. Those are the valid +/// values for \p Ts. +template class CSVRow { +private: + /// Parses a given row of CSV data into \c CSVRow::Data. /// - /// \note No bounds checking is performed. + /// \ CSVRow::Data is filled with values parsed from \p LineStream. Entries + /// in the line are to be separated by commas, the character `,`. /// - /// \param Index the position of the entry + /// \note Parsed values are silently converted to types \p Ts. + /// + /// \note Parsing silently fails if values do not match \p Ts. + /// + /// \tparam S0 indices to access tuple elements. + /// + /// \param [in,out] LineStream the line to parse /// - /// \return constant reference for the stored entry at position \p Index - const T &operator[](const size_t Index) const noexcept { return Data[Index]; } + /// \note The last argument is used only to get \p S0, the actual value of + /// the parameter is ignored. + template + void parseRow(std::stringstream &LineStream, char Delimeter, Seq) { + STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), + "Not matching template arguments."); + std::string Cell; + // Get fields and parse the values into the proper element of the tuple + // one by one in a fold expression. + ((std::getline(LineStream, Cell, Delimeter), + std::get(Data) = ValueParser::parse(Cell)), + ...); + + } + +public: - /// Tells the number of entries stored in the row. + /// Constructor with all possible parameters + /// + /// The function creates an instance of an CSVRow object and sets the attributes of the + /// object to the values of the parameters. /// - /// \return number of stored entries. - size_t size(void) const noexcept { return Data.size(); } + /// \param SkipRows the number of data rows to skip, not taking header into account. + /// \param HeaderInfo is the first line of the file a header row or not. + /// \param Delimeter to seperate between the data entries within one row. + CSVRow(const size_t SkipRows = 0, + const HeaderInformation HeaderInfo = HeaderInformation::HasHeader, + const char Delimeter = ',') : + SkipRows(SkipRows), HeaderInfo(HeaderInfo), Delimeter(Delimeter), + RowNumber(0), IsHeaderRead(false) { + } + + /// Parses and stores one row of CSV data. /// /// The function reads one line from \p Str and parses it into /// \c rosa::csv::CSVRow::Data using \c rosa::csv::CSVRowParser. /// /// \param [in,out] Str input stream of a CSV file - void readNextRow(std::istream &Str) { + void readNextRow(std::istream &Str) noexcept { std::string Line; + std::getline(Str, Line); - std::stringstream LineStream(Line); - CSVRowParser::parse(LineStream, Data); + + if(Line.size() > 0){ + std::stringstream LineStream(Line); + parseRow(LineStream, Delimeter, seq_t()); + + RowNumber = RowNumber + 1; + } + + } + + /// Read header row and stores it as \p std::string. + /// + /// The function reads the first line of the csv file and stores the entries + /// in a vector. + /// + /// \param [in,out] Str input stream of a CSV file + void readHeader(std::istream &Str) noexcept { + std::string Line; + std::getline(Str, Line); + std::stringstream LineStream(Line); + std::string Value; + + while( getline(LineStream, Value, Delimeter) ){ + Header.push_back(Value); + } + + IsHeaderRead = true; + } + + /// The number of rows to skip once. + /// + /// This function returns the number of data rows to skip + /// at the beginning of the file. + /// + /// \return The number of rows to skip at the beginning of a csv file. + inline size_t SkipNumRows() const noexcept { + return this->SkipRows; + } + + /// The current row number within the csv file. + /// + /// This function returns the current row number. The header + /// row is not counted as a row. + /// + /// \returns the current row number within the csv file. + inline size_t CurRow() const noexcept { + return this->RowNumber; + } + + /// Indiciates if the header was already read. + /// + /// This function returns true, if the header of a csv file which contains + /// a header file is already read. + /// The user has to pass in the attribute HeaderInfo the information if the + /// file has in the first row the header row or not. + /// + /// \return if the header of a file is already read. + inline bool IsHeaderReadDone() const noexcept{ + return this->IsHeaderRead; + } + + + /// Indicates if the file contains a header row in the first row. + /// + /// This function returns if the file contains a header row. + /// The information if the file contains a header row or not, has to be passed by the user. + /// The standard value is HeaderInformation::HasHeader + /// + /// \return if the csv file contains a header row in the first line of the file. + inline HeaderInformation HasFileHeader() const noexcept { + return this->HeaderInfo; + } + + /// Set the number of rows to skip. + /// + /// This function sets the number of rows to skip at the beginning of + /// the reading of the file. + /// + /// \param SkipRows the number of rows you want to skip at the beginning of the file. + inline void SetSkipRows(const size_t SkipRows) noexcept { + this->SkipRows = SkipRows; + } + + /// Is the first row a header row or not. + /// + /// This function sets the information, if the first row of the csv file + /// is a header line or not. + /// + /// \param HeaderInfo if the first row is a header row or not. + inline void SetHeaderInfo(const HeaderInformation HeaderInfo) noexcept { + this->HeaderInfo = HeaderInfo; + } + + /// Set the seperator between data entries. + /// + /// This funcction sets the separator between the data entries of the csv file. + /// + /// \param Delimeter the character that separates the data values. + inline void SetDelimeter(char Delimeter) { + this->Delimeter = Delimeter; } + + + + /// Gives a constant references for the \c std::tuple containing the values + /// read by \p this object. + /// + /// \return \c CSVRow::Data + const std::tuple &tuple(void) const noexcept { return Data; } + private: - std::vector Data; ///< Stores parsed entries + std::tuple Data; ///< Stores parsed entries + size_t SkipRows; ///< The number of rows to skip at the very beginning of the file. + ///< This number only applies on the number of data rows. + ///< If your file contains a header row and data rows, the skiping + ///< of the header row is not taken into account. + HeaderInformation HeaderInfo; ///< If the file contains a header row or not. + char Delimeter; ///< The seperator between the data entries. + size_t RowNumber; ///< Current row number, counts all row numbers including the header row. + bool IsHeaderRead; ///< Was the header read or not. + std::vector Header; ///< The content of the header row. + }; /// Reads a row of CSV data into \c rosa::csv::CSVRow. /// /// The next line is read from \p Str by calling -/// \c rosa::csv::CSVRow::readNextRow on \p Data. +/// \c rosa::csv::CSVRow::readNextRow on \p Data until all lines are +/// skipped. /// -/// \note A CSV file should contain no empty lines. +/// If the function is called for the first time and the file contains +/// a header than is the header and the first data row read in after the +/// number of rows that the user wants to skip. +/// +/// \tparam Ts type of values to read from the row +/// +/// \note The CSV file should contain a line with fields matching \p Ts... /// /// \param [in,out] Str input stream of a CSV file /// \param [in,out] Data object to read the next line into /// /// \return \p Str after reading one line from it -template -std::istream &operator>>(std::istream &Str, CSVRow &Data) { +template +std::istream &operator>>(std::istream &Str, CSVRow &Data) { + + if( Data.HasFileHeader() == HeaderInformation::HasHeader && !Data.IsHeaderReadDone() ) { + Data.readHeader(Str); + } + + while(Data.CurRow() < (Data.SkipNumRows())){ + Data.readNextRow(Str); + } + + //read the lines after you skipped the number of rows you want to skip Data.readNextRow(Str); + return Str; } } // End namespace -/// Provides `InputIterator` features for iterating over a CSV file in a -/// flat way. +/// Provides `InputIterator` features for iterating over a CSV file. /// -/// The iterator hides rows of the CSV file, and iterates over the entries -/// row-by-row. +/// The iterator parses rows into `std::tuple` values and iterates over the +/// file row by row. /// -/// \note A CSV file should contain no empty lines. +/// \tparam Ts types of values stored in one row of the CSV file /// -/// \tparam T type of values to iterate over, i.e. entries in the CSV file. +/// \note The iterator expects each row to consists of fields matching \p Ts. /// /// \note The implementation relies on \c rosa::csv::CSVRow, which in turn /// relies on \c rosa::csv::CSVRowParser, which is implemented only for /// `arithmetic` types -- signed and unsigned integral types and floating-point -/// types -- and for \c std::string. Those are the valid values for \p T. -template -class CSVFlatIterator { +/// types -- and for \c std::string. Those are the valid values for \p Ts +template class CSVIterator { public: - /// \defgroup CSVFlatIteratorTypedefs Typedefs of rosa::csv::CSVFlatIterator + /// \defgroup CSVIteratorTypedefs Typedefs of rosa::csv::CSVIterator /// /// Standard `typedef`s for iterators. /// ///@{ typedef std::input_iterator_tag - iterator_category; ///< Category of the iterator. - typedef T value_type; ///< Type of values iterated over. - typedef std::size_t difference_type; ///< Type to identify distance. - typedef T *pointer; ///< Pointer to the type iterated over. - typedef T &reference; ///< Reference to the type iterated over. + iterator_category; ///< Category of the iterator. + typedef std::tuple value_type; ///< Type of values iterated over. + typedef std::size_t difference_type; ///< Type to identify distance. + typedef std::tuple *pointer; ///< Pointer to the type iterated over. + typedef std::tuple + &reference; ///< Reference to the type iterated over. ///@} /// Creates a new instance. /// /// \param [in,out] S input stream to iterate over - CSVFlatIterator(std::istream &S) - : Str(S.good() ? &S : nullptr), Pos((size_t)(-1)) { - // \c rosa::csv::CSVFlatIterator::Pos is initialized to `-1` so the first - // incrementation here will set it properly. + /// \param SkipRows the number of rows you want to skip only once at the beginning of the file. + /// If you have an header in the file, it is supposed to be the first row, and it will be always read out. + /// But after this header the next number of Rows will be skipped. + /// \param HeaderInfo is used to know wheter the file contains an header row or not. + /// The header has to be in the first row. + /// \param Delimeter is the separator between the differnt values of the csv file. + CSVIterator(std::istream &S, const size_t SkipRows = 0, + const HeaderInformation HeaderInfo = HeaderInformation::HasHeader, + const char Delimeter = ',') : Str(S.good() ? &S : nullptr), + SkipRows(SkipRows), HeaderInfo(HeaderInfo), Delimeter(Delimeter), Row(){ + + Row.SetSkipRows(SkipRows); + Row.SetHeaderInfo(HeaderInfo); + Row.SetDelimeter(Delimeter); + + // \c rosa::csv::CSVIterator::Row is initialized empty so the first + // incrementation here will read the first row. ++(*this); } /// Creates an empty new instance. - CSVFlatIterator(void) noexcept : Str(nullptr) {} + CSVIterator(void) noexcept : Str(nullptr) {} /// Pre-increment operator. /// - /// The implementation moves over the entries in the current row and advances - /// to the next row when the end of the current row is reached. If the end of - /// the input stream is reached, the operator becomes empty and has no - /// further effect. + /// The implementation reads the next row. If the end of the input stream is + /// reached, the operator becomes empty and has no further effect. /// /// \return \p this object after incrementing it. - CSVFlatIterator &operator++() { + CSVIterator &operator++() { if (Str) { - ++Pos; - if (Pos == Row.size()) { - if (!((*Str) >> Row)) { - Str = nullptr; - --Pos; // Stay on the last entry forever. - } else { - Pos = 0; - } + if (!((*Str) >> Row)) { + Str = nullptr; } } return *this; } /// Post-increment operator. /// /// The implementation uses the pre-increment operator and returns a copy of /// the original state of \p this object. /// /// \return \p this object before incrementing it. - CSVFlatIterator operator++(int) { - CSVFlatIterator Tmp(*this); + CSVIterator operator++(int) { + CSVIterator Tmp(*this); ++(*this); return Tmp; } /// Returns a constant reference to the current entry. /// /// \note Should not dereference the iterator when it is empty. /// /// \return constant reference to the current entry. - const T &operator*(void)const noexcept { return Row[Pos]; } + const std::tuple &operator*(void)const noexcept { return Row.tuple(); } /// Returns a constant pointer to the current entry. /// /// \note Should not dereference the iterator when it is empty. /// /// \return constant pointer to the current entry. - const T *operator->(void)const noexcept { return &Row[Pos]; } + const std::tuple *operator->(void)const noexcept { + return &Row.tuple(); + } /// Tells if \p this object is equal to another one. /// /// Two \c rosa::csv::CSVReader instances are equal if and only if they are /// the same or both are empty. /// /// \param RHS other object to compare to /// /// \return whether \p this object is equal with \p RHS - bool operator==(const CSVFlatIterator &RHS) const noexcept { + bool operator==(const CSVIterator &RHS) const noexcept { return ((this == &RHS) || ((this->Str == nullptr) && (RHS.Str == nullptr))); } /// Tells if \p this object is not equal to another one. /// /// \see rosa::csv::CSVReader::operator== /// /// \param RHS other object to compare to /// /// \return whether \p this object is not equal with \p RHS. - bool operator!=(const CSVFlatIterator &RHS) const noexcept { + bool operator!=(const CSVIterator &RHS) const noexcept { return !((*this) == RHS); } + /// Set the delimeter used in the csv file. + /// \param Delimeter the character which separates the values in the csv file. + inline void setDelimeter(char Delimeter) noexcept { + this->Delimeter = Delimeter; + } + + /// get the delimeter currently set to separate the values in the csv file. + /// \return the current character, which is used to separte teh values in the csv file. + inline char getDelimeter() const noexcept { + return this->Delimeter; + } + private: - std::istream *Str; ///< Input stream of a CSV file to iterate over. - CSVRow Row; ///< Content of the current row iterating over. - size_t Pos; ///< Current position within the current row. + std::istream *Str; ///< Input stream of a CSV file to iterate over. + size_t SkipRows; ///< Number of Rows to skip only once at the beginning of the file. + HeaderInformation HeaderInfo; ///< does the csv file contain a header or not, if this information is + ///< not given correclty, the reading of the header would result in + ///< in an error. + char Delimeter; ///< Delimeter between the entries in the csv file. + CSVRow Row; ///< Content of the current row + }; } // End namespace csv } // End namespace rosa #endif // ROSA_SUPPORT_CSV_CSVREADER_HPP diff --git a/include/rosa/support/csv/CSVWriter.hpp b/include/rosa/support/csv/CSVWriter.hpp index b67de6a..a077e3d 100755 --- a/include/rosa/support/csv/CSVWriter.hpp +++ b/include/rosa/support/csv/CSVWriter.hpp @@ -1,99 +1,221 @@ //===-- rosa/support/csv/CSVWriter.hpp --------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/csv/CSVWriter.hpp /// -/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// \authors David Juhasz (david.juhasz@tuwien.ac.at) +/// Edwin Willegger (edwin.willegger@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Facitilities to write CSV files. /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_CSV_CSVWRITER_HPP #define ROSA_SUPPORT_CSV_CSVWRITER_HPP +#include #include +#include +#include +#include + +#include "rosa/support/log.h" + namespace rosa { namespace csv { /// Provides facilities to write values into a CSV file. /// /// The writer emits a comma, the character `,`, between each written values. /// The resulted stream is a flat CSV file as it consists of onlyone row, no new /// line is emitted. /// /// \tparam T type of values to write template class CSVWriter { public: /// Creates a new instance. /// /// \param [in,out] S output stream to write to /// /// \note The writer operates on non-binary outputs as long as \p S is in /// good state. CSVWriter(std::ostream &S) : Str(S.good() && !(S.flags() & std::ios::binary) ? &S : nullptr), IsFirst(true) {} /// Tells if the last operation was successful. /// /// \return if the last operation was successful bool good(void) const noexcept { return Str != nullptr; } /// Writes an entry to the output stream. /// /// The implementation does anything only if the last operation was /// successful. If so, \p V is written to \c rosa::csv::CSVWriter::Str. /// The emitted value is preceded with a comma if the actual call is not the /// first one for \p this object. Success of the operation is checked at the /// end. /// /// \param V value to write void write(const T &V) { if (Str) { if (!IsFirst) { *Str << ','; } else { IsFirst = false; } *Str << V; if (!Str->good()) { Str = nullptr; } } } private: std::ostream *Str; ///< Output stream to write to. bool IsFirst; ///< Denotes if the next write would be the first one. }; +/// Writes a tuple of values into a CSV file +/// +/// \tparam Ts types of values to write +template class CSVTupleWriter { + +public: + +// typedef value_type ; ///< Type of values written. + typedef std::tuple value_type; + + /// Creates a new instance. + /// + /// \param [in,out] S output stream to write to + /// + /// \note The writer operates on non-binary outputs as long as \p S is in + /// good state. + CSVTupleWriter(std::ostream &S) + : Str(S.good() && !(S.flags() & std::ios::binary) ? &S : nullptr), + IsHeaderWritten(false), IsDataWritten(false) {} + + /// Tells if the last operation was successful. + /// + /// \return if the last operation was successful + bool good(void) const noexcept { + return Str != nullptr; + } + + + /// Write the values of a tuple to a CSV file with \c rosa::csv::CSVTupleWriter. + /// + /// \see rosa::csv::CSVTupleWriter + /// + /// + /// \param [in,out] values tuple, which values are written in a recusive fashion into a stream. + template + void write(const std::tuple &values) { + constexpr size_t size = sizeof...(Ts); + + LOG_TRACE_STREAM << "Writing tuple values into file \n"; + LOG_TRACE_STREAM << " Tuple has " << std::to_string(size) << " elements. \n"; + + LOG_TRACE_STREAM << " Value is " << std::get(values); + + if(Str){ + /// Write the current element of the tuple into the stream and add a separtor after it, + /// and call the function for the next element in the tuple. + if constexpr(i+1 != sizeof...(Ts)){ + *Str << std::get(values) << ", "; + write(values); + /// If the last element is written into the stream than begin a new line. + }else if constexpr(i + 1 == sizeof...(Ts)){ + *Str << std::get(values) << '\n'; + /// every time the last data value of a line is written, the flag indicates that data was already written into the file. + IsDataWritten = true; + } + } + } + + /// Write the header values to a CSV file with \c rosa::csv::CSVTupleWriter. + /// + /// \note The function has no effect if anything has already been written + /// to the output stream either by \c + /// rosa::csv::CSVTupleWriter::writeHeader() or \c + /// rosa::csv::CSVTupleWriter::write(). + /// + /// \see rosa::csv::CSVTupleWriter + /// + /// \param header the content of the header line. + void writeHeader(const std::array &header){ + size_t index = 0; + /// write into the stream only, if it is not a nullptr, and if no data and no header was already written into it. + if(Str && IsDataWritten == false && IsHeaderWritten == false){ + index = 0; + for (auto i = header.begin(); i != header.end(); ++i){ + index = index + 1; + /// write into the stream every entry with a delimeter, in this case ", " until + /// the last entry + if(index != header.size()){ + *Str << *i << ", "; + /// write the last entry into the stream, without any delimeter + }else { + *Str << *i; + } + } + /// finish the header line and start a new line. + *Str << '\n'; + /// now it is not possible to write additional header lines. + IsHeaderWritten = true; + } + } + +private: + std::ostream *Str; ///< Output stream to write to. + bool IsHeaderWritten; ///< If an header line was already written into the stream. If set than no additional header could be written. + bool IsDataWritten; ///< If one line of data has already been written into the stream, than no headerline could be added. +}; + +/// Writes all values of a tuple to a CSV file with \c rosa::csv::CSVTupleWriter. +/// +/// \see rosa::csv::CSVTupleWriter +/// +/// \tparam Ts types of values to write +/// +/// \param [in,out] W object to write with +/// \param V values to write +/// +/// \return \p W after writing \p V with it +template +CSVTupleWriter &operator<<(CSVTupleWriter &W, const std::tuple &V) +{ + W.write(V); + return W; +} + /// Writes a value to a CSV file with \c rosa::csv::CSVWriter. /// /// \see rosa::csv::CSVWriter /// /// \tparam T type of value to write /// /// \param [in,out] W object to write with /// \param V value to write /// /// \return \p W after writing \p V with it template CSVWriter &operator<<(CSVWriter &W, const T& V) { W.write(V); return W; } } // End namespace csv } // End namespace rosa #endif // ROSA_SUPPORT_CSV_CSVWRITER_HPP diff --git a/include/rosa/support/iterator/namespace.h b/include/rosa/support/iterator/namespace.h new file mode 100644 index 0000000..027978d --- /dev/null +++ b/include/rosa/support/iterator/namespace.h @@ -0,0 +1,25 @@ +//===-- rosa/support/iterator/namespace.h -----------------------*- C++ -*-===/ +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===/ +/// +/// \file rosa/support/iterator/namespace.h +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Documentation for the namespace \c rosa::iterator. +/// +//===----------------------------------------------------------------------===/ + +#ifndef ROSA_SUPPORT_ITERATOR_NAMESPACE_H +#define ROSA_SUPPORT_ITERATOR_NAMESPACE_H + +namespace rosa { +/// Provides facilities to work with iterators. +namespace iterator {} +} // End namespace rosa + +#endif // ROSA_SUPPORT_ITERATOR_NAMESPACE_H diff --git a/include/rosa/support/iterator/split_tuple_iterator.hpp b/include/rosa/support/iterator/split_tuple_iterator.hpp new file mode 100644 index 0000000..f5af821 --- /dev/null +++ b/include/rosa/support/iterator/split_tuple_iterator.hpp @@ -0,0 +1,469 @@ +//===-- rosa/support/iterator/split_tuple_iterator.hpp ----------*- C++ -*-===/ +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===/ +/// +/// \file rosa/support/iterator/split_tuple_iterator.hpp +/// +/// \authors David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Facilities to split iterators of \c std::tuple into iterators of +/// their elements. +/// +//===----------------------------------------------------------------------===/ + +#ifndef ROSA_SUPPORT_ITERATOR_SPLIT_TUPLE_ITERATOR_HPP +#define ROSA_SUPPORT_ITERATOR_SPLIT_TUPLE_ITERATOR_HPP + +#include "rosa/support/debug.hpp" +#include "rosa/support/sequence.hpp" + +#include +#include +#include +#include + +namespace rosa { +namespace iterator { + +/// Anonymous namespace providing implementation for splitting tuple iterators; +/// consider it private. +namespace { + +/// Iterator of values provided by a buffer based on an index +/// +/// \tparam TB buffer providing values to iterate +/// \tparam I index of the values to iterator from \p TB +/// +/// \note \p TB is expected to implemnet an interface matching that of \c +/// TupleIteratorBuffer. +template +class SplittedElementIterator { +public: + /// Type alias for the values the iterator iterates. + using T = typename TB::template element_type; + + /// \defgroup SplittedElementIteratorTypedefs Typedefs of + /// \c SplittedElementIterator + /// + /// Standard `typedef`s for iterators. + /// + ///@{ + typedef std::input_iterator_tag + iterator_category; ///< Category of the iterator. + typedef T value_type; ///< Type of values iterated over. + typedef std::size_t difference_type; ///< Type to identify distance. + typedef T *pointer; ///< Pointer to the type iterated over. + typedef T &reference; ///< Reference to the type iterated ver. + ///@} + + /// Creates a new instance. + /// + /// \param Buffer buffer providing values for \p this object + /// + /// \note \p Buffer is captured as a \c std::shared_ptr because it is shared + /// among iterators for its element positions. + SplittedElementIterator(std::shared_ptr Buffer) + : Buffer(Buffer), Value() { + // \c Value is initialized empty so the first incrementation here will read + // the first value. + ++(*this); + } + + /// Creates an empty new instance. + SplittedElementIterator(void) noexcept : Buffer(), Value() {} + + /// Pre-increment operator. + /// + /// The implementation reads the next value from \c Buffer. If no more values + /// can be provided by \c Buffer, \p this object becomes empty and the + /// operator has no further effect. + /// + /// \return \p this object after incrementing it. + SplittedElementIterator &operator++() { + if (Buffer->template hasMore()) { + Value = Buffer->template next(); + } else { + // Reached end of range, remove reference of \c Buffer. + Buffer.reset(); + } + return *this; + } + + /// Post-increment operator. + /// + /// The implementation uses the pre-increment operator and returns a copy of + /// the original state of \p this object. + /// + /// \return \p this object before incrementing it. + SplittedElementIterator operator++(int) { + SplittedElementIterator Tmp(*this); + ++(*this); + return Tmp; + } + + /// Returns a constant reference to the current value. + /// + /// \note Should not dereference the iterator when it is empty. + /// + /// \return constant reference to the current value. + const T &operator*(void)const noexcept { return Value; } + + /// Returns a constant pointer to the current value. + /// + /// \note Should not dereference the iterator when it is empty. + /// + /// \return constant pointer to the current value. + const T *operator->(void)const noexcept { return &Value; } + + /// Tells if \p this object is equal to another one. + /// + /// Two \c SplittedElementIterator instances are equal if and only if they are + /// the same or both are empty. + /// + /// \param RHS other object to compare to + /// + /// \return whether \p this object is equal with \p RHS + bool operator==(const SplittedElementIterator &RHS) const noexcept { + return ((this == &RHS) || (!Buffer && !RHS.Buffer)); + } + + /// Tells if \p this object is not equal to another one. + /// + /// \see SplittedElementIterator::operator== + /// + /// \param RHS other object to compare to + /// + /// \return whether \p this object is not equal with \p RHS. + bool operator!=(const SplittedElementIterator &RHS) const noexcept { + return !((*this) == RHS); + } + +private: + std::shared_ptr Buffer; ///< Buffer providing values for \p this object + T Value; ///< The current value of the iterator +}; + +///\defgroup TupleBufferContainer Type converter turning a \c std::tuple of +///types into a \c std::tuple of container of those types. +/// +/// The new type is used for buffering elements of tuples separately. +/// +///@{ + +/// Template declaration. +/// +/// \tparam T type to convert +/// +/// \note The template is defined only when \p T is a \c std::tuple. +/// +/// Usage for a type \c Tuple as:\code +/// typename TupleBufferContainer::Type +/// \endcode +template struct TupleBufferContainer; + +/// Template definition for \c std::tuple. +template struct TupleBufferContainer> { + /// The converted type. + using Type = std::tuple...>; +}; + +///@} + +/// Convenience template type alias for easy use of \c TupleBufferContainer. +/// +/// Converts a \c std::tuple of types into a \c std::tuple of container of those +/// types. +/// +/// \tparam Tuple type to convert +template +using tuple_buffer_container_t = typename TupleBufferContainer::Type; + +/// Buffer for element values for iterator of \c std::tuple. +/// +/// The class can be instantiated with a range of iterator of \c std::tuple and +/// provides an interface for accessing elements of the iterated tuples one by +/// one. +/// +/// \note The class is utilized by \c SplittedElementIterator. +/// +/// \tparam TupleIterator the iterator type that provides values of \c +/// std::tuple +/// +/// \todo Consider thread safety of the class. +template +class TupleIteratorBuffer { +public: + /// Type alias for the value type of \p TupleIterator. + /// \note The type is expected to be \c std::tuple. + using iterator_value_type = typename TupleIterator::value_type; + + /// The number of elements of \c iterator_value_type. + static constexpr size_t iterator_value_size = + std::tuple_size_v; + + /// Template type alias to get element types of \c iterator_value_type by + /// index. + /// \tparam I the index of the element + template + using element_type = + typename std::tuple_element::type; + + /// Type alias for index sequence for accessing elements of \c + /// iterator_value_type. + using element_idx_seq_t = seq_t; + + /// Creates a new instance. + /// + /// \param Begin the beginning of the iterator range to buffer + /// \param End the end of the iterator range to buffer + TupleIteratorBuffer(TupleIterator &&Begin, const TupleIterator &End) noexcept + : Iterator(Begin), End(End), Buffer() {} + + /// Tells whether there is any more value in the handled range for index \p I. + /// + /// \tparam I the index of the element to check + /// + /// \return whether there is more value for index \p I + template bool hasMore(void) const noexcept { + const auto &ElementBuffer = std::get(Buffer); + // There is more value if some has already been buffered or the end of the + // handled range is not reached yet. + return !ElementBuffer.empty() || Iterator != End; + } + + /// Gives the next value from the handled range for index \p I. + /// + /// \note The function should not be called if there is no more value for + /// index \p I (i.e., \c hasMore() returns \c false). If it is called in + /// that situation, it returns the default value of \c element_type. + /// + /// \tparam I the index of the element to fetch next value for + /// + /// \return the next value for index \p I + template element_type next(void) noexcept { + auto &ElementBuffer = std::get(Buffer); + if (ElementBuffer.empty()) { + // This position needs a new value from Iterator if there is more. + if (Iterator != End) { + // Push next value for all elements. + push(*Iterator++, element_idx_seq_t()); + } else { + // No more values; called when hasMore() is false. + // Return default value. + return element_type(); + } + } + // Buffer is not empty here, return the next value. + ASSERT(!ElementBuffer.empty() && "Unexpected condition"); + // Get the next value and also pop it from the buffer. + const element_type Elem = ElementBuffer.front(); + ElementBuffer.pop(); + return Elem; + } + +private: + /// Buffers next tuple value elementwise into element containers. + /// + /// \tparam S0 indices for accessing elements of \c std::tuple + /// + /// \param Tuple the values to buffer + /// + /// \note The second parameter provides indices as template parameter \p S0 + /// and its actual value is ignored. + template + void push(const iterator_value_type &Tuple, Seq) noexcept { + (std::get(Buffer).push(std::get(Tuple)), ...); + } + + TupleIterator Iterator; ///< Current input iterator + const TupleIterator End; ///< End of iterator range to handle + tuple_buffer_container_t + Buffer; ///< Container for elementwise buffering +}; + +/// Template type alias for iterator of an element based on buffered iterator of +/// \c std::tuple. +/// +/// The alias utilizes \c SplittedElementIterator for iterating over the values +/// provided by \p TB. +/// +/// \tparam TB buffer providing values to iterate +/// \tparam I index of the values to iterator from \p TB +template +using element_iterator_t = SplittedElementIterator; + +/// Template type alias for iterator-range of an element based on buffered +/// iterator of \c std::tuple. +/// +/// \tparam TB buffer providing values to iterate +/// \tparam I index of the values to iterator from \p TB +template +using element_iterator_range_t = + std::pair, element_iterator_t>; + +///\defgroup ElementIteratorRanges Type converter turning a buffer of \c +/// std::tuple into a \c std::tuple of corresponding \c +/// element_iterator_range_t. +/// +///@{ + +/// Template declaration. +/// +/// \tparam TB buffer providing values +/// \tparam S type providing indices for accessing elements of \c std::tuple +/// +/// \note \p TB is expected to implement an interface matching that of \c +/// TupleIteratorBuffer. +/// +/// \note The template is defined only when \p S is a \c rosa::Seq. +/// +/// Usage for a proper buffer type \c TB:\code +/// typename ElementIteratorRanges::Type +/// \endcode +template struct ElementIteratorRanges; + +/// Template definition. +template +struct ElementIteratorRanges> { + /// The converted type. + using Type = std::tuple...>; +}; + +///@} + +/// Convenience template type alias for easy use of \c ElementIteratorRanges. +/// +/// Converts a buffer of \c std::tuple into a \c std::tuple of corresponding \c +/// element_iterator_range_t. +/// +/// \tparam TB buffer providing values +/// +/// \note \p TB is expected to implement an interface matching that of \c +/// TupleIteratorBuffer. +template +using element_iterator_ranges_t = + typename ElementIteratorRanges::Type; + +/// Template type alias for turning an iterator of \c std::tuple into +/// corresponding \c element_iterator_ranges_t. +/// +/// The alias utilizes \c TupleIteratorBuffer for buffering the values provided +/// by \p TupleIterator. The elementwise iterators are \c +/// SplittedElementIterator. +/// +/// \tparam TupleIterator iterator of \c std::tuple +template +using splitted_tuple_iterator_ranges_t = + element_iterator_ranges_t>; + +/// Creates elementwise iterator ranges for an iterator range of \c std::tuple. +/// +/// \note Implementation for \c rosa::iterator::splitTupleIterator. +/// +/// \tparam TupleIterator iterator of \c std::tuple +/// \tparam S0 indices for accessing elements of \c std::tuple +/// +/// \param Begin the beginning of the iterator range to handle +/// \param End the end of the iterator range to handle +/// +/// \note The last parameter provides indices as template parameter \p S0 and +/// its actual value is ignored. +/// +/// \return \c std::tuple of elementwise iterator ranges corresponding to \p +/// Begin and \p End +template +splitted_tuple_iterator_ranges_t +splitTupleIteratorImpl(TupleIterator &&Begin, const TupleIterator &End, + Seq) noexcept { + using TB = TupleIteratorBuffer; + // Create a buffer in shared pointer. The buffer will be destructed once + // all corresponding element iterators (created below) have reached the end of + // the handled range or have been destructed. + auto Buffer = std::make_shared(std::move(Begin), End); + return {std::make_pair(element_iterator_t(Buffer), + element_iterator_t())...}; +} + +} // End namespace + +/// Gives the beginning of a range created by \c splitTupleIterator(). +/// +/// \see rosa::iterator::splitTupleIterator() +/// +/// \note The signature of the template function uses implementation details. +/// +/// \tparam TB buffer providing values to iterate +/// \tparam I index of the values to iterator from \p TB +/// +/// \param Range range to get the beginning of +/// +/// \return beginning of \p Range +template +element_iterator_t &begin(element_iterator_range_t &Range) { + return Range.first; +} + +/// Gives the end of a range created by \c splitTupleIterator(). +/// +/// \see rosa::iterator::splitTupleIterator() +/// +/// \note The signature of the template function uses implementation details. +/// +/// \tparam TB buffer providing values to iterate +/// \tparam I index of the values to iterator from \p TB +/// +/// \param Range range to get the end of +/// +/// \return end of \p Range +template +element_iterator_t &end(element_iterator_range_t &Range) { + return Range.second; +} + +/// Creates elementwise iterator ranges for an iterator range of \c std::tuple. +/// +/// \note The implementation utilizes \c splitTupleIteratorImpl. +/// +/// Obtain element iterator ranges for an iterator range \c Begin and \c End of +/// iterator type \c TupleIterator of \c std::tuple as \code +/// auto [T1Range, T2Range, T3Range] = splitTupleIterator(std::move(Begin), End); +/// auto [T1Begin, T1End] = T1Range; +/// auto [T2Begin, T2End] = T2Range; +/// auto [T3Begin, T3End] = T3Range; +/// \endcode +/// One range can also be dissected by dedicated functions like \code +/// auto T1Begin = begin(T1Range); +/// auto T1End = end(T1Range); +/// \endcode +/// +/// The function returns a tuple of ranges (i.e., \c std::pair of iterators), +/// which can be assigned to variables using structured binding. The ranges can +/// be dissected into begin and end iterators by separate structured bindings. +/// +/// The function moves its first argument (i.e., \p Begin cannot be used +/// directly after splitting it). If the first argument is a variable, it needs +/// to be moved explicitly by \c std::move() as in the example. +/// +/// \tparam TupleIterator iterator of \c std::tuple +/// +/// \param Begin the beginning of the iterator range to handle +/// \param End the end of the iterator range to handle +/// +/// \return \c std::tuple of elementwise iterator ranges corresponding to \p +/// Begin and \p End +template +splitted_tuple_iterator_ranges_t +splitTupleIterator(TupleIterator &&Begin, const TupleIterator &End) noexcept { + return splitTupleIteratorImpl( + std::move(Begin), End, + seq_t>()); +} + +} // End namespace iterator +} // End namespace rosa + +#endif // ROSA_SUPPORT_ITERATOR_SPLIT_TUPLE_ITERATOR_HPP diff --git a/include/rosa/support/sequence.hpp b/include/rosa/support/sequence.hpp index 62b8408..bf61487 100755 --- a/include/rosa/support/sequence.hpp +++ b/include/rosa/support/sequence.hpp @@ -1,57 +1,56 @@ //===-- rosa/support/sequence.hpp -------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/sequence.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Template facilities to statically generate a sequence of numbers. /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_SEQUENCE_HPP #define ROSA_SUPPORT_SEQUENCE_HPP #include namespace rosa { /// \defgroup Seq Implementation of rosa::Seq /// /// Facility to statically generate sequences of numbers. /// ///@{ /// Template with an empty struct to store a sequence of numbers in compile time /// as template arguments. /// /// Generate a sequence of numbers from `0` up to (including) `(N - 1)` like /// \code /// typename GenSeq::Type /// \endcode template struct Seq {}; /// Sequence generator, the general case when counting down by extending the /// sequence. template struct GenSeq : GenSeq {}; /// Sequence generator, the terminal case when storing the generated sequence /// into \c Seq. template struct GenSeq<0, S...> { using Type = Seq; }; ///@} /// Convenience template alias for using \c rosa::GenSeq to obtain an instance -/// of \c rosa::Seq. -/// -/// \see \c rosa::Seq and \c rosa::GenSeq -template using seq_t = typename GenSeq::Type; - + /// of \c rosa::Seq. + /// + /// \see \c rosa::Seq and \c rosa::GenSeq + template using seq_t = typename GenSeq::Type; } // End namespace rosa #endif // ROSA_SUPPORT_SEQUENCE_HPP diff --git a/include/rosa/support/writer/namespace.h b/include/rosa/support/writer/namespace.h new file mode 100644 index 0000000..3349a93 --- /dev/null +++ b/include/rosa/support/writer/namespace.h @@ -0,0 +1,25 @@ +//===-- rosa/support/writer/namespace.h --------------------------*- C++ -*-===/ +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===/ +/// +/// \file rosa/support/writer/namespace.h +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Documentation for the namespace \c rosa::writer. +/// +//===----------------------------------------------------------------------===/ + +#ifndef ROSA_SUPPORT_WRITER_NAMESPACE_H +#define ROSA_SUPPORT_WRITER_NAMESPACE_H + +namespace rosa { +/// Provides facilities to work with writers. +namespace writer {} +} // End namespace rosa + +#endif // ROSA_SUPPORT_WRITER_NAMESPACE_H diff --git a/include/rosa/support/writer/split_tuple_writer.hpp b/include/rosa/support/writer/split_tuple_writer.hpp new file mode 100644 index 0000000..d98ace2 --- /dev/null +++ b/include/rosa/support/writer/split_tuple_writer.hpp @@ -0,0 +1,562 @@ +//===-- rosa/support/writer/split_tuple_writer.hpp ---------------*- C++ -*-===/ +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===/ +/// +/// \file rosa/support/writer/split_tuple_writer.hpp +/// +/// \authors David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Facilities to split writers of \c std::tuple into writers of +/// their elements. +/// +//===----------------------------------------------------------------------===/ + +#ifndef ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP +#define ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP + +#include "rosa/support/debug.hpp" +#include "rosa/support/sequence.hpp" + +#include +#include +#include + +namespace rosa { +namespace writer { + +/// What to do when splitted element writers got destructed so that some +/// incomplete tuples remain in the buffer. +enum IncompleteTuplePolicy { + Ignore, ///< ignore incomplete tuples, data discarded + Flush, ///< flush incomplete tuples, use default value for missing elements +#ifndef NDEBUG + DbgFail ///< fail with an assertion, available only in debug build +#endif // !defined NDEBUG +}; + +/// Anonymous namespace providing implementation for splitting tuple writers; +/// consider it private. +namespace { + +/// Writes values into a buffer based on an index. +/// +/// The values are first buffered and flushed out later once a corresponding +/// tuple gets complete (i.e., other elementwise writers put the corresponding +/// values into the buffer). Since the underlying buffer waits for tuples to +/// become complete, the elementwise writers are expected to provide the same +/// number of values for the buffer (i.e., completing all tuples that have an +/// element). That is, however, not enforced in any ways. +/// +/// Once all elementwise writers are destructed, the underlying buffer gets +/// destructed as well. Should the buffer contain some values, which are +/// elements of incomplete tuples, the destructor of the buffer handles the +/// situation according to the \c rosa::writer::IncompleteTuplePolicy set when +/// the elementwise writers were created by \c rosa::writer::splitTupleWriter(). +/// +/// \tparam TB buffer to write into +/// \tparam I index of the values to write to \p TB +/// +/// \note \p TB is expected to implemnet an interface matching that of \c +/// TupleWriterBuffer. +/// +/// \note the +template +class SplittedElementWriter { +public: + /// Type alias for the values the writer writes. + using T = typename TB::template element_type; + + /// \defgroup SplittedElemenWriterTypedefs Typedefs of + /// \c SplittedElementWriter + /// + /// Useful `typedef`s for writers. + /// + ///@{ + typedef T value_type; ///< Type of values written. + typedef T &reference; ///< Reference to the type written + ///@} + + /// Creates a new instance. + /// + /// \param Buffer buffer \p this object writes values into + /// + /// \note \p Buffer is captured as a \c std::shared_ptr because it is shared + /// among writers for its element positions. + SplittedElementWriter(std::shared_ptr Buffer) : Buffer(Buffer) {} + + /// Tells how many values are still in the buffer. + /// + /// Values are buffered as long as all elements of their corresponding tuples + /// are written into the buffer by other elementwise writers and in turn + /// complete tuples written by the buffer itself. This function tells how many + /// values are still in the buffer waiting for their containing tuples to be + /// complete and written out from the buffer. + /// + /// \note The function simply calles the corresponding function of \p TB. + /// + /// \return how many values are still in the buffer. + size_t buffered(void) const noexcept { + return Buffer->template buffered(); + } + + /// Tells if the last write operation of the buffer was successful. + /// + /// Values are buffered until their corresponding tuples gets complete. The + /// buffer writes complete tuples by the underlying writer. This function + /// tells if the buffer successfully has written the latest complete tuple by + /// the underlying writer. + /// + /// Once this function returns \c false, further \c write() calls has no + /// effect and the last \c buffered() values will not be written to the + /// underlying writer of \c std::tuple. + /// + /// \note The function simply calles the corresponding function of \p TB. + /// + /// \return if the last write operation was successful + bool good(void) const noexcept { return Buffer->good(); } + + /// Writes an entry to the buffer. + /// + /// The function has no effect if an earlier write operation of the buffer has + /// failed, that is \c good() returns \c false. + /// + /// \note The function simply calles the corresponding function of \p TB. + /// + /// \param V value to write + void write(const T &V) { Buffer->template write(V); } + +private: + std::shared_ptr Buffer; ///< Buffer \p this object writes into +}; + +/// Writes an element of a tuple. +/// +/// \see SplittedElementWriter +/// +/// \tparam TB type of the buffer \p W writes into +/// \tparam I index of the values \p W write to \p TB +/// +/// \param [in,out] W object to write with +/// \param V value to write +/// +/// \return \p W after writing \p V with it +template +SplittedElementWriter & +operator<<(SplittedElementWriter &W, + const typename SplittedElementWriter::value_type &V) { + W.write(V); + return W; +} + +///\defgroup TupleBufferContainer Type converter turning a \c std::tuple of +///types into a \c std::tuple of container of those types. +/// +/// The new type is used for buffering elements of tuples separately. +/// +///@{ + +/// Template declaration. +/// +/// \tparam T type to convert +/// +/// \note The template is defined only when \p T is a \c std::tuple. +/// +/// Usage for a type \c Tuple as:\code +/// typename TupleBufferContainer::Type +/// \endcode +template struct TupleBufferContainer; + +/// Template definition for \c std::tuple. +template struct TupleBufferContainer> { + /// The converted type. + using Type = std::tuple...>; +}; + +///@} + +/// Convenience template type alias for easy use of \c TupleBufferContainer. +/// +/// Converts a \c std::tuple of types into a \c std::tuple of container of those +/// types. +/// +/// \tparam Tuple type to convert +template +using tuple_buffer_container_t = typename TupleBufferContainer::Type; + +/// Buffer for element values for writer of \c std::tuple. +/// +/// The class can be instantiated with a writer of \c std::tuple and provides +/// an interface for writing elements into tuples one by one. +/// +/// \note The class is utilized by \c SplittedElementWriter. +/// +/// \tparam TupleWriter the writer type that handles values of \c std::tuple +/// +/// \note The elements of the written \c std::tuple need to be default +/// constructible because of \c rosa::writer::IncompleteTuplePolicy::Flush. +/// +/// \todo Consider thread safety of the class. +template +class TupleWriterBuffer { +public: + /// Type alias for the value type of \p TupleWriter. + /// \note The type is expected to be \c std::tuple. + using writer_value_type = typename TupleWriter::value_type; + + /// The number of elements of \c writer_value_type. + static constexpr size_t writer_value_size = + std::tuple_size_v; + + /// Template type alias to get element types of \c writer_value_type by + /// index. + /// \tparam I the index of the element + template + using element_type = + typename std::tuple_element::type; + + /// Type alias for index sequence for accessing elements of \c + /// writer_value_size. + using element_idx_seq_t = seq_t; + + /// Creates a new instance. + /// + /// \param Writer the writer \p this object uses to write tuples + /// \param Policy what to do with incomplete tuples when destructing \p + /// this object + TupleWriterBuffer(TupleWriter &&Writer, + const IncompleteTuplePolicy Policy) noexcept + : Writer(Writer), Policy(Policy), Buffer() {} + + /// Destructor. + /// + /// Should \p this object has some values in \c Buffer (i.e., incomplete + /// tuples), the destructor takes care of them according to \c Policy. + /// + /// \note The destructor may flush the buffered values if the last write + /// operation was successful, that is \c good() returns \c true. + ~TupleWriterBuffer(void) noexcept { + if (good()) { + switch (Policy) { + default: + ASSERT(false && "Unexpected Policy"); + case Ignore: + // Do not care about incomplete tuples in the buffer. + break; + case Flush: + // Whatever we have in the buffer, flush it out. + flush(); + break; +#ifndef NDEBUG + case DbgFail: + ASSERT(empty(element_idx_seq_t()) && "Non-empty buffer"); + break; +#endif // !defined NDEBUG + } + } + } + + /// Tells how many values are in the buffer for index \p I. + /// + /// \tparam I the index of the element to check + /// + /// \return the number of values in buffer for index \p I + template size_t buffered(void) const noexcept { + return std::get(Buffer).size(); + } + + /// Tells if the last write operation was successful. + /// + /// Values are buffered until their corresponding tuples gets complete. + /// Once a tuple becomes complete, it is written by \c Writer. This function + /// tells if writing the latest complete tuple was successful. + /// + /// Once this function returns \c false, further \c write() calls has no + /// effect and the last \c buffered() values will not be written to the + /// underlying writer of \c std::tuple. + /// + /// \return if the last write operation was successful + bool good(void) const noexcept { return Writer.good(); } + + /// Writes an entry to the buffer for index \p I. + /// + /// The function has no effect if an earlier write operation of the buffer has + /// failed, that is \c good() returns \c false. + /// + /// The value is buffered first and written by \p Writer once its + /// corresponding tuple becomes complete (i.e., all other elements of the + /// tuple are written into \p this object by corresponding elementwise + /// writers). + /// + /// \tparam I the index of the element to write + /// + /// \param V value to write + template void write(const element_type &V) { + ASSERT(!hasCompleteTuple(element_idx_seq_t())); + if (good()) { + std::get(Buffer).push(V); + if (hasCompleteTuple(element_idx_seq_t())) { + writeTuple(false); + } + } + ASSERT(!hasCompleteTuple(element_idx_seq_t())); + } + +private: + /// Tells whether \c Buffer is completely empty, all elements of it are empty. + /// + /// \tparam S0 indices for accessing elements of \c std::tuple + /// + /// \note The parameter provides indices as template parameter \p S0 and its + /// actual value is ignored. + /// + /// \return whether all elements of \c Buffer are empty + template bool empty(Seq) const noexcept { + return (true && ... && std::get(Buffer).empty()); + } + + /// Tells whether \c Buffer contains at least one complete tuple. + /// + /// \tparam S0 indices for accessing elements of \c std::tuple + /// + /// \note The parameter provides indices as template parameter \p S0 and its + /// actual value is ignored. + /// + /// \return whether there is at least one complete tuple in \c Buffer + template bool hasCompleteTuple(Seq) const noexcept { + return (true && ... && !std::get(Buffer).empty()); + } + + /// Makes sure \c Buffer has one complete tuple in front. + /// + /// If the tuple in front of \c Buffer is not complete, missing elements are + /// default constructed in \c Buffer to complete the tuple. + /// + /// \tparam S0 indices for accessing elements of \c std::tuple + /// + /// \note The parameter provides indices as template parameter \p S0 and its + /// actual value is ignored. + template void makeCompleteTuple(Seq) noexcept { + auto addDefaultIfEmpty = [](auto &Queue) { + if (Queue.empty()) { + Queue.emplace(); // default construct a value in the empty queue. + } + }; + (addDefaultIfEmpty(std::get(Buffer)), ...); + } + + /// Gives the first complete tuple from \c Buffer. + /// + /// \tparam S0 indices for accessing elements of \c std::tuple + /// + /// \note The parameter provides indices as template parameter \p S0 and its + /// actual value is ignored. + /// + /// \return the first complete tuple from \c Buffer + /// + /// \pre There is at least one complete tuple in \c Buffer:\code + /// hasCompleteTuple(element_idx_seq_t()) + /// \endcode + template writer_value_type front(Seq) const noexcept { + ASSERT(hasCompleteTuple(element_idx_seq_t())); + return {std::get(Buffer).front()...}; + } + + /// Removes the first complete tuple from \c Buffer. + /// + /// \tparam S0 indices for accessing elements of \c std::tuple + /// + /// \note The parameter provides indices as template parameter \p S0 and its + /// actual value is ignored. + /// + /// \pre There is at least one complete tuple in \c Buffer:\code + /// hasCompleteTuple(element_idx_seq_t()) + /// \endcode + template void pop(Seq) noexcept { + ASSERT(hasCompleteTuple(element_idx_seq_t())); + (std::get(Buffer).pop(), ...); + } + + /// Takes the first tuple from \c Buffer and writes it with \c Writer. + /// + /// \c Buffer need to contain a complete tuple to write it with \c Writer. The + /// function makes sure that the tuple in front of \c Buffer is complete if \p + /// MakeComplete is true. The tuple in fron of \c Buffer are expected to be + /// complete otherwise. + /// + /// \param MakeComplete whether to make the first tuple complete + /// + /// \pre There is at least one complete tuple in \c Buffer if not \p + /// MakeComplete: \code + /// MakeComplete || hasCompleteTuple(element_idx_seq_t()) + /// \endcode + void writeTuple(const bool MakeComplete) noexcept { + if (MakeComplete) { + makeCompleteTuple(element_idx_seq_t()); + } + ASSERT(hasCompleteTuple(element_idx_seq_t())); + Writer.write(front(element_idx_seq_t())); + pop(element_idx_seq_t()); + } + + /// Empties \c Buffer by writing out all tuples from it so that missing + /// elements of incomplete tuples are extended with default constructed + /// values. + void flush(void) noexcept { + while (!empty(element_idx_seq_t())) { + ASSERT(!hasCompleteTuple(element_idx_seq_t())); // Sanity check. + writeTuple(true); + } + } + + TupleWriter Writer; ///< The splitted writer + const IncompleteTuplePolicy Policy; ///< what to do with incomplete tuples + ///< when destructing \p this object + tuple_buffer_container_t + Buffer; ///< Container for elementwise buffering +}; + +/// Template type alias for writer of an element based on buffered writer of +/// \c std::tuple. +/// +/// The alias utilizes \c SplittedElementWriter for writing element values by +/// \p TB. +/// +/// \tparam TB buffer to write into +/// \tparam I index of the values to write to \p TB +template +using element_writer_t = SplittedElementWriter; + +///\defgroup ElementWriters Type converter turning a buffer of \c std::tuple +///into a \c std::tuple of corresponding \c element_writer_t. +/// +///@{ + +/// Template declaration. +/// +/// \tparam TB buffer to write into +/// \tparam S type providing indices for accessing elements of \c std::tuple +/// +/// \note \p TB is expected to implement an interface matching that of \c +/// TupleWriterBuffer. +/// +/// \note The template is defined only when \p S is a \c rosa::Seq. +/// +/// Usage for a proper buffer type \c TB:\code +/// typename ElementIteratorWriters::Type +/// \endcode +template struct ElementWriters; + +/// Template definition. +template +struct ElementWriters> { + /// The converted type. + using Type = std::tuple...>; +}; + +///@} + +/// Convenience template type alias for easy use of \c ElementIteratorWriters. +/// +/// Converts a buffer of \c std::tuple into a \c std::tuple of corresponding \c +/// element_writer_t. +/// +/// \tparam TB buffer to write into +/// +/// \note \p TB is expected to implement an interface matching that of \c +/// TupleWriterBuffer. +template +using element_writers_t = + typename ElementWriters::Type; + +/// Template type alias for turning a writer of \c std::tuple into +/// corresponding \c element_writers_t. +/// +/// The alias utilizes \c TupleWriterBuffer for buffering values before writing +/// them by \p TupleWriter. The elementwise writers are \c +/// SplittedElementWriter. +/// +/// \tparam TupleWriter writer of \c std::tuple +template +using splitted_tuple_writers_t = + element_writers_t>; + +/// Creates elementwise writers for a writer of \c std::tuple. +/// +/// \note Implementation for \c rosa::iterator::splitTupleWriter. +/// +/// \tparam TupleWriter writer of \c std::tuple +/// \tparam S0 indices for accessing elements of \c std::tuple +/// +/// \param Writer the writer to write tuples with +/// \param Policy how to handle incomplete tuples when destructing the +/// underlying buffer +/// +/// \note The last parameter provides indices as template parameter \p S0 and +/// its actual value is ignored. +/// +/// \return \c std::tuple of elementwise writers corresponding to \p Writer +template +splitted_tuple_writers_t +splitTupleWriterImpl(TupleWriter &&Writer, const IncompleteTuplePolicy Policy, + Seq) noexcept { + using TB = TupleWriterBuffer; + // Create a buffer in shared pointer. The buffer will be destructed once + // all corresponding element writers (created below) have been destructed. + auto Buffer = std::make_shared(std::move(Writer), Policy); + return {element_writer_t(Buffer)...}; +} + +} // End namespace + +/// Creates elementwise writers for a writer of \c std::tuple. +/// +/// \note The implementation utilizes \c splitTupleWriterImpl. +/// +/// Obtain elementwise writers for a writer \p Writer of type \c TupleWriter of +/// \c std::tuple as \code +/// auto [T1Writer, T2Writer, T3Writer] = splitTupleWriter(std::move(Writer), +/// Ignore); +/// \endcode +/// +/// The function returns a tuple of writers, which can be assigned to variables +/// using structured binding. +/// +/// The function moves its first argument (i.e., \p Writer cannot be used +/// directly after splitting it). If the first argument is a variable, it needs +/// to be moved explicitly by \c std::move() as in the example. +/// +/// While a tuple could be written with \p Writer directly as \code +/// Writer << std::make_tuple(T1(), T2(), T3()); +/// \endcode The same tuple is written with splitted writers as \code +/// T1Writer << T1(); +/// T2Writer << T2(); +/// T3Writer << T3(); +/// \endcode +/// +/// \tparam TupleWriter writer of \c std::tuple +/// +/// \note The elements of the written \c std::tuple need to be default +/// constructible because of \c rosa::writer::IncompleteTuplePolicy::Flush. +/// +/// \param Writer the writer to write tuples with +/// \param Policy what to do with incomplete tuples when destructing the +/// underlying buffer +/// +/// \return \c std::tuple of elementwise writers corresponding to \p Writer +template +splitted_tuple_writers_t +splitTupleWriter(TupleWriter &&Writer, + const IncompleteTuplePolicy Policy) noexcept { + return splitTupleWriterImpl( + std::move(Writer), Policy, + seq_t>()); +} + +} // End namespace writer +} // End namespace rosa + +#endif // ROSA_SUPPORT_WRITER_SPLIT_TUPLE_WRITER_HPP diff --git a/lib/deluxe/DeluxeAgent.cpp b/lib/deluxe/DeluxeAgent.cpp index 8f6669e..3deb5ea 100644 --- a/lib/deluxe/DeluxeAgent.cpp +++ b/lib/deluxe/DeluxeAgent.cpp @@ -1,313 +1,298 @@ //===-- deluxe/DeluxeAgent.cpp ----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file deluxe/DeluxeAgent.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Implementation of rosa/deluxe/DeluxeAgent.hpp. /// //===----------------------------------------------------------------------===// #include "rosa/deluxe/DeluxeAgent.hpp" #include "rosa/deluxe/DeluxeSystem.hpp" #include namespace rosa { namespace deluxe { bool DeluxeAgent::inv(void) const noexcept { // Check execution policy. // \note The \c rosa::System the \c rosa::Unit is created with is a // \c rosa::DeluxeSystem. const DeluxeSystem &DS = static_cast(Unit::system()); if (!ExecutionPolicy || !ExecutionPolicy->canHandle(Self, DS)) { return false; } // Check number of inputs and master-outputs. if (NumberOfInputs != NumberOfMasterOutputs) { return false; } // Check container sizes. if (!(InputTypes.size() == NumberOfInputs && InputNextPos.size() == NumberOfInputs && InputChanged.size() == NumberOfInputs && - InputStorageOffsets.size() == NumberOfInputs && - InputValues->size() == - InputStorageOffsets[NumberOfInputs - 1] + - lengthOfToken(InputTypes[NumberOfInputs - 1]) && + InputValues.size() == NumberOfInputs && MasterOutputTypes.size() == NumberOfInputs // == NumberOfMasterOutputs && Slaves.size() == NumberOfInputs)) { return false; } - // Stores storage offset for the next slave position checked in the following - // loop. - token_size_t InputStorageOffset = 0; - // Check *slave* types and validate *slave* registrations and reverse lookup // information. std::map RefIds; // Build up a reference of SlaveIds in this. for (size_t I = 0; I < NumberOfInputs; ++I) { - // First, validate the corresponding storage offset value. - if (InputStorageOffsets[I] != InputStorageOffset) { - return false; - } - // Fetch type-related information for the input position. const Token T = InputTypes[I]; const token_size_t TL = lengthOfToken(T); - const size_t StorageOffset = InputStorageOffsets[I]; - // Update storage offset for the next position. - InputStorageOffset += TL; + // Validate size of storage at position \c I. + if (InputValues[I]->size() != TL) { + return false; + } // Validate input types at position \c I. for (token_size_t TI = 0; TI < TL; ++TI) { - const size_t ElemOffset = StorageOffset + TI; - // The assert must hold if \p this object was successfuuly constructed. - ASSERT(static_cast(static_cast(ElemOffset)) == - ElemOffset); - if (InputValues->typeAt(static_cast(ElemOffset)) != + if (InputValues[I]->typeAt(static_cast(TI)) != typeAtPositionOfToken(T, TI)) { return false; } } // Check the index of next expected element for position \c I. if (InputNextPos[I] >= TL) { return false; } // Check the registered *slave* at position \c I. const auto &Slave = Slaves[I]; // If \c Slave is empty, nothing to check. if (!Slave) continue; // Prepare master-output related info for the *slave*. const Token MT = MasterOutputTypes[I]; const bool hasMT = !emptyToken(MT); // \c Slave is not empty here. // Check the `OutputType` and `MasterInputType` of the registered *slave*. const auto &A = unwrapAgent(*Slave); if (!((A.Kind == atoms::SensorKind && static_cast(A).OutputType == T && (!hasMT || static_cast(A).MasterInputType == MT)) || (A.Kind == atoms::AgentKind && static_cast(A).OutputType == T && (!hasMT || static_cast(A).MasterInputType == MT)))) { return false; } // Validate that the *slave* is not registered more than once. if (std::any_of( Slaves.begin() + I + 1, Slaves.end(), [&Slave](const Optional &O) { return O && *Slave == *O; })) { return false; } // Build the content of \c RefIds. RefIds.emplace(A.Id, I); } // Validate *slave* reverse lookup information against our reference. if (RefIds != SlaveIds) { return false; } // Check the size of the master-input storage. if (MasterInputValue->size() != lengthOfToken(MasterInputType)) { return false; } // Check the index of next expected element from the *master*. const token_size_t MITL = lengthOfToken(MasterInputType); if ((MITL != 0 && MasterInputNextPos >= MITL) || (MITL == 0 && MasterInputNextPos != 0)) { return false; } // All checks were successful, the invariant is held. return true; } DeluxeAgent::~DeluxeAgent(void) noexcept { ASSERT(inv()); LOG_TRACE_STREAM << "Destroying DeluxeAgent " << FullName << "..." << std::endl; // Make sure \p this object is not a registered *slave*. if (Master) { ASSERT(unwrapAgent(*Master).Kind == atoms::AgentKind); // Sanity check. DeluxeAgent &M = static_cast(unwrapAgent(*Master)); ASSERT(M.positionOfSlave(self()) != M.NumberOfInputs); // Sanity check. M.registerSlave(M.positionOfSlave(self()), {}); Master = {}; } // Also, make sure \p this object is no acting *master*. for (size_t Pos = 0; Pos < NumberOfInputs; ++Pos) { registerSlave(Pos, {}); } // Now there is no connection with other entities, safe to destroy. } id_t DeluxeAgent::masterId(void) const noexcept { ASSERT(inv() && Master); return unwrapAgent(*Master).Id; } const DeluxeExecutionPolicy &DeluxeAgent::executionPolicy(void) const noexcept { ASSERT(inv()); return *ExecutionPolicy; } bool DeluxeAgent::setExecutionPolicy( std::unique_ptr &&EP) noexcept { ASSERT(inv()); LOG_TRACE_STREAM << "DeluxeAgent " << FullName << " setting execution policy " << *EP << std::endl; bool Success = false; // \note The \c rosa::System the \c rosa::Unit is created with is a // \c rosa::DeluxeSystem. const DeluxeSystem &DS = static_cast(Unit::system()); if (EP && EP->canHandle(self(), DS)) { ExecutionPolicy.swap(EP); Success = true; } else { LOG_TRACE_STREAM << "Execution policy " << *EP << " cannot handle DeluxeAgent " << FullName << std::endl; } ASSERT(inv()); return Success; } Optional DeluxeAgent::master(void) const noexcept { ASSERT(inv()); return Master; } void DeluxeAgent::registerMaster(const Optional _Master) noexcept { ASSERT(inv() && (!_Master || unwrapAgent(*_Master).Kind == atoms::AgentKind)); Master = _Master; ASSERT(inv()); } Token DeluxeAgent::inputType(const size_t Pos) const noexcept { ASSERT(inv() && Pos < NumberOfInputs); return InputTypes[Pos]; } Token DeluxeAgent::masterOutputType(const size_t Pos) const noexcept { ASSERT(inv() && Pos < NumberOfMasterOutputs); return MasterOutputTypes[Pos]; } Optional DeluxeAgent::slave(const size_t Pos) const noexcept { ASSERT(inv() && Pos < NumberOfInputs); return Slaves[Pos]; } void DeluxeAgent::registerSlave(const size_t Pos, const Optional Slave) noexcept { ASSERT(inv() && Pos < NumberOfInputs && (!Slave || (unwrapAgent(*Slave).Kind == atoms::SensorKind && static_cast(unwrapAgent(*Slave)).OutputType == InputTypes[Pos] && (emptyToken(MasterOutputTypes[Pos]) || static_cast(unwrapAgent(*Slave)) .MasterInputType == MasterOutputTypes[Pos])) || (unwrapAgent(*Slave).Kind == atoms::AgentKind && static_cast(unwrapAgent(*Slave)).OutputType == InputTypes[Pos] && (emptyToken(MasterOutputTypes[Pos]) || static_cast(unwrapAgent(*Slave)) .MasterInputType == MasterOutputTypes[Pos])))); // If registering an actual *slave*, not just clearing the slot, make sure // the same *slave* is not registered to another slot. if (Slave) { auto It = SlaveIds.find(unwrapAgent(*Slave).Id); if (It != SlaveIds.end()) { Slaves[It->second] = {};//Optional(); SlaveIds.erase(It); } } // Obtain the place whose content is to be replaced with \p Slave auto &OldSlave = Slaves[Pos]; // If there is already a *slave* registered at \p Pos, clear reverse lookup // information for it, and make sure it no longer has \p this object as // *master*. if (OldSlave) { auto &A = unwrapAgent(*OldSlave); ASSERT(SlaveIds.find(A.Id) != SlaveIds.end()); // Sanity check. SlaveIds.erase(A.Id); if (A.Kind == atoms::AgentKind) { static_cast(A).registerMaster({}); } else { ASSERT(A.Kind == atoms::SensorKind); // Sanity check. static_cast(A).registerMaster({}); } } // Register \p Slave at \p Pos. OldSlave = Slave; // If registering an actual *slave*, not just clearing the slot, register // reverse lookup information for the new *slave*. if (Slave) { SlaveIds.emplace(unwrapAgent(*Slave).Id, Pos); } ASSERT(inv()); } size_t DeluxeAgent::positionOfSlave(const AgentHandle Slave) const noexcept { ASSERT(inv()); bool Found = false; size_t Pos = 0; while (!Found && Pos < NumberOfInputs) { auto &ExistingSlave = Slaves[Pos]; if (ExistingSlave && *ExistingSlave == Slave) { Found = true; } else { ++Pos; } } ASSERT(Found || Pos == NumberOfInputs); // Sanity check. return Pos; } void DeluxeAgent::handleTrigger(atoms::Trigger) noexcept { ASSERT(inv()); FP(); ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa diff --git a/lib/support/CMakeLists.txt b/lib/support/CMakeLists.txt index 6e191ef..318486d 100644 --- a/lib/support/CMakeLists.txt +++ b/lib/support/CMakeLists.txt @@ -1,38 +1,46 @@ set(LIB_INCLUDE_DIR ${ROSA_MAIN_INCLUDE_DIR}/rosa/support) add_library(ROSASupport ${LIB_INCLUDE_DIR}/debug.hpp debug.cpp ${LIB_INCLUDE_DIR}/terminal_colors.h terminal_colors.cpp ${LIB_INCLUDE_DIR}/log.h log.cpp ${LIB_INCLUDE_DIR}/math.hpp math.cpp ${LIB_INCLUDE_DIR}/type_helper.hpp type_helper.cpp ${LIB_INCLUDE_DIR}/types.hpp types.cpp ${LIB_INCLUDE_DIR}/atom.hpp atom.cpp ${LIB_INCLUDE_DIR}/type_pair.hpp type_pair.cpp ${LIB_INCLUDE_DIR}/type_list.hpp type_list.cpp ${LIB_INCLUDE_DIR}/squashed_int.hpp squashed_int.cpp ${LIB_INCLUDE_DIR}/type_numbers.hpp type_numbers.cpp ${LIB_INCLUDE_DIR}/type_token.hpp type_token.cpp ${LIB_INCLUDE_DIR}/tokenized_storages.hpp tokenized_storages.cpp ${LIB_INCLUDE_DIR}/sequence.hpp sequence.cpp ${LIB_INCLUDE_DIR}/csv/namespace.h csv/namespace.cpp ${LIB_INCLUDE_DIR}/csv/CSVReader.hpp csv/CSVReader.cpp ${LIB_INCLUDE_DIR}/csv/CSVWriter.hpp csv/CSVWriter.cpp + ${LIB_INCLUDE_DIR}/iterator/namespace.h + iterator/namespace.cpp + ${LIB_INCLUDE_DIR}/iterator/split_tuple_iterator.hpp + iterator/split_tuple_iterator.cpp + ${LIB_INCLUDE_DIR}/writer/namespace.h + writer/namespace.cpp + ${LIB_INCLUDE_DIR}/writer/split_tuple_writer.hpp + writer/split_tuple_writer.cpp ) diff --git a/lib/support/iterator/namespace.cpp b/lib/support/iterator/namespace.cpp new file mode 100644 index 0000000..57fe49f --- /dev/null +++ b/lib/support/iterator/namespace.cpp @@ -0,0 +1,20 @@ +//===-- support/iterator/namespace.cpp --------------------------*- C++ -*-===/ +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===/ +/// +/// \file support/iterator/namespace.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Placeholder for rosa/support/iterator/namespace.h. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/support/iterator/namespace.h. +/// +//===----------------------------------------------------------------------===/ + +#include "rosa/support/iterator/namespace.h" diff --git a/lib/support/iterator/split_tuple_iterator.cpp b/lib/support/iterator/split_tuple_iterator.cpp new file mode 100644 index 0000000..72f4f1f --- /dev/null +++ b/lib/support/iterator/split_tuple_iterator.cpp @@ -0,0 +1,20 @@ +//===-- support/iterator/split_tuple_iterator.cpp ---------------*- C++ -*-===/ +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===/ +/// +/// \file support/iterator/split_tuple_iterator.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Implementation for rosa/support/iterator/split_tuple_iterator.hpp. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/support/iterator/split_tuple_iterator.hpp. +/// +//===----------------------------------------------------------------------===/ + +#include "rosa/support/iterator/split_tuple_iterator.hpp" diff --git a/lib/support/writer/namespace.cpp b/lib/support/writer/namespace.cpp new file mode 100644 index 0000000..b659e2d --- /dev/null +++ b/lib/support/writer/namespace.cpp @@ -0,0 +1,20 @@ +//===-- support/writer/namespace.cpp -----------------------------*- C++ -*-===/ +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===/ +/// +/// \file support/writer/namespace.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Placeholder for rosa/support/iterator/namespace.h. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/support/writer/namespace.h. +/// +//===----------------------------------------------------------------------===/ + +#include "rosa/support/writer/namespace.h" diff --git a/lib/support/writer/split_tuple_writer.cpp b/lib/support/writer/split_tuple_writer.cpp new file mode 100644 index 0000000..651e012 --- /dev/null +++ b/lib/support/writer/split_tuple_writer.cpp @@ -0,0 +1,20 @@ +//===-- support/writer/split_tuple_writer.cpp --------------------*- C++ -*-===/ +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===/ +/// +/// \file support/writer/split_tuple_writer.cpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Implementation for rosa/support/writer/split_tuple_writer.hpp. +/// +/// \note Empty implementation, source file here to have a compile database +/// entry for rosa/support/writer/split_tuple_writer.hpp. +/// +//===----------------------------------------------------------------------===/ + +#include "rosa/support/writer/split_tuple_writer.hpp"