diff --git a/.gitignore b/.gitignore index 722d5e7..48bf31e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .vscode +build* +CMakeLists.txt.user \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ce72e40 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "modules/cxxopts"] + path = modules/cxxopts + url = https://github.com/jarro2783/cxxopts.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 03bbc68..f8d7bdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,188 +1,197 @@ cmake_minimum_required(VERSION 3.3.0) # Start project, set version here. project("RoSA") # NOTE: Adjust the variables version and release in docs/conf.py according to # version changes here. set(ROSA_VERSION_MAJOR 0) set(ROSA_VERSION_MINOR 1) set(ROSA_VERSION_PATCH 0) # Add path for custom modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ) # Package information if( NOT PACKAGE_VERSION ) set(PACKAGE_VERSION "${ROSA_VERSION_MAJOR}.${ROSA_VERSION_MINOR}.${ROSA_VERSION_PATCH}") endif() set(PACKAGE_NAME "Research on Self-Awareness Framework") set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") set(PACKAGE_BUGREPORT "david.juhasz@tuwien.ac.at") #TODO: cpack? # Sanity check we are to make out-of-tree build if( CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR ) message(FATAL_ERROR "In-source builds are not allowed.") endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) # Set various paths set(ROSA_MAIN_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(ROSA_MAIN_INCLUDE_DIR ${ROSA_MAIN_SRC_DIR}/include) set(ROSA_MAIN_BIN_DIR ${ROSA_MAIN_SRC_DIR}/bin) set(ROSA_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(ROSA_RUNTIME_OUTPUT_INTDIR ${ROSA_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin) set(ROSA_LIBRARY_OUTPUT_INTDIR ${ROSA_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib) set(ROSA_EXEC_BINARY_DIR ${ROSA_BINARY_DIR}/bin) set(ROSA_LIBRARY_DIR ${ROSA_BINARY_DIR}/lib) set(ROSA_INCLUDE_DIR ${ROSA_BINARY_DIR}/include) +set(ROSA_MAIN_MODULE_DIR ${ROSA_MAIN_SRC_DIR}/modules) # Add some generic helpers. include(AddCMakeTools) # Set build options option(ROSA_INCLUDE_TOOLS "Generate build targets for RoSA tools." ON) option(ROSA_INCLUDE_EXAMPLES "Generate build targets for RoSA examples." ON) option(ROSA_ENABLE_PEDANTIC "Compile with pedantic enabled." ON) # Assertions are always enabled for Debug builds, this option is respected only # for non-Debug builds. option(ROSA_ENABLE_ASSERTIONS "Enable assertions for non-Debug builds." OFF) option(ROSA_ENABLE_CLANG_TIDY "Run clang-tidy checks when building RoSA." OFF) option(ROSA_INCLUDE_CLANG_FORMAT "Generate build target for formatting RoSA sources with clang-format." OFF) option(ROSA_INCLUDE_DOCS "Generate build targets for RoSA documentation." ON) option(ROSA_BUILD_DOCS "Build RoSA documentation." OFF) option(ROSA_ENABLE_DOXYGEN "Use doxygen to generate RoSA API documentation." OFF) option(ROSA_ENABLE_SPHINX "Use Sphinx to generate RoSA documentation." OFF) set(ROSA_LOG_LEVEL "" CACHE STRING "Level of logging to be used.") set(ROSA_INCLUDE_APPS "" CACHE STRING "Generate build targets for the defined RoSA applications.") # All options referred to from HandleROSAOptions have to be specified # BEFORE this include, otherwise options will not be correctly set on # first cmake run include(config-ix) include(HandleROSAOptions) # Configure the ROSA configuration header file. if( NOT ROSA_LOG_LEVEL STREQUAL "") if( ${ROSA_LOG_LEVEL} EQUAL 5 ) set(ROSA_LOG_LEVEL_INT -1) else() set(ROSA_LOG_LEVEL_INT ${ROSA_LOG_LEVEL}) endif() else() set(ROSA_LOG_LEVEL_INT -1) endif() configure_file( ${ROSA_MAIN_INCLUDE_DIR}/rosa/config/rosa_config.h.cmake ${ROSA_INCLUDE_DIR}/rosa/config/rosa_config.h ) +# Handling modules +configure_file( + ${ROSA_MAIN_MODULE_DIR}/cxxopts/include/cxxopts.hpp + ${ROSA_INCLUDE_DIR}/cxxopts/cxxopts.hpp + COPYONLY +) + # They are not referenced. See set_output_directory(). set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ROSA_EXEC_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ROSA_LIBRARY_DIR}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${ROSA_LIBRARY_DIR}) # Set include directories set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(${ROSA_INCLUDE_DIR} ${ROSA_MAIN_INCLUDE_DIR}) # Set up YCM set(CMAKE_EXPORT_COMPILE_COMMANDS 1) message("Set compilation_database_folder in .ymc_extra_conf.py in the source directory if you want to use YMC.") # Add parts of the project include(AddROSATools) add_subdirectory(lib) add_subdirectory(apps) +add_subdirectory(modules) if( ROSA_INCLUDE_TOOLS ) add_subdirectory(tools) endif() if( ROSA_INCLUDE_EXAMPLES ) add_subdirectory(examples) endif() if( ROSA_INCLUDE_DOCS ) add_subdirectory(docs) endif() #TODO: install? # Print summary set(ROSA_ENABLE_ASSERTIONS_STR "OFF") if( ROSA_ENABLE_ASSERTIONS_INT EQUAL 1 ) set(ROSA_ENABLE_ASSERTIONS_STR "ON") endif() set(LOG_LEVEL_STR "disabled") if( NOT ROSA_LOG_LEVEL STREQUAL "" ) if( ${ROSA_LOG_LEVEL} EQUAL 0 ) set(LOG_LEVEL_STR "ERROR") elseif( ${ROSA_LOG_LEVEL} EQUAL 1 ) set(LOG_LEVEL_STR "WARNING") elseif( ${ROSA_LOG_LEVEL} EQUAL 2 ) set(LOG_LEVEL_STR "INFO") elseif( ${ROSA_LOG_LEVEL} EQUAL 3 ) set(LOG_LEVEL_STR "DEBUG") elseif( ${ROSA_LOG_LEVEL} EQUAL 4 ) set(LOG_LEVEL_STR "TRACE") elseif( ${ROSA_LOG_LEVEL} EQUAL 5 ) set(LOG_LEVEL_STR "disabled") else() set(LOG_LEVEL_STR "invalid") endif() endif() message(STATUS "\n====================| Build Summary |====================" "\n" "\nRoSA version: ${PACKAGE_VERSION}" "\n" "\nBuild type: ${CMAKE_BUILD_TYPE}" "\nAssertions: ${ROSA_ENABLE_ASSERTIONS_STR}" "\nClang-tidy: ${ROSA_ENABLE_CLANG_TIDY}" "\nClang-format: ${ROSA_INCLUDE_CLANG_FORMAT}" "\nLog level: ${LOG_LEVEL_STR}" "\n" "\nBuild apps: ${ROSA_INCLUDE_APPS}" "\nBuild tools: ${ROSA_INCLUDE_TOOLS}" "\nBuild examples: ${ROSA_INCLUDE_EXAMPLES}" "\nInclude docs: ${ROSA_INCLUDE_DOCS}" "\nBuild docs: ${ROSA_BUILD_DOCS}" "\n -enable doxygen: ${ROSA_ENABLE_DOXYGEN}" "\n -enable Sphinx: ${ROSA_ENABLE_SPHINX}" "\n" "\nCC: ${CMAKE_C_COMPILER}" "\nCFLAGS: ${CMAKE_C_FLAGS}" "\nCXX: ${CMAKE_CXX_COMPILER}" "\nCXXFLAGS: ${CMAKE_CXX_FLAGS}" "\nLIBRARIES: ${LD_FLAGS}" "\n" "\nSource directory: ${ROSA_MAIN_SRC_DIR}" "\nBuild directory: ${ROSA_BINARY_DIR}" "\nExecutable path: ${ROSA_EXEC_BINARY_DIR}" "\nLibrary path: ${ROSA_LIBRARY_DIR}" "\nGenerator: ${CMAKE_GENERATOR}" "\n" "\n===========================================================\n") diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index 813fd09..d00c89d 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -1,2 +1,7 @@ +# Allow exceptions by removing restricting flag. +if ( ROSA_COMPILER_IS_GCC_COMPATIBLE ) + remove("-fno-exceptions" CMAKE_CXX_FLAGS) +endif() + # Add the different subdirectories ADDALLSUBDIRS() diff --git a/apps/sa-ews1/sa-ews1.cpp b/apps/sa-ews1/sa-ews1.cpp index 23b02b8..b47e00d 100644 --- a/apps/sa-ews1/sa-ews1.cpp +++ b/apps/sa-ews1/sa-ews1.cpp @@ -1,316 +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 = DeluxeAgent::D; + 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", - DeluxeAgent::D( + 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", - DeluxeAgent::D( + 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::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/cmake/modules/HandleROSAOptions.cmake b/cmake/modules/HandleROSAOptions.cmake index 6c89812..e569892 100644 --- a/cmake/modules/HandleROSAOptions.cmake +++ b/cmake/modules/HandleROSAOptions.cmake @@ -1,214 +1,221 @@ # This CMake module is responsible for interpreting the user defined ROSA_ # options and executing the appropriate CMake commands to realize the users' # selections. include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) if ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" ) set(ROSA_COMPILER_IS_CLANG ON) # Enforce minimum version. if ( CMAKE_CXX_COMPILER_VERSION VERSION_LESS "3.9.0" ) message(FATAL_ERROR "Insufficient LLVM version") endif() else() set(ROSA_COMPILER_IS_CLANG OFF) endif() if( CMAKE_COMPILER_IS_GNUCXX ) set(ROSA_COMPILER_IS_GCC_COMPATIBLE ON) elseif( MSVC ) set(ROSA_COMPILER_IS_GCC_COMPATIBLE OFF) elseif( ROSA_COMPILER_IS_CLANG ) set(ROSA_COMPILER_IS_GCC_COMPATIBLE ON) else() message(FATAL_ERROR "Unexpected C++ compiler!") endif() function(append value) foreach(variable ${ARGN}) set(${variable} "${${variable}} ${value}" PARENT_SCOPE) endforeach(variable) endfunction() function(append_if condition value) if (${condition}) foreach(variable ${ARGN}) set(${variable} "${${variable}} ${value}" PARENT_SCOPE) endforeach(variable) endif() endfunction() +function(remove value) + foreach(variable ${ARGN}) + string(REPLACE "${value}" "" NEWVAR "${${variable}}") + set(${variable} "${NEWVAR}" PARENT_SCOPE) + endforeach(variable) +endfunction() + # NDEBUG on non-Debug builds append("-DNDEBUG" CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS_MINSIZEREL) # Assertions are always enabled on Debug builds, this is interesting for # non-Debug builds. if( NOT "${uppercase_CMAKE_BUILD_TYPE}" STREQUAL "DEBUG" AND ${ROSA_ENABLE_ASSERTIONS} ) # MSVC doesn't like _DEBUG on release builds. See PR 4379. if( NOT MSVC ) add_definitions( -D_DEBUG ) set(ENABLE_ASSERTIONS "1") else() set(ENABLE_ASSERTIONS "0") endif() # On non-Debug builds we NDEBUG, but in this case we need to undefine it: add_definitions(-UNDEBUG) # Also remove /D NDEBUG to avoid MSVC warnings about conflicting defines. foreach (flags_var_to_scrub CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS_MINSIZEREL) string (REGEX REPLACE "(^| )[/-]D *NDEBUG($| )" " " "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") endforeach() endif() set(ROSA_ENABLE_ASSERTIONS_INT -1) if( "${uppercase_CMAKE_BUILD_TYPE}" STREQUAL "DEBUG" OR ${ROSA_ENABLE_ASSERTIONS} ) set(ROSA_ENABLE_ASSERTIONS_INT 1) endif() if( ROSA_COMPILER_IS_GCC_COMPATIBLE ) append("-Wall -Wextra -Wdocumentation -Werror" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) append_if(ROSA_ENABLE_PEDANTIC "-pedantic -Wno-long-long" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) append("-fno-rtti -fno-exceptions" CMAKE_CXX_FLAGS) append("-Wno-c++1z-compat" CMAKE_CXX_FLAGS) check_cxx_compiler_flag("-std=c++17" CXX_SUPPORTS_CXX17) if ( CXX_SUPPORTS_CXX17 ) append("-std=c++17" CMAKE_CXX_FLAGS) else() message(FATAL_ERROR "RoSA requires C++17 support but the '-std=c++17' flag isn't supported.") endif() check_c_compiler_flag("-std=c11" C_SUPPORTS_C11) if ( C_SUPPORTS_C11 ) append("-std=c11" CMAKE_C_FLAGS) else() message(FATAL_ERROR "RoSA requires C11 support but the '-std=c11' flag isn't supported.") endif() elseif ( MSVC ) if (ROSA_COMPILER_IS_CLANG) # Remove/adjsut incorrect flags that might be generated automatically. foreach (flags_var_to_scrub CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS_MINSIZEREL) string (REGEX REPLACE "(^| )-Wall($| )" " " "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") string (REGEX REPLACE "(^| )-frtti($| )" " " "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") string (REGEX REPLACE "(^| )-fexceptions($| )" " " "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") string (REGEX REPLACE "(^| )-fno-inline($| )" " -Ob0 " "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") string (REGEX REPLACE "(^| )-gline-tables-only($| )" " " "${flags_var_to_scrub}" "${${flags_var_to_scrub}}") message("${flags_var_to_scrub}: ${${flags_var_to_scrub}}") endforeach() endif() # Flags matching GCC-compatible "-Wall -Wextra -Werror". # NOTE: Do not use -Wall for MSVC because it generates silly warning (e.g., # warnings about things in external libraries), use -W4 instead. append("/W4 /WX" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) if ( ROSA_COMPILER_IS_CLANG ) # GCC-compatible flags without corresponding MSVC options. append("-Wdocumentation" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) # The flag "-pedantic" is not in the MSVC flavour. append_if(ROSA_ENABLE_PEDANTIC "-Wno-long-long" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) endif() # Flags matching GCC-compatible "-fno-rtti". append("/GR-" CMAKE_CXX_FLAGS) # NOTE: We cannot disable unwind semantics (i.e., -fno-exceptions) for Windows # libraries... append("/EHsc" CMAKE_CXX_FLAGS) if ( ROSA_COMPILER_IS_CLANG ) append("-Wno-c++1z-compat" CMAKE_CXX_FLAGS) # MSVC puts there some garbage which needs to be ignored... append("-Qunused-arguments" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) endif() if ( ROSA_COMPILER_IS_CLANG ) check_cxx_compiler_flag("-Xclang -std=c++17" CXX_SUPPORTS_CXX17) if ( CXX_SUPPORTS_CXX17 ) append("-Xclang -std=c++17" CMAKE_CXX_FLAGS) else() message(FATAL_ERROR "RoSA requires C++17 support but the '-std=c++17' flag isn't supported.") endif() check_c_compiler_flag("-Xclang -std=c11" C_SUPPORTS_C11) if ( C_SUPPORTS_C11 ) append("-Xclang -std=c11" CMAKE_C_FLAGS) else() message(FATAL_ERROR "RoSA requires C11 support but the '-std=c11' flag isn't supported.") endif() else () check_cxx_compiler_flag("/std:c++17 /permissive-" CXX_SUPPORTS_CXX17) if ( CXX_SUPPORTS_CXX17 ) append("/std:c++17 /permissive-" CMAKE_CXX_FLAGS) else() message(FATAL_ERROR "RoSA requires C++17 support but the '/std:c++17 /permissive-' flags aren't supported.") endif() message(WARNING "RoSA is supposed to use C11 code in C files, which MSVC might not fully support.") # Make MSVC ignore warning C4514 about unreferenced inline functions. append("/nowarn:4514") # Make MSVC ignore warning LNK4221 in libraries. The warning is caused by # empty object files that correspond to the empty .cpp files, which we have # for generating compile database entries for all header files. append("/IGNORE:4221" CMAKE_STATIC_LINKER_FLAGS) endif() endif() if( ROSA_ENABLE_CLANG_TIDY ) # Set clang-tidy arguments, use the same for both C and CXX. set(CMAKE_C_CLANG_TIDY "${CLANGTIDY_EXECUTABLE};-header-filter=.*") set(CMAKE_CXX_CLANG_TIDY ${CMAKE_C_CLANG_TIDY}) # Apply suggested fixes if requested. append_if(ROSA_CLANG_TIDY_FIX ",-fix" CMAKE_C_CLANG_TIDY CMAKE_CXX_CLANG_TIDY) endif( ROSA_ENABLE_CLANG_TIDY ) if( ROSA_INCLUDE_APPS STREQUAL "") message(STATUS "Include all apps by default.") SUBDIRLIST(ROSA_INCLUDE_APPS ${ROSA_MAIN_SRC_DIR}/apps) else() message(STATUS "Include apps: ${ROSA_INCLUDE_APPS}.") endif() diff --git a/docs/Dev.rst b/docs/Dev.rst index 78ed678..f4bdda9 100755 --- a/docs/Dev.rst +++ b/docs/Dev.rst @@ -1,373 +1,387 @@ ============================= Developing the RoSA Framework ============================= .. contents:: :local: This document provides information that might be useful for contributing to RoSA. Please also consult :doc:`Build`. .. _Dev_Source_Directory: The Source Directory ==================== The source directory consists of the following subdirectories: `cmake` Contains files used for configuring the `CMake Project`_. `docs` Contains `Documentation`_-related files. `examples` Contains `Examples`_ on using the public API. `include/rosa` Contains the RoSA public API -- that is the interface of RoSA `Libraries`_. The directory `include` is to be used as include directory and RoSA header files are to be included in C++ sources as `"rosa/"`. `lib` Contains the implementation of the RoSA public API -- that is the implementation of RoSA `Libraries`_. `apps` Contains `Apps`_ based on RoSA features. `tools` Contains `Tools`_ based on RoSA features. +`modules` + Contains third-party `Modules`_ that are not part of RoSA but used by `Apps`_ + Software Sources ================ The section describes the `Logical Structure`_ of the software sources and what `Coding Standards`_ are supposed to be followed for the implementation. Logical Structure ----------------- Various features provided by RoSA are sorted into different `Libraries`_. `Examples`_ , `Apps`_, and `Tools`_ using those `Libraries`_ are separated from the implementation of the RoSA features into different directories. +`Examples`_ , `Apps`_, and `Tools`_ may also use third-party libraries, +called `Modules`_. Libraries ~~~~~~~~~ The framework consists of separate libraries providing different features. The public interfaces for RoSA libraries are defined in `include/rosa`, while corresponding implementation is in `lib`. Each library has its own subdirectory in the mentioned directories. RoSA provides the following libraries: `config` Provides information on the configuration used to build the framework, e.g., version number, log level, assertions, and debugging. `support` Provides general features -- template metaprograms dealing with types, for instance -- for implementing other libraries. `core` Provides the basic RoSA features, like systems managing agents passing messages. `agent` Provides features to be used for implementing agents. `deluxe` Provides a somewhat more modular interface for defining systems with RoSA. .. _Library_Dependencies: Dependencies '''''''''''' The following table summarizes dependencies among libraries. A marking in a row denotes that the library in the beginning of the row depends on the library in the head of the given column. +---------+--------+---------+------+--------+-------+ | | config | support | core | deluxe | agent | +=========+========+=========+======+========+=======+ | config | | | | | | +---------+--------+---------+------+--------+-------+ | support | | | | | | +---------+--------+---------+------+--------+-------+ | core | | × | | | | +---------+--------+---------+------+--------+-------+ | deluxe | | | × | | | +---------+--------+---------+------+--------+-------+ | agent | | | | | | +---------+--------+---------+------+--------+-------+ Examples ~~~~~~~~ Some simple samples are provided in `examples` to demonstrate how to to use different parts of the RoSA API. Apps ~~~~ Apps, actual applications based on the RoSA libraries, are implemented in `apps`. Tools ~~~~~ Tools, programs based on the RoSA libraries and providing standalone functionalities, are implemented in `tools`. +Modules +~~~~~~~ + +This directory contains third-party modules that can be used inside RoSA. These +include: + + * cxxopts: https://github.com/jarro2783/cxxopts + Example usage can be seen in modules/cxxopts/src/example.cpp + .. _Coding_Standards: Coding Standards ---------------- RoSA is implemented in standard *C++17* code. All the software sources are to be written in accordance to the `LLVM Coding Standards`_. .. _nortti-noexceptions: Feature Restrictions ~~~~~~~~~~~~~~~~~~~~ Pay attention `not to use RTTI and Exceptions`_. Those features are disabled in the CMake project. Documentation Comments ~~~~~~~~~~~~~~~~~~~~~~ It is important to remember to document source code using `doxygen comments`_ as `API Documentation`_ is generated directly from sources. Note that the syntax of documentation comments is checked during compilation -- at least when using a GCC-compatible compiler. Further, check :ref:`Doxygen warnings ` as issues not being detected by the compiler may be found when actually generating the documentation. Whenever you work on a source file, make sure your name is in the author-list defined in the header comment of the file. Each author should be defined with a separate `\\author` command so that recent authors come first. Authors not participating in further development of a file anymore may be marked with the period of their contribution. If declarations belonging to a namespace are spread to more than one source files, document the namespace in a separate `namespace.h` in the directory belonging to the library. Otherwise, document the namespace in the only file in which entities of the namespace are declared. Header Files ~~~~~~~~~~~~ Follow the recommendations on public and private header files and the usage of `#include` from the `LLVM Coding Standards`_. Use `.h` and `.hpp` extensions to indicate the content of the header file: * header files containing any *definition* -- template or inline definition -- or including another header file with `.hpp` extension have `.hpp` extension; * header files containing only *declarations* and including only header files with `.h` extension have `.h` extension. It may happen that a header file does not need any corresponding implementation in a `.cpp` file. Nevertheless, do create a corresponding `.cpp` file which only includes the header file in this case. That makes sure that the header file is compiled and hence checked for errors, and also a corresponding entry in the compilation database is generated. Checking and Enforcing the Coding Standards ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The tools `clang-tidy `_ and `clang-format `_ can be used to check and enforce the coding standards. The two tools are integrated into the CMake project, refer to CMake variables :ref:`ROSA_ENABLE_CLANG_TIDY ` and :ref:`ROSA_INCLUDE_CLANG_FORMAT `. Note that there may be situations when `clang-tidy` checks result in false positives -- for example, for some cases of the order of `#include` directives. One can order `clang-tidy` to suppress warnings for a line of code by marking that line with:: // NOLINT It may be preferred to diverge from the standard formatting -- for example for the sake of readability of static definition of arrays following some structure. One can disable `clang-format` for some lines of code by designating a range with two special comments as:: // clang-format off ... clang-format is disabled here ... // clang-format on Documentation ============= The RoSA Framework is delivered with two kinds of documentation: `General Documentation`_ and `API Documentation`_, generation of both of which is integrated into the CMake project. References between the two documentations are relative addresses corresponding to the directory structure of the :ref:`generated documentation `. General Documentation --------------------- General documentation is written as `reStructuredText `_ compiled with `Sphinx `_. For build integration, refer to the CMake variable :ref:`ROSA_ENABLE_SPHINX `. Documentation files are located in `docs` with extension `.rst`. The main page of the documentation is `docs/index.rst`. Configuration for building the documentation is `docs/conf.py`. The directory `docs/CommandGuide` contains documentation for each separate tool. Those pages are included in the HTML documentation via `docs/CommandGuide/index.rst`. Moreover, man pages can be generated from those tool documentation pages. API Documentation ----------------- API documentation is directly generated from sources with `Doxygen `_. For build integration, refer to the CMake variable :ref:`ROSA_ENABLE_DOXYGEN `. The main page used for the API documentation is `docs/doxygen-mainpage.dox`. Configuration for generating the API documentation is `docs/doxygen.cfg.in`. .. _CMake Project: Managing the CMake Project ========================== This section briefly summarizes when and how to modify CMake files during the development process. No general discussion on CMake features is provided here. When modifying `Documentation`_, no need to update the CMake files. Software -------- One needs to modify the CMake files only if source files are to be added or removed from the project. Here follows some typical scenarios. Source Files ~~~~~~~~~~~~ Each library and executable target has its own directory and its own definition as a file called `CMakeLists.txt` in that directory. When adding or removing a source file -- both headers and `.cpp` files -- to a library or executable, locate the corresponding `CMakeLists.txt` file. The file is typically in the same directory where the file to be added or removed is located. Except for header files of the public API, for which the corresponding CMake target is defined in a `lib` subdirectory corresponding to the library the header files belongs to. Update the source list in the argument of the `add_library` or `add_executable` command in the `CMakeLists.txt`, for libraries and executables, respectively. A library and executable may use features provided by another library. Such a dependency is to be defined in the `CMakeLists.txt` file of the dependent target by using the `ROSA_add_library_dependencies` command. CMake Libraries ~~~~~~~~~~~~~~~ When adding or removing a library, add or remove the corresponding directories from `include` and `lib`. If you have already had generated a build project with CMake, `touch` [#]_ `lib/CMakeLists.txt` to make CMake rescan subdirectories on next build. When defining a new library, the new subdirectory under `lib` needs to contain a `CMakeLists.txt`, which needs to contain at least an `add_library` command defining the name of the library and the source files belonging to it. CMake Executables ~~~~~~~~~~~~~~~~~ When adding or removing an executable, add or remove the corresponding directory from `apps`, `examples`, or `tools`. If you have already had generated a build project with CMake, `touch` `CMakeLists.txt` in the containing directory (like in the case of libraries) to make CMake rescan subdirectories on next build. When defining a new executable, the new subdirectory needs to contain a `CMakeLists.txt`, which needs to contain at least an `add_executable` command defining the name of the executable and the source files belonging to it. .. _Dev Managing Sources: Managing Sources ================ Consider the followings before committing changes to the repository: * your code complies with the `Coding Standards`_ as much as possible; * your code is well documented; * your code is not bloated with unusued code and/or comments; * your changes do not break building and executing the framework: * test all of the supported platforms if possible, * look into the generated documentation if you have edited `General Documentation`_; * you do not pollute the repository with unused and generated files. When committing changes to the repository, provide a concise log message with your commit. Miscellaneous Concerns ====================== Using YCM --------- If you happen to use `YCM `_, just make a copy of the provided `ycm_extra_conf.py.template` file as `.ycm_extra_conf.py` in the RoSA source directory, and set the following two variables in it: `compilation_database_folder` the absolute path of your build directory `extra_system_include_dirs` any system include directory which might not be searched by `libclang` [#]_. You probably want compile with Clang if you use YCM, so run CMake with environment variables `CC=clang` and `CXX=clang++` set. Also note that header files in the `include` directory are compiled for YCM with the compiler flags of a corresponding source file in the `lib` directory, if any. Header files in other locations are supposed to have a corresponding source file in the same directory. Notes ~~~~~ * If the project's include directory (`include/rosa`) would ever be changed, then the YCM configuration file needs to be adjusted accordingly. .. rubric:: Footnotes .. [#] Set the last modified time of the file to the current time. .. [#] See: https://github.com/Valloric/YouCompleteMe/issues/303; use the following command to figure out the used system directories: echo | clang -std=c++11 -v -E -x c++ - .. _`LLVM Coding Standards`: http://llvm.org/docs/CodingStandards.html .. _`not to use RTTI and Exceptions`: http://llvm.org/docs/CodingStandards.html#do-not-use-rtti-or-exceptions .. _`doxygen comments`: http://llvm.org/docs/CodingStandards.html#doxygen-use-in-documentation-comments diff --git a/examples/agent-functionalities/agent-functionalities.cpp b/examples/agent-functionalities/agent-functionalities.cpp index 7e4cf9b..0766f28 100644 --- a/examples/agent-functionalities/agent-functionalities.cpp +++ b/examples/agent-functionalities/agent-functionalities.cpp @@ -1,109 +1,188 @@ //===-- examples/agent-functionalities/agent-functionalities.cpp *-- C++-*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file examples/agent-functionalities/agent-functionalities.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief A simple example on defining \c rosa::Agent instances using /// \c rosa::agent::Functionality object as components. /// //===----------------------------------------------------------------------===// +// Make sure M_PI is available, needed for _WIN32 +#define _USE_MATH_DEFINES +#include + #include "rosa/agent/Abstraction.hpp" #include "rosa/agent/Confidence.hpp" +#include "rosa/agent/FunctionAbstractions.hpp" +#include "rosa/agent/RangeConfidence.hpp" #include "rosa/config/version.h" #include "rosa/core/Agent.hpp" #include "rosa/core/MessagingSystem.hpp" #include "rosa/support/log.h" #include "rosa/support/terminal_colors.h" #include using namespace rosa; using namespace rosa::agent; using namespace rosa::terminal; +// We use pi as float rather than double, which M_PI is. +constexpr float Pi = (float) M_PI; + /// A dummy wrapper for testing \c rosa::MessagingSystem. /// /// \note Since we test \c rosa::MessagingSystem directly here, we need to get /// access to its protected members. That we do by imitating to be a decent /// subclass of \c rosa::MessagingSystem, while calling protected member /// functions on an object of a type from which we actually don't inherit. struct SystemTester : protected MessagingSystem { template static AgentHandle createMyAgent(MessagingSystem *S, const std::string &Name, Funs &&... Fs) { return ((SystemTester *)S)->createAgent(Name, std::move(Fs)...); } static void destroyMyAgent(MessagingSystem *S, const AgentHandle &H) { ((SystemTester *)S)->destroyUnit(unwrapAgent(H)); } }; /// A special \c rosa::Agent with its own state. class MyAgent : public Agent { public: using Tick = AtomConstant; private: enum class Categories { Bad, Normal, Good }; static const std::map CategoryNames; - History H; + StaticLengthHistory H; Confidence C; RangeAbstraction A; + PartialFunction L; + RangeConfidence RCL; + RangeConfidence RCS; public: void handler(Tick, uint8_t V) noexcept { // Record \p V to the \c rosa::agent::History, then print state info. H << V; ASSERT(H.entry() == V); // Sanity check. LOG_INFO_STREAM << "\nNext value: " << PRINTABLE(V) << ", confidence: " << C(H) - << ", category: " << CategoryNames.at(A(H.entry())) << '\n'; + << ", category: " << CategoryNames.at(A(H.entry())) + << ", partial: " << int(L(H.entry())) + << ", range-confidence-linear: "; + + std::map ResLin = RCL(H.entry()); + for (auto Con : ResLin) { + LOG_INFO_STREAM << " " << CategoryNames.at(Con.first) << " " << Con.second + << ","; + } + LOG_INFO_STREAM << " range-confidence-sine: "; + std::map ResSine = RCS(H.entry()); + for (auto Con : ResSine) { + LOG_INFO_STREAM << " " << CategoryNames.at(Con.first) << " " << Con.second + << ","; + } + LOG_INFO_STREAM << '\n'; } MyAgent(const AtomValue Kind, const rosa::id_t Id, const std::string &Name, MessagingSystem &S) : Agent(Kind, Id, Name, S, THISMEMBER(handler)), H(), C(5, 20, 1), A({{{(uint8_t)10, (uint8_t)14}, Categories::Normal}, {{(uint8_t)15, (uint8_t)17}, Categories::Good}, {{(uint8_t)18, (uint8_t)19}, Categories::Normal}}, - Categories::Bad) {} + Categories::Bad), + L({{{0, 2}, std::make_shared>(0, 1)}, + {{2, 4}, std::make_shared>(2, 0)}, + {{4, 6}, std::make_shared>(6, -1)}}, + 0), + RCL({ + {Categories::Bad, PartialFunction({ + {{0.f, 3.f}, std::make_shared> + (0.f, 1.f/3)}, + {{3.f, 6.f}, std::make_shared> + (1.f, 0.f)}, + {{6.f, 9.f}, std::make_shared> + (3.f, -1.f/3)}, + },0)}, + {Categories::Normal, PartialFunction({ + {{6.f, 9.f}, std::make_shared> + (-2.f, 1.f/3)}, + {{9.f, 12.f}, std::make_shared> + (1.f, 0.f)}, + {{12.f, 15.f}, std::make_shared> + (5.f, -1.f/3)}, + },0)}, + {Categories::Good, PartialFunction({ + {{12.f, 15.f}, std::make_shared> + (-4.f, 1.f/3)}, + {{15.f, 18.f}, std::make_shared> + (1.f, 0.f)}, + {{18.f, 21.f}, std::make_shared> + (7.f, -1.f/3)}, + },0)} + }), + RCS({ + {Categories::Bad, PartialFunction({ + {{0.f, 3.f}, std::make_shared> + (Pi/3, 0.5f, -Pi/2, 0.5f)}, + {{3.f, 6.f}, std::make_shared>(1.f, 0.f)}, + {{6.f, 9.f}, std::make_shared> + (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)}, + },0)}, + {Categories::Normal, PartialFunction({ + {{6.f, 9.f}, std::make_shared> + (Pi/3, 0.5f, -Pi/2, 0.5f)}, + {{9.f, 12.f}, std::make_shared>(1.f, 0.f)}, + {{12.f, 15.f}, std::make_shared> + (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)}, + },0)}, + {Categories::Good, PartialFunction({ + {{12.f, 15.f}, std::make_shared> + (Pi/3, 0.5f, -Pi/2, 0.5f)}, + {{15.f, 18.f}, std::make_shared>(1.f, 0.f)}, + {{18.f, 21.f}, std::make_shared> + (Pi/3, 0.5f, -Pi/2 + 3, 0.5f)}, + },0)} + }, true){} }; const std::map MyAgent::CategoryNames{ {Categories::Bad, "Bad"}, {Categories::Normal, "Normal"}, {Categories::Good, "Good"}}; int main(void) { LOG_INFO_STREAM << library_string() << " -- " << Color::Red - << "agent-functionalities example" << Color::Default - << '\n'; + << "agent-functionalities example" << Color::Default << '\n'; std::unique_ptr S = MessagingSystem::createSystem("Sys"); MessagingSystem *SP = S.get(); AgentHandle A = SystemTester::createMyAgent(SP, "MyAgent"); - std::vector Vs{4, 5, 6, 7, 9, 10, 11, 13, - 15, 14, 15, 16, 19, 20, 21}; + std::vector Vs{0, 1, 2, 3, 4, 5, 6, 7, 9, 10, + 11, 13, 15, 14, 15, 16, 19, 20, 21}; for (auto I = Vs.begin(); I != Vs.end(); ++I) { A.send(MyAgent::Tick::Value, *I); } SystemTester::destroyMyAgent(SP, A); return 0; } diff --git a/examples/deluxe-interface/deluxe-interface.cpp b/examples/deluxe-interface/deluxe-interface.cpp old mode 100755 new mode 100644 index d50396e..78638ff --- a/examples/deluxe-interface/deluxe-interface.cpp +++ b/examples/deluxe-interface/deluxe-interface.cpp @@ -1,177 +1,338 @@ //===-- examples/deluxe-interface/deluxe-interface.cpp ----------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file examples/deluxe-interface/deluxe-interface.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief A simple example on the \c rosa::deluxe::DeluxeContext and related /// classes. //===----------------------------------------------------------------------===// #include "rosa/config/version.h" #include "rosa/deluxe/DeluxeContext.hpp" #include #include #include using namespace rosa; using namespace rosa::deluxe; using namespace rosa::terminal; /// How many cycles of simulation to perform. const size_t NumberOfSimulationCycles = 16; -/// Helper function creating a deluxe agent for logging and forwarding values. -/// -/// Received values are dumped to \c LOG_INFO_STREAM and then returned as -/// result. -/// -/// \tparam T type of values to handle -/// -/// \param C the deluxe context to create the agent in -/// \param Name name of the new agent -/// -/// \return handle for the new agent -template -AgentHandle createLowLevelAgent(std::unique_ptr &C, - const std::string &Name) { - using handler = DeluxeAgent::D; - 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.first}; - })); -} - int main(void) { LOG_INFO_STREAM << '\n' << library_string() << " -- " << Color::Red << "deluxe-interface example" << Color::Default << '\n'; std::unique_ptr C = DeluxeContext::create("Deluxe"); // // 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 + // with the default value of the last argument. That, however, requires the // data type to be explicitly defined. This is good for simulation only. + + // The first and second sensors do not receive master-input. + AgentHandle BoolSensor = C->createSensor("BoolSensor"); AgentHandle IntSensor = C->createSensor("IntSensor"); - AgentHandle FloatSensor = C->createSensor("FloatSensor"); + + // This sensor receives master-input and dumps it to \c LOG_INFO_STREAM. + const std::string FloatSensorName = "FloatSensor"; + AgentHandle FloatSensor = C->createSensor( + FloatSensorName, [&FloatSensorName](std::pair I) { + LOG_INFO_STREAM << "\n******\n" + << FloatSensorName + << " master-input " << (I.second ? "" : "") + << " value: " << I.first << "\n******\n"; + }); + + // This sensor do not receive master-input but produces tuples. + using TupleType = DeluxeTuple; + AgentHandle TupleSensor = C->createSensor("TupleSensor"); + + // + // Check and set execution policy for sensors. + // + LOG_INFO("Execution policies for sensors."); + + LOG_INFO(std::to_string(*C->getExecutionPolicy(IntSensor))); + C->setExecutionPolicy(IntSensor, DeluxeExecutionPolicy::decimation(2)); + C->setExecutionPolicy(FloatSensor, DeluxeExecutionPolicy::decimation(2)); + LOG_INFO(std::to_string(*C->getExecutionPolicy(IntSensor))); // // Create low-level deluxe agents with \c createLowLevelAgent. // LOG_INFO("Creating low-level agents."); - AgentHandle IntAgent = createLowLevelAgent(C, "IntAgent"); - AgentHandle FloatAgent = createLowLevelAgent(C, "FloatAgent"); + // All agents below dump their received values to \c LOG_INFO_STREAM on each + // triggering. + + // This agent does not receive master-input and does not produce + // master-output. It results in the value it received. + const std::string BoolAgentName = "BoolAgent"; + using BoolResult = Optional; + using BoolHandler = std::function)>; + AgentHandle BoolAgent = C->createAgent( + BoolAgentName, + BoolHandler([&BoolAgentName](std::pair I) -> BoolResult { + LOG_INFO_STREAM << "\n******\n" + << BoolAgentName << " " + << (I.second ? "" : "") + << " value: " << I.first << "\n******\n"; + return {I.first}; + })); + + // This agent receives master-input but does not produce master-output. The + // agent maintains a state in \c IntAgentOffset. The master-input handler + // updates \c IntAgentOffset according to each received (new) value from its + // master. The slave-input handler results in the sum of the received value + // and the actual value of \c IntAgentOffset. + const std::string IntAgentName = "IntAgent"; + using IntMasterHandler = std::function)>; + using IntResult = Optional; + using IntHandler = std::function)>; + uint32_t IntAgentOffset = 0; + AgentHandle IntAgent = C->createAgent( + IntAgentName, + // Master-input handler. + IntMasterHandler([&IntAgentName, + &IntAgentOffset](std::pair I) { + LOG_INFO_STREAM << "\n******\n" + << IntAgentName + << " master-input " << (I.second ? "" : "") + << " value: " << I.first << "\n******\n"; + if (I.second) { + IntAgentOffset = I.first; + } + }), + // Slave-input handler. + IntHandler([&IntAgentName, + &IntAgentOffset](std::pair I) -> IntResult { + LOG_INFO_STREAM << "\n******\n" + << IntAgentName << " " << (I.second ? "" : "") + << " value: " << I.first << "\n******\n"; + return {I.first + IntAgentOffset}; + })); + + // This agent receives master-input and produces master-output. The + // master-input handler propagaates each received (new) value to its slave as + // master-output. The slave-input handler results in the value it received and + // produces no actual master-output. + const std::string FloatAgentName = "FloatAgent"; + using FloatMasterResult = std::tuple>; + using FloatMasterHandler = + std::function)>; + using FloatResult = std::tuple, Optional>; + using FloatHandler = std::function)>; + AgentHandle FloatAgent = C->createAgent( + FloatAgentName, + // Master-input handler. + FloatMasterHandler([&FloatAgentName]( + std::pair I) -> FloatMasterResult { + LOG_INFO_STREAM << "\n******\n" + << FloatAgentName + << " master-input " << (I.second ? "" : "") + << " value: " << I.first << "\n******\n"; + const auto Output = + I.second ? Optional(I.first) : Optional(); + return {Output}; + }), + // Slave-input handler. + FloatHandler([&FloatAgentName](std::pair I) -> FloatResult { + LOG_INFO_STREAM << "\n******\n" + << FloatAgentName << " " + << (I.second ? "" : "") + << " value: " << I.first << "\n******\n"; + return {{I.first}, {}}; + })); + + // This agent does not receive master-input and does not produce + // master-output. It results in the sum of the values it receives in a tuple. + const std::string TupleAgentName = "TupleAgent"; + using TupleSumResult = Optional>; + using TupleHandler = + std::function)>; + AgentHandle TupleAgent = C->createAgent( + TupleAgentName, + TupleHandler( + [&TupleAgentName](std::pair I) -> TupleSumResult { + LOG_INFO_STREAM << "\n******\n" + << TupleAgentName << " " + << (I.second ? "" : "") + << " value: " << I.first << "\n******\n"; + return {std::get<0>(I.first) + std::get<1>(I.first)}; + })); + + // + // Set execution policies for low-level agents. + // + LOG_INFO("Setting Execution policies for low-level agents."); + + C->setExecutionPolicy(IntAgent, DeluxeExecutionPolicy::awaitAll({0})); + C->setExecutionPolicy(FloatAgent, DeluxeExecutionPolicy::awaitAll({0})); // // Connect sensors to low-level agents. // LOG_INFO("Connect sensors to their corresponding low-level agents."); + C->connectSensor(BoolAgent, 0, BoolSensor, "Bool Sensor Channel"); C->connectSensor(IntAgent, 0, IntSensor, "Int Sensor Channel"); C->connectSensor(FloatAgent, 0, FloatSensor, "Float Sensor Channel"); + C->connectSensor(TupleAgent, 0, TupleSensor, "Tuple 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. + using SingleDoubleOutputType = Optional>; + using SingleUInt32OutputType = Optional>; + using NoOutputType = Optional; + + // This agent does not receive master-input but produces master-output for its + // slaves at positions `1` and `2` but not for that at position `0`. The agent + // maintains a state in \c SumAgentState. The handler increments \c + // SumAgentState upon each received (new) `true` value from its slave at + // position `0`. Whenever \c SumAgentState has been updated, it is sent to the + // slaves at positions `1` and `2`. The handler results in the sum of the + // values received from slaves at positions `1`, `2`, and `3`. + using SumResult = + std::tuple; + using SumHandler = std::function, bool>, std::pair, bool>, + std::pair, bool>, + std::pair, bool>)>; + uint32_t SumAgentState = 0; AgentHandle SumAgent = C->createAgent( - "Sum Agent", DeluxeAgent::D( - [](std::pair I1, - std::pair I2) -> Optional { - LOG_INFO_STREAM - << "\n*******\nSum Agent triggered with values:\n" - << (I1.second ? "" : "") - << " int value: " << I1.first << "\n" - << (I2.second ? "" : "") - << " float value: " << I2.first << "\n******\n"; - return {I1.first + I2.first}; - })); + "Sum Agent", + SumHandler([&SumAgentState]( + std::pair, bool> I0, + std::pair, bool> I1, + std::pair, bool> I2, + std::pair, bool> I3) -> SumResult { + const auto V0 = std::get<0>(I0.first); + const auto V1 = std::get<0>(I1.first); + const auto V2 = std::get<0>(I2.first); + const auto V3 = std::get<0>(I3.first); + LOG_INFO_STREAM << "\n*******\nSum Agent triggered with values:\n" + << (I0.second ? "" : "") + << " bool value: " << V0 << "\n" + << (I1.second ? "" : "") + << " int value: " << V1 << "\n" + << (I2.second ? "" : "") + << " float value: " << V2 << "\n" + << (I3.second ? "" : "") + << " double value: " << V3 << "\n******\n"; + if (I0.second && V0) { + ++SumAgentState; + } + const SingleUInt32OutputType MasterOutput = + I0.second && V0 + ? SingleUInt32OutputType(DeluxeTuple(SumAgentState)) + : SingleUInt32OutputType(); + const DeluxeTuple Output = {V1 + V2 + V3}; + return {{Output}, {}, {MasterOutput}, {MasterOutput}, {}}; + })); // // Connect low-level agents to the high-level agent. // LOG_INFO("Connect low-level agents to the high-level agent."); - C->connectAgents(SumAgent, 0, IntAgent, "Int Agent Channel"); - C->connectAgents(SumAgent, 1, FloatAgent, "Float Agent Channel"); + C->connectAgents(SumAgent, 0, BoolAgent, "Bool Agent Channel"); + C->connectAgents(SumAgent, 1, IntAgent, "Int Agent Channel"); + C->connectAgents(SumAgent, 2, FloatAgent, "Float Agent Channel"); + C->connectAgents(SumAgent, 3, TupleAgent, "Tuple Agent Channel"); // // For simulation output, create a logger agent writing the output of the // high-level agent into a log stream. // LOG_INFO("Create a logger agent."); - // The agent logs each new input value and produces nothing. + // The agent dumps each received (new) value to \c LOG_INFO_STREAM and + // produces nothing; does not receive mater-input and does not produce + // master-output. AgentHandle LoggerAgent = C->createAgent("Logger Agent", - DeluxeAgent::D( + std::function(std::pair)>( [](std::pair Sum) -> Optional { if (Sum.second) { LOG_INFO_STREAM << "Result: " << Sum.first << "\n"; } 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, SumAgent, "Sum Agent Channel"); // // Do simulation. // LOG_INFO("Setting up and performing simulation."); // // Initialize deluxe context for simulation. // C->initializeSimulation(); // // Create some vectors and register them for their corresponding sensors. // + std::vector BoolValues(NumberOfSimulationCycles); + std::generate(BoolValues.begin(), BoolValues.end(), + [i = 0](void) mutable -> bool { return (++i % 4) == 0; }); + C->registerSensorValues(BoolSensor, BoolValues.begin(), BoolValues.end()); + std::vector IntValues(NumberOfSimulationCycles); std::generate(IntValues.begin(), IntValues.end(), [i = 0](void) mutable { return ++i; }); C->registerSensorValues(IntSensor, IntValues.begin(), IntValues.end()); std::vector FloatValues(NumberOfSimulationCycles); std::generate(FloatValues.begin(), FloatValues.end(), [f = 0.5f](void) mutable { f += 0.3f; return std::floor(f) + 0.5f; }); C->registerSensorValues(FloatSensor, FloatValues.begin(), FloatValues.end()); + std::vector TupleValues(NumberOfSimulationCycles); + std::generate(TupleValues.begin(), TupleValues.end(), + [f1 = 0.f, f2 = 3.14f](void) mutable -> TupleType { + f1 += f2; + f2 -= f1; + return {f1, f2}; + }); + C->registerSensorValues(TupleSensor, TupleValues.begin(), TupleValues.end()); + // // Simulate. // C->simulate(NumberOfSimulationCycles); return 0; } diff --git a/examples/messaging/messaging.cpp b/examples/messaging/messaging.cpp index 7d1269b..640a2d1 100644 --- a/examples/messaging/messaging.cpp +++ b/examples/messaging/messaging.cpp @@ -1,126 +1,127 @@ //===-- examples/messaging/messaging.cpp ------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file examples/messaging/messaging.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief An example showcasing features related to the \c rosa::Message class. /// //===----------------------------------------------------------------------===// #include "rosa/config/version.h" #include "rosa/core/MessageHandler.hpp" #include "rosa/support/log.h" #include "rosa/support/terminal_colors.h" using namespace rosa; using namespace rosa::terminal; int main(void) { LOG_INFO_STREAM << library_string() << " -- " << Color::Red << "messaging" << Color::Default << '\n'; auto &Log = LOG_INFO_STREAM << '\n'; // Message interface. auto PMsg = Message::create(1, 2); auto &Msg = *PMsg; Log << "Checking on a 'Message with TypeList<>':" << "\n Size: " << Msg.Size << "\n Pos 0 is uint8_t: " << Msg.isTypeAt(0) << "\n Pos 1 is uint16_t: " << Msg.isTypeAt(1) << "\n Pos 2 is uint32_t: " << Msg.isTypeAt(1) << "\n Value at pos 0: " << PRINTABLE(Msg.valueAt(0)) << "\n Value at pos 1: " << Msg.valueAt(1) << "\n\n"; // MessageMatcher. using MyMatcher = MsgMatcher; Log << "Matching against 'TypeList':" << "\n matching: " << MyMatcher::doesStronglyMatch(Msg); auto Vs = MyMatcher::extractedValues(Msg); Log << "\n value: '(" << PRINTABLE(std::get<0>(Vs)) << ", " << std::get<1>(Vs) << ")'\n\n"; using MyWrongMatcher = MsgMatcher; Log << "Matching against 'TypeList':" << "\n matching: " << MyWrongMatcher::doesStronglyMatch(Msg) << "\n\n"; using MyAtom = AtomConstant; const MyAtom &A = MyAtom::Value; using MyNAtom = AtomConstant; auto PAMsg = Message::create(A); auto &AMsg = *PAMsg; Log << "Checking on a 'Message with TypeList>':" << "\n Size: " << AMsg.Size << "\n Pos 0 is 'AtomValue': " << AMsg.isTypeAt(0) << "\n Pos 0 is 'AtomConstant': " << AMsg.isTypeAt(0) << "\n\n"; using MyAtomMatcher = MsgMatcher; Log << "Matching against 'TypeList>':" << "\n matching: " << MyAtomMatcher::doesStronglyMatch(AMsg) << "\n value: '(" - << to_string(std::get<0>(MyAtomMatcher::extractedValues(AMsg))) << ")'" + << std::to_string(std::get<0>(MyAtomMatcher::extractedValues(AMsg))) + << ")'" << "\n\n"; using MyWrongAtomMatcher = MsgMatcher; Log << "Matching against 'TypeList>':" << "\n matching: " << MyWrongAtomMatcher::doesStronglyMatch(AMsg) << "\n\n"; // Invoker. auto IP = Invoker::wrap(Invoker::F([&Log](MyAtom) noexcept->void { Log << "** Handling 'Message with " "TypeList>'.\n"; })); auto &I = *IP; // Get a reference from the pointer. Log << "Invoking a function of signature 'void(AtomConstant) " "noexcept':" << "\n with 'Message with TypeList'" << "\n does Message match Invoker: " << I.match(Msg) << "\n invoking..."; I(Msg); Log << "\n with 'Message with TypeList>'..." << "\n does Message match Invoker: " << I.match(AMsg) << "\n invoking..."; I(AMsg); Log << "\n\n"; // MessageHandler. MessageHandler Handler{ Invoker::F([&Log](uint8_t, uint16_t) { Log << "** Handling 'Message with TypeList'\n"; }), Invoker::F([&Log](MyAtom) { Log << "** Handling 'Message with " "TypeList>'\n"; })}; auto PANMsg = Message::create(MyNAtom::Value); auto &ANMsg = *PANMsg; Log << "Handling Messages with 'MessageHandler " "{ Invoker::F, " "Invoker::F> }':" << "\n 'Message with TypeList'" << "\n can handle: " << Handler.canHandle(Msg) << "\n handling..."; Handler(Msg); Log << "\n 'Message with TypeList>'" << "\n can handle: " << Handler.canHandle(AMsg) << "\n handling..."; Handler(AMsg); Log << "\n 'Message with TypeList>'" << "\n can handle: " << Handler.canHandle(ANMsg) << "\n handling..."; Handler(ANMsg); Log << "\n\n"; Log << "Terminating, destroying automatic variables.\n"; return 0; } diff --git a/examples/type-facilities/type-facilities.cpp b/examples/type-facilities/type-facilities.cpp index d714a73..b4a3ade 100644 --- a/examples/type-facilities/type-facilities.cpp +++ b/examples/type-facilities/type-facilities.cpp @@ -1,88 +1,88 @@ //===-- examples/type-facilities/type-facilities.cpp ------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file examples/type-facilities/type-facilities.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief An example showcasing various type-related support facilities. /// //===----------------------------------------------------------------------===// #include "rosa/config/version.h" #include "rosa/support/log.h" #include "rosa/support/terminal_colors.h" #include "rosa/support/type_token.hpp" using namespace rosa; using namespace rosa::terminal; int main(void) { LOG_INFO_STREAM << library_string() << " -- " << Color::Red << "type facilities" << Color::Default << '\n'; auto &Log = LOG_TRACE_STREAM; Log << "\nNumberOfBuiltinTypes: " << NumberOfBuiltinTypes << "\nTokenBits: " << token::TokenBits << "\nRepresentationBits: " << token::RepresentationBits << "\nMaxTokenizableListSize: " << token::MaxTokenizableListSize << "\n\n"; Log << "Type number information on 'uint8_t':"; constexpr TypeNumber TN = TypeNumberOf::Value; Log << "\n type number: " << PRINTABLE_TN(TN) << "\n size: " << TypeForNumber::Size << "\n name: " << TypeForNumber::Name << "\n\n"; Log << "Type number information on 'std::string':"; constexpr TypeNumber TNS = TypeNumberOf::Value; Log << "\n type number: " << PRINTABLE_TN(TNS) << "\n size: " << TypeForNumber::Size << "\n name: " << TypeForNumber::Name << "\n\n"; Log << "Type number information of AtomConstants:"; using Atom1 = AtomConstant; using Atom2 = AtomConstant; Log << "\n std::is_same::value: " << std::is_same::value << "\n TypeNumberOf::Value: " << PRINTABLE_TN(TypeNumberOf::Value) << "\n TypeNumberOf::Value: " << PRINTABLE_TN(TypeNumberOf::Value) << "\n name: " << TypeForNumber::Value>::Name << "\n\n"; Log << "Type token information on 'TypeList':"; // \c rosa::Token is generated statically. constexpr Token T = TypeToken::Value; STATIC_ASSERT( (T == TypeListToken>::Value), "alias template definition is wrong"); Token T_ = T; // We need a non-const value for dropping head later. // Iterate over encoded entries in \c T_. while (!emptyToken(T_)) { Log << "\n token: " << PRINTABLE_TOKEN(T_) << "\n valid: " << validToken(T_) << "\n empty: " << emptyToken(T_) - << "\n length: " << lengthOfToken(T_) + << "\n length: " << static_cast(lengthOfToken(T_)) << "\n full size: " << sizeOfValuesOfToken(T_) << "\n head type number: " << PRINTABLE_TN(headOfToken(T_)) << "\n size of head: " << sizeOfHeadOfToken(T_) << "\n name of head: " << nameOfHeadOfToken(T_) << "\n is head uint8_t: " << isHeadOfTokenTheSameType(T_) << "\n is head uint16_t: " << isHeadOfTokenTheSameType(T_) << "\n is head std::string: " << isHeadOfTokenTheSameType(T_) << "\nDropping head..."; dropHeadOfToken(T_); } // Here when Token became empty. Log << "\n token: " << PRINTABLE_TOKEN(T_) << "\n empty: " << emptyToken(T_) << '\n'; return 0; } diff --git a/include/rosa/agent/Abstraction.hpp b/include/rosa/agent/Abstraction.hpp index c9c4fa2..b44b2de 100644 --- a/include/rosa/agent/Abstraction.hpp +++ b/include/rosa/agent/Abstraction.hpp @@ -1,191 +1,238 @@ //===-- rosa/agent/Abstraction.hpp ------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/Abstraction.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Definition of *abstraction* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_ABSTRACTION_HPP #define ROSA_AGENT_ABSTRACTION_HPP #include "rosa/agent/Functionality.h" #include "rosa/support/debug.hpp" #include #include namespace rosa { namespace agent { /// Abstracts values from a type to another one. /// /// \tparam T type to abstract from /// \tparam A type to abstract to template class Abstraction : public Functionality { protected: /// Value to abstract to by default. const A Default; - public: /// Creates an instance. /// /// \param Default value to abstract to by default Abstraction(const A Default) noexcept : Default(Default) {} /// Destroys \p this object. ~Abstraction(void) = default; + /// Checks wether the Abstraction evaluates to default at the given position + /// + /// \param V the value at which to check if the function falls back to it's + /// default value. + /// \return true, the default implementation always falls back to the default + /// value + virtual bool isDefaultAt(const T &V) const noexcept{ + (void)V; + return true; + } + /// Abstracts a value from type \p T to type \p A. /// /// \note The default implementation always returns /// \c rosa::agent::Abstraction::Default, hence the actual argument is /// ignored. /// /// \return the abstracted value virtual A operator()(const T &) const noexcept { return Default; } }; /// Implements \c rosa::agent::Abstraction as a \c std::map from a type to /// another one. /// /// \note This implementation is supposed to be used to abstract between /// enumeration types, which is statically enforced. /// /// \tparam T type to abstract from /// \tparam A type to abstract to template class MapAbstraction : public Abstraction, private std::map { // Make sure the actual type arguments are enumerations. STATIC_ASSERT((std::is_enum::value && std::is_enum::value), "mapping not enumerations"); // Bringing into scope inherited members. using Abstraction::Default; using std::map::end; using std::map::find; public: /// Creates an instance by initializing the underlying \c std::map. /// /// \param Map the mapping to do abstraction according to /// \param Default value to abstract to by default MapAbstraction(const std::map &Map, const A Default) noexcept : Abstraction(Default), std::map(Map) {} /// Destroys \p this object. ~MapAbstraction(void) = default; + /// Checks wether the Abstraction evaluates to default at the given position + /// + /// \param V the value at which to check if the function falls back to it's + /// default value. + /// \return true if the Abstraction falls back to the default value + bool isDefaultAt(const T &V) const noexcept override { + const auto I = find(V); + return I == end() ? true : false; + } + /// Abstracts a value from type \p T to type \p A based on the set mapping. /// /// Results in the value associated by the set mapping to the argument, or /// \c rosa::agent::MapAbstraction::Default if the actual argument is not /// associated with anything by the set mapping. /// /// \param V value to abstract /// /// \return the abstracted value based on the set mapping A operator()(const T &V) const noexcept override { const auto I = find(V); return I == end() ? Default : *I; } }; /// Implements \c rosa::agent::Abstraction as a \c std::map from ranges of a /// type to values of another type. /// /// \note This implementation is supposed to be used to abstract ranges of /// arithmetic types into enumerations, which is statically enforced. /// /// \invariant The keys in the underlying \c std::map define valid ranges /// such that `first <= second` and there are no overlapping ranges defined by /// the keys. /// /// \tparam T type to abstract from /// \tparam A type to abstract to template class RangeAbstraction : public Abstraction, private std::map, A> { // Make sure the actual type arguments are matching our expectations. STATIC_ASSERT((std::is_arithmetic::value), "abstracting not arithmetic"); - STATIC_ASSERT((std::is_enum::value), "abstracting not to enumeration"); + /// \todo check if this compiles with the definition of abstractions as + /// self-aware properties + //STATIC_ASSERT((std::is_enum::value), "abstracting not to enumeration"); // Bringing into scope inherited members. using Abstraction::Default; using std::map, A>::begin; using std::map, A>::end; using std::map, A>::find; public: /// Creates an instance by Initializing the unserlying \c std::map. /// /// \param Map the mapping to do abstraction according to /// \param Default value to abstract to by default /// /// \pre Each key defines a valid range such that `first <= second` and /// there are no overlapping ranges defined by the keys. RangeAbstraction(const std::map, A> &Map, const A &Default) : Abstraction(Default), std::map, A>(Map) { // Sanity check. ASSERT(std::all_of( begin(), end(), [this](const std::pair, A> &P) { return P.first.first <= P.first.second && std::all_of(++find(P.first), end(), [&P](const std::pair, A> &R) { // \note Values in \c Map are sorted. return P.first.first < P.first.second && P.first.second <= R.first.first || P.first.first == P.first.second && P.first.second < R.first.first; }); })); } /// Destroys \p this object. ~RangeAbstraction(void) = default; + /// Checks wether the Abstraction evaluates to default at the given position + /// + /// \param V the value at which to check if the function falls back to it's + /// default value. + /// \return true if the Abstraction falls back to the default value + bool isDefaultAt(const T &V) const noexcept override { + auto I = begin(); + bool Found = false; // Indicates if \c I refers to a matching range. + bool Failed = false; // Indicates if it is pointless to continue searching. + while (!Found && !Failed && I != end()) { + if (V < I->first.first) { + // No match so far and \p V is below the next range, never will match. + // \note Keys are sorted in the map. + return true; + } else if (I->first.first <= V && V < I->first.second) { + // Matching range found. + return false; + } else { + // Cannot conclude in this step, move to the next range. + ++I; + } + } + return true; + } + /// Abstracts a value from type \p T to type \p A based on the set mapping. /// /// Results in the value associated by the set mapping to the argument, or /// \c rosa::agent::RangeAbstraction::Default if the actual argument is not /// included in any of the ranges in the set mapping. /// /// \param V value to abstract /// /// \return the abstracted value based on the set mapping A operator()(const T &V) const noexcept override { auto I = begin(); bool Found = false; // Indicates if \c I refers to a matching range. bool Failed = false; // Indicates if it is pointless to continue searching. while (!Found && !Failed && I != end()) { if (V < I->first.first) { // No match so far and \p V is below the next range, never will match. // \note Keys are sorted in the map. Failed = true; } else if (I->first.first <= V && V < I->first.second) { // Matching range found. Found = true; } else { // Cannot conclude in this step, move to the next range. ++I; } } ASSERT(!Found || I != end()); return Found ? I->second : Default; } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_ABSTRACTION_HPP diff --git a/include/rosa/agent/Confidence.hpp b/include/rosa/agent/Confidence.hpp index 3d07606..35b7496 100644 --- a/include/rosa/agent/Confidence.hpp +++ b/include/rosa/agent/Confidence.hpp @@ -1,204 +1,202 @@ //===-- rosa/agent/Confidence.hpp -------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/Confidence.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Definition of *confidence* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_CONFIDENCE_HPP #define ROSA_AGENT_CONFIDENCE_HPP #include "rosa/agent/History.hpp" #include "rosa/support/debug.hpp" #include namespace rosa { namespace agent { /// Confidence validator. /// /// Checks the plausibility of given values by validating if a valid region /// contains them. It also capable of checking consistency by validating the /// rate of change recorded by a \c rosa::agent::History object against a /// maximal absolute valid rate of change. /// /// \tparam T type of values to validate /// /// \note The template is defined only for arithmetic types. /// /// \note The lower bound is inclusive and the upper bound is exclusive. /// /// \invariant The bounds are defined in a meaningful way:\code /// LowerBound <= UpperBound /// \endcode template class Confidence : public Functionality { // Make sure the actual type argument is an arithmetic type. STATIC_ASSERT(std::is_arithmetic::value, "not arithmetic Confidence"); public: /// Unsigned type corresponding to \p T. using UT = unsigned_t; /// The minimal value of type \p T. /// \note Not exist \c V of type \p T such that `V < Min`. static constexpr T Min = std::is_integral::value ? std::numeric_limits::min() : std::numeric_limits::lowest(); /// The maximal value of type \p T. /// \note Not exist \c V of type \p T such that `V > Max`. static constexpr T Max = (std::is_integral::value || !std::numeric_limits::has_infinity) ? std::numeric_limits::max() : std::numeric_limits::infinity(); /// The maximal value of type \c UT. /// \note Not exist \c V of type \c UT such that `V > UnsignedMax`. static constexpr UT UnsignedMax = (std::is_integral::value || !std::numeric_limits::has_infinity) ? std::numeric_limits::max() : std::numeric_limits::infinity(); private: /// The inclusive lower bound for plausibility check. T LowerBound; /// The exclusive upper bound for plausibility check. T UpperBound; /// The maximal absolute rate of change for consistency check. UT ChangeRate; public: /// Creates an instance by setting the validator variables. /// /// \param LowerBound the lower bound for plausability check /// \param UpperBound the upper bound for plausability check /// \param ChangeRate maximal absolute rate of change for consistency check /// /// \pre The bounds are defined in a meaningful way:\code /// LowerBound <= UpperBound /// \endcode Confidence(const T LowerBound = Min, const T UpperBound = Max, const UT ChangeRate = UnsignedMax) noexcept - : LowerBound(LowerBound), - UpperBound(UpperBound), - ChangeRate(ChangeRate) { + : LowerBound(LowerBound), UpperBound(UpperBound), ChangeRate(ChangeRate) { // Make sure Confidence is created in a valid state. if (LowerBound > UpperBound) { ROSA_CRITICAL("Confidence with LowerBound higher than UpperBound"); } } /// Destroys \p this object. ~Confidence(void) = default; /// Gives a snapshot of the current state of the validator variables. /// /// \param [out] LowerBound to copy \c rosa::agent::Confidence::LowerBound /// into /// \param [out] UpperBound to copy \c rosa::agent::Confidence::UpperBound /// into /// \param [out] ChangeRate to copy \c rosa::agent::Confidence::ChangeRate /// into void getParameters(T &LowerBound, T &UpperBound, UT &ChangeRate) const noexcept { // Copy members to the given references. LowerBound = this->LowerBound; UpperBound = this->UpperBound; ChangeRate = this->ChangeRate; } /// Sets the lower bound for plausability check. /// /// Beyond setting the lower bound, the function also adjusts the upper bound /// to the given lower bound if the new lower bound would be higher than the /// upper bound. /// /// \param LowerBound the new lower bound to set void setLowerBound(const T LowerBound) noexcept { // Adjust UpperBound if necessary, then set LowerBound. if (UpperBound < LowerBound) { UpperBound = LowerBound; } this->LowerBound = LowerBound; } /// Sets the upper bound for plausability check. /// /// Beyond setting the upper bound, the function also adjusts the lower bound /// to the given upper bound if the new upper bound would be lower than the /// lower bound. /// /// \param UpperBound the new upper bound to set void setUpperBound(const T UpperBound) noexcept { // Adjust LowerBound if necessary, then set UpperBound. if (UpperBound < LowerBound) { LowerBound = UpperBound; } this->UpperBound = UpperBound; } /// Sets the maximal rate of change for consistency check. /// /// \param ChangeRate the new rate of change to set void setChangeRate(const UT ChangeRate) noexcept { // Set ChangeRate. this->ChangeRate = ChangeRate; } /// Tells the binary confidence on the plausibility of a value. /// /// \param V value to check /// /// \return whether \c V is within the range defined by /// \c rosa::agent::Confidence::LowerBound and /// \c rosa::agent::Confidence::UpperBound. bool operator()(const T V) const noexcept { // Return if \c V is plausible. return LowerBound <= V && V < UpperBound; } /// Tells the binary confidence on the plausibility and consistency of the /// last value recorded by a \c rosa::agent::History instance. /// /// Consistency of the last value is checked by validating the difference with /// its preceding entry. /// /// \note The \c rosa::agent::History instance needs to store values of type /// \p T. /// /// \note An empty \c rosa::agent::History instance results in full /// confidence. /// /// \tparam N number of values \p H is able to store /// \tparam P retention policy followed by \p H when capacity is reached /// /// \param H *history* whose last entry to check template - bool operator()(const History &H) const noexcept { + bool operator()(const StaticLengthHistory &H) const noexcept { if (H.empty()) { // No entry to validate. return true; } else { // Validate the last entry and the one step average absolute difference. return (*this)(H.entry()) && H.averageAbsDiff(1) <= ChangeRate; } } }; } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_CONFIDENCE_HPP diff --git a/include/rosa/agent/FunctionAbstractions.hpp b/include/rosa/agent/FunctionAbstractions.hpp new file mode 100644 index 0000000..59200de --- /dev/null +++ b/include/rosa/agent/FunctionAbstractions.hpp @@ -0,0 +1,222 @@ +//===-- rosa/agent/FunctionAbstractions.hpp ---------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/FunctionAbstractions.hpp +/// +/// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Definition of *FunctionAbstractions* *functionality*. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP +#define ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP + +#include "rosa/agent/Abstraction.hpp" +#include "rosa/agent/Functionality.h" + +#include "rosa/support/debug.hpp" + +#include +#include +#include +#include + +namespace rosa { +namespace agent { + +/// Implements \c rosa::agent::Abstraction as a linear function, +/// y = Coefficient * X + Intercept. +/// +/// \note This implementation is supposed to be used to represent a linear +/// function from an arithmetic domain to an arithmetic range. This is enforced +/// statically. +/// +/// \tparam D type of the functions domain +/// \tparam R type of the functions range +template +class LinearFunction : public Abstraction { + // Make sure the actual type arguments are matching our expectations. + STATIC_ASSERT((std::is_arithmetic::value), + "LinearFunction not arithmetic T"); + STATIC_ASSERT((std::is_arithmetic::value), + "LinearFunction not to arithmetic"); + +protected: + /// The Intercept of the linear function + const D Intercept; + /// The Coefficient of the linear function + const D Coefficient; + +public: + /// Creates an instance. + /// + /// \param Intercept the intercept of the linear function + /// \param Coefficient the coefficient of the linear function + LinearFunction(D Intercept, D Coefficient) noexcept + : Abstraction(Intercept), Intercept(Intercept), + Coefficient(Coefficient) {} + + /// Destroys \p this object. + ~LinearFunction(void) = default; + + /// Checks wether the Abstraction evaluates to default at the given position + /// As LinearFunctions can be evaluated everythwere, this is always false + /// + /// \param V the value at which to check if the function falls back to it's + /// default value. + /// + /// \return false + bool isDefaultAt(const D &V) const noexcept override { + (void)V; + return false; + } + + /// Evaluates the linear function + /// + /// \param X the value at which to evaluate the function + /// + /// \return Coefficient*X + Intercept + virtual R operator()(const D &X) const noexcept override { + return Intercept + X * Coefficient; + } +}; + +/// Implements \c rosa::agent::Abstraction as a sine function, +/// y = Amplitude * sin(Frequency * X + Phase) + Average. +/// +/// \note This implementation is supposed to be used to represent a sine +/// function from an arithmetic domain to an arithmetic range. This is enforced +/// statically. +/// +/// \tparam D type of the functions domain +/// \tparam R type of the functions range +template +class SineFunction : public Abstraction { + // Make sure the actual type arguments are matching our expectations. + STATIC_ASSERT((std::is_arithmetic::value), + "SineFunction not arithmetic T"); + STATIC_ASSERT((std::is_arithmetic::value), + "SineFunction not to arithmetic"); + +protected: + /// The frequency of the sine wave + const D Frequency; + /// The Ampiltude of the sine wave + const D Amplitude; + /// The Phase-shift of the sine wave + const D Phase; + /// The y-shift of the sine wave + const D Average; + +public: + /// Creates an instance. + /// + /// \param Frequency the frequency of the sine wave + /// \param Amplitude the amplitude of the sine wave + /// \param Phase the phase of the sine wave + /// \param Average the average of the sine wave + SineFunction(D Frequency, D Amplitude, D Phase, D Average) noexcept + : Abstraction(Average), Frequency(Frequency), Amplitude(Amplitude), + Phase(Phase), Average(Average) {} + + /// Destroys \p this object. + ~SineFunction(void) = default; + + /// Checks wether the Abstraction evaluates to default at the given position + /// As SineFunctions can be evaluated everythwere, this is always false + /// + /// \param V the value at which to check if the function falls back to it's + /// default value. + /// + /// \return false + bool isDefaultAt(const D &V) const noexcept override { + (void)V; + return false; + } + + /// Evaluates the sine function + /// + /// \param X the value at which to evaluate the function + /// \return the value of the sine-function at X + virtual R operator()(const D &X) const noexcept override { + return Amplitude * sin(Frequency * X + Phase) + Average; + } +}; +/// Implements \c rosa::agent::Abstraction as a partial function from a domain +// /to a range. +/// +/// \note This implementation is supposed to be used to represent a partial +/// function from an arithmetic domain to an arithmetic range. This is enforced +/// statically. +/// +/// A partial function is defined as a list of abstractions, where each +/// abstraction is associated a range in which it is defined. These ranges must +/// be mutually exclusive. +/// +/// \tparam D type of the functions domain +/// \tparam R type of the functions range +template +class PartialFunction : public Abstraction { + // Make sure the actual type arguments are matching our expectations. + STATIC_ASSERT((std::is_arithmetic::value), "abstracting not arithmetic"); + STATIC_ASSERT((std::is_arithmetic::value), + "abstracting not to arithmetic"); + +private: + /// A \c rosa::agent::RangeAbstraction RA is used to represent the association + /// from ranges to Abstractions. + /// This returns the Abstraction that is defined for any given value, or + /// a default Abstraction if no Abstraction is defined for that value. + RangeAbstraction>> RA; + +public: + /// Creates an instance by Initializing the underlying \c Abstraction. + /// + /// \param Map the mapping to do abstraction according to + /// \param Default abstraction to abstract to by default + /// + /// \pre Each key defines a valid range such that `first <= second` and + /// there are no overlapping ranges defined by the keys. + PartialFunction( + const std::map, std::shared_ptr>> &Map, + const R Default) + : Abstraction(Default), + RA(Map, + std::shared_ptr>(new Abstraction(Default))) { + } + + /// Destroys \p this object. + ~PartialFunction(void) = default; + + /// Checks wether the Abstraction evaluates to default at the given position + /// + /// \param V the value at which to check if the function falls back to it's + /// default value. + /// + /// \return false if the value falls into a defined range and the Abstraction + /// defined for that range does not fall back to it's default value. + bool isDefaultAt(const D &V) const noexcept override { + return RA.isDefaultAt(V) ? true : RA(V)->isDefaultAt(V); + } + + /// Searches for an Abstraction for the given value and executes it for that + /// value, if such an Abstraction is found. The default Abstraction is + /// evaluated otherwise. + /// + /// \param V value to abstract + /// + /// \return the abstracted value based on the set mapping + R operator()(const D &V) const noexcept override { + return RA(V)->operator()(V); + } +}; +} // End namespace agent +} // End namespace rosa + +#endif // ROSA_AGENT_FUNCTIONABSTRACTIONS_HPP diff --git a/include/rosa/agent/History.hpp b/include/rosa/agent/History.hpp index 1097ad0..5593cb7 100644 --- a/include/rosa/agent/History.hpp +++ b/include/rosa/agent/History.hpp @@ -1,292 +1,528 @@ //===-- rosa/agent/History.hpp ----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/agent/History.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Definition of *history* *functionality*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_AGENT_HISTORY_HPP #define ROSA_AGENT_HISTORY_HPP #include "rosa/agent/Functionality.h" #include "rosa/config/config.h" #include "rosa/support/debug.hpp" #include "rosa/support/type_helper.hpp" #include +#include namespace rosa { namespace agent { /// Retention policies defining what a \c rosa::agent::History instance should /// do when the number of recorded entries reached its capacity. enum class HistoryPolicy { SRWF, ///< Stop Recording When Full -- no new entry is recorded when full - FIFO ///< First In First Out -- overwrite the earliest entry with a new one + FIFO, ///< First In First Out -- overwrite the earliest entry with a new one + LIFO ///< Last In First Out -- overwrite the latest entry with a new one }; -/// Implements *history* by recording and storing values. -/// -/// \note Not thread-safe implementation, which should not be a problem as any -/// instance of \c rosa::agent::Functionality is an internal component of a -/// \c rosa::Agent, which is the basic unit of concurrency. -/// -/// \tparam T type of values to store -/// \tparam N number of values to store at most -/// \tparam P retention policy to follow when capacity is reached -/// -/// \invariant The size of the underlying \c std::array is `N + 1`:\code -/// max_size() == N + 1 && N == max_size() - 1 -/// \endcode -template -class History : public Functionality, private std::array { - - // Bring into scope inherited functions that are used. - using std::array::max_size; - using std::array::operator[]; - - /// The index of the first data element in the circular buffer. - size_t Data; - - /// The index of the first empty slot in the circular buffer. - size_t Space; +template class History : public Functionality { public: - /// Creates an instances by initializing the indices for the circular buffer. - History(void) noexcept : Data(0), Space(0) {} + History(void) noexcept {} /// Destroys \p this object. - ~History(void) = default; + virtual ~History(void) = default; /// Tells the retention policy applied to \p this object. /// /// \return \c rosa::agent::History::P - static constexpr HistoryPolicy policyOfHistory(void) noexcept { return P; } + static constexpr HistoryPolicy policy(void) noexcept { return P; } /// Tells how many entries may be recorded by \c this object. /// /// \note The number of entries that are actually recorded may be smaller. /// - /// \return \c rosa::agent::History::N - static constexpr size_t lengthOfHistory(void) noexcept { return N; } + /// \return The max number of entries that may be recorded + virtual size_t maxLength(void) const noexcept = 0; /// Tells how many entries are currently recorded by \p this object. /// /// \return number of entries currently recorded by \p this object. /// /// \post The returned value cannot be larger than the capacity of \p this /// object:\code /// 0 <= numberOfEntries() && numberOfEntries <= lengthOfHistory() /// \endcode - size_t numberOfEntries(void) const noexcept { - return Data <= Space ? Space - Data : max_size() - Data + Space; - } + virtual size_t numberOfEntries(void) const noexcept = 0; /// Tells if \p this object has not recorded anything yet. /// /// \return if \p this object has no entries recorded bool empty(void) const noexcept { return numberOfEntries() == 0; } + /// Tells if the history reached it's maximum length + /// + /// \return if the history reached it's maximum length. + bool full(void) const noexcept { return numberOfEntries() == maxLength(); } + /// Gives a constant lvalue reference to an entry stored in \p this object. /// /// \note The recorded entries are indexed starting from the latest one. /// /// \param I the index at which the stored entry to take from /// /// \pre \p I is a valid index:\code - /// 0 <= I && I <= numberOfEntries() + /// 0 <= I && I < numberOfEntries() /// \endcode - const T &entry(const size_t I = 0) const noexcept { - ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. - // Position counted back from the last recorded entry. - typename std::make_signed::type Pos = Space - (1 + I); - // Actual index wrapped around to the end of the buffer if negative. - return (*this)[Pos >= 0 ? Pos : max_size() + Pos]; - } + virtual const T &entry(const size_t I = 0) const noexcept = 0; + + /// Removes all entries recorded in \p this object. + virtual void clear() noexcept = 0; private: - /// Tells if the circular buffer is full. + /// Pushes a new entry into the history. /// - /// \return if the circular buffer is full. - bool full(void) const noexcept { return numberOfEntries() == N; } - - /// Pushes a new entry into the circular buffer. + /// \note The earliest entry gets overwritten if the history is full. /// - /// \note The earliest entry gets overwritten if the buffer is full. + /// \param V value to push into the history + virtual void pushBack(const T &V) noexcept = 0; + + /// Replaces the most recent entry in the history. /// - /// \param V value to push into the buffer - void pushBack(const T &V) noexcept { - // Store value to the first empty slot and step Space index. - (*this)[Space] = V; - Space = (Space + 1) % max_size(); - if (Data == Space) { - // Buffer was full, step Data index. - Data = (Data + 1) % max_size(); - } - } + /// \param V value to replace the most current value with + virtual void replaceFront(const T &V) noexcept = 0; public: /// Adds a new entry to \p this object and tells if the operation was /// successful. /// /// \note Success of the operation depends on the actual policy. /// /// \param V value to store /// /// \return if \p V was successfully stored bool addEntry(const T &V) noexcept { switch (P) { default: ROSA_CRITICAL("unkown HistoryPolicy"); + case HistoryPolicy::LIFO: + if (full()) { + replaceFront(V); + return true; + } case HistoryPolicy::SRWF: if (full()) { return false; } // \note Fall through to FIFO which unconditionally pushes the new entry. case HistoryPolicy::FIFO: // FIFO and SRWF not full. pushBack(V); return true; } } /// Tells the trend set by the entries recorded by \p this object. /// /// The number of steps to go back when calculating the trend is defined as /// argument to the function. /// /// \note The number of steps that can be made is limited by the number of /// entries recorded by \p this object. /// /// \note The function is made a template only to be able to use /// \c std::enable_if. /// /// \tparam X always use the default! /// /// \param D number of steps to go back in *history* /// /// \return trend set by analyzed entries /// /// \pre Statically, \p this object stores signed arithmetic values:\code /// std::is_arithmetic::value && std::is_signed::value /// \endcode Dynamically, \p D is a valid number of steps to take:\code - /// 0 <= D && D < N + /// 0 <= D && D < lengthOfHistory() /// \endcode template typename std::enable_if< std::is_arithmetic::value && std::is_signed::value, X>::type - trend(const size_t D = N - 1) const noexcept { + trend(const size_t D) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); - ASSERT(0 <= D && D < N); // Boundary check. + ASSERT(0 <= D && D < maxLength()); // Boundary check. if (numberOfEntries() < 2 || D < 1) { // No entries for computing trend. return {}; // Zero element of \p T } else { // Here at least two entries. // \c S is the number of steps that can be done. const size_t S = std::min(numberOfEntries() - 1, D); size_t I = S; // Compute trend with linear regression. size_t SumIndices = 0; T SumEntries = {}; T SumSquareEntries = {}; T SumProduct = {}; while (I > 0) { // \note Indexing for the regression starts in the past. const size_t Index = S - I; const T Entry = entry(--I); SumIndices += Index; SumEntries += Entry; SumSquareEntries += Entry * Entry; SumProduct += Entry * Index; } return (SumProduct * S - SumEntries * SumIndices) / (SumSquareEntries * S - SumEntries * SumEntries); } } /// Tells the average absolute difference between consecutive entries recorded /// by \p this object /// The number of steps to go back when calculating the average is defined as /// argument to the function. /// /// \note The number of steps that can be made is limited by the number of /// entries recorded by \p this object. /// /// \note The function is made a template only to be able to use /// \c std::enable_if. /// /// \tparam X always use the default! /// /// \param D number of steps to go back in *history* /// /// \pre Statically, \p this object stores arithmetic values:\code /// std::is_arithmetic::value /// \endcode Dynamically, \p D is a valid number of steps to take:\code - /// 0 <= D && D < N + /// 0 <= D && D < lengthOfHistory() /// \endcode template typename std::enable_if::value, size_t>::type - averageAbsDiff(const size_t D = N - 1) const noexcept { + averageAbsDiff(const size_t D) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); - ASSERT(0 <= D && D < N); // Boundary check. + ASSERT(0 <= D && D < maxLength()); // Boundary check. if (numberOfEntries() < 2 || D < 1) { // No difference to average. return {}; // Zero element of \p T } else { // Here at least two entries. // \c S is the number of steps that can be done. const size_t S = std::min(numberOfEntries() - 1, D); // Sum up differences as non-negative values only, hence using an // unsigned variable for that. size_t Diffs = {}; // Init to zero. // Count down entry indices and sum up all the absolute differences. size_t I = S; T Last = entry(I); while (I > 0) { T Next = entry(--I); Diffs += Last < Next ? Next - Last : Last - Next; Last = Next; } // Return the average of the summed differences. return Diffs / S; } } + + /// Tells the average of all entries recorded by \p this object + /// + /// \tparam R type of the result + template R average() const noexcept { + R Average = 0; + for (size_t I = 0; I < numberOfEntries(); I++) { + Average += entry(I); + } + Average /= numberOfEntries(); + return Average; + } +}; + +/// Implements *history* by recording and storing values. +/// The length of the underlying std::array is static and must be set at +/// compile-time +/// +/// \note Not thread-safe implementation, which should not be a problem as any +/// instance of \c rosa::agent::Functionality is an internal component of a +/// \c rosa::Agent, which is the basic unit of concurrency. +/// +/// \tparam T type of values to store +/// \tparam N number of values to store at most +/// \tparam P retention policy to follow when capacity is reached +/// +/// \invariant The size of the underlying \c std::array is `N + 1`:\code +/// max_size() == N + 1 && N == max_size() - 1 +/// \endcode +template +class StaticLengthHistory : public History, private std::array { + + // Bring into scope inherited functions that are used. + using std::array::max_size; + using std::array::operator[]; + + /// The index of the first data element in the circular buffer. + size_t Data; + + /// The index of the first empty slot in the circular buffer. + size_t Space; + +public: + using History::policy; + using History::empty; + using History::full; + using History::addEntry; + using History::trend; + using History::averageAbsDiff; + + /// Creates an instances by initializing the indices for the circular buffer. + StaticLengthHistory(void) noexcept : Data(0), Space(0) {} + + /// Destroys \p this object. + ~StaticLengthHistory(void) override = default; + + /// Tells how many entries may be recorded by \c this object. + /// + /// \note The number of entries that are actually recorded may be smaller. + /// + /// \return \c rosa::agent::History::N + size_t maxLength(void) const noexcept override { return N; } + + /// Tells how many entries are currently recorded by \p this object. + /// + /// \return number of entries currently recorded by \p this object. + /// + /// \post The returned value cannot be larger than the capacity of \p this + /// object:\code + /// 0 <= numberOfEntries() && numberOfEntries <= lengthOfHistory() + /// \endcode + size_t numberOfEntries(void) const noexcept override { + return Data <= Space ? Space - Data : max_size() - Data + Space; + } + + /// Gives a constant lvalue reference to an entry stored in \p this object. + /// + /// \note The recorded entries are indexed starting from the latest one. + /// + /// \param I the index at which the stored entry to take from + /// + /// \pre \p I is a valid index:\code + /// 0 <= I && I < numberOfEntries() + /// \endcode + const T &entry(const size_t I = 0) const noexcept override { + ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. + // Position counted back from the last recorded entry. + typename std::make_signed::type Pos = Space - (1 + I); + // Actual index wrapped around to the end of the buffer if negative. + return (*this)[Pos >= 0 ? Pos : max_size() + Pos]; + } + + /// Removes all entries recorded in \p this object. + void clear() noexcept override { + Data = 0; + Space = 0; + } + +private: + /// Pushes a new entry into the circular buffer. + /// + /// \note The earliest entry gets overwritten if the buffer is full. + /// + /// \param V value to push into the buffer + void pushBack(const T &V) noexcept override { + // Store value to the first empty slot and step Space index. + (*this)[Space] = V; + Space = (Space + 1) % max_size(); + if (Data == Space) { + // Buffer was full, step Data index. + Data = (Data + 1) % max_size(); + } + } + + /// Replaces the most recent entry in the history. + /// + /// \param V value to replace the most current value with + void replaceFront(const T &V) noexcept override { + (*this)[(Space - 1) % max_size()] = V; + } }; /// Adds a new entry to a \c rosa::agent::History instance. /// /// \note The result of \c rosa::agent::History::addEntry is ignored. /// /// \tparam T type of values stored in \p H /// \tparam N number of values \p H is able to store /// \tparam P retention policy followed by \p H when capacity is reached /// /// \param H to add a new entry to /// \param V value to add to \p H /// /// \return \p H after adding \p V to it template -History &operator<<(History &H, const T &V) noexcept { +StaticLengthHistory &operator<<(StaticLengthHistory &H, + const T &V) noexcept { H.addEntry(V); return H; } +/// Implements *DynamicLengthHistory* by recording and storing values. +/// +/// \note Not thread-safe implementation, which should not be a problem as any +/// instance of \c rosa::agent::Functionality is an internal component of a +/// \c rosa::Agent, which is the basic unit of concurrency. +/// +/// \tparam T type of values to store +/// \tparam P retention policy to follow when capacity is reached +template +class DynamicLengthHistory : public History, private std::vector { + + // Bring into scope inherited functions that are used. + using std::vector::erase; + using std::vector::begin; + using std::vector::end; + using std::vector::size; + using std::vector::max_size; + using std::vector::resize; + using std::vector::push_back; + using std::vector::pop_back; + using std::vector::operator[]; + + /// The current length of the DynamicLengthHistory. + size_t Length; + +public: + using History::policy; + using History::empty; + using History::full; + using History::addEntry; + using History::trend; + using History::averageAbsDiff; + + /// Creates an instances by setting an initial length + DynamicLengthHistory(size_t Length) noexcept : Length(Length) { + this->resize(Length); + } + + /// Destroys \p this object. + ~DynamicLengthHistory(void) override = default; + + /// Tells how many entries may be recorded by \c this object. + /// + /// \note The number of entries that are actually recorded may be smaller. + /// + /// \return \c rosa::agent::DynamicLengthHistory::N + size_t maxLength(void) const noexcept override { return Length; } + + /// Tells how many entries are currently recorded by \p this object. + /// + /// \return number of entries currently recorded by \p this object. + /// + /// \post The returned value cannot be larger than the capacity of \p this + /// object:\code + /// 0 <= numberOfEntries() && numberOfEntries <= + /// lengthOfHistory() \endcode + size_t numberOfEntries(void) const noexcept { return size(); } + + /// Gives a constant lvalue reference to an entry stored in \p this object. + /// + /// \note The recorded entries are indexed starting from the latest one. + /// + /// \param I the index at which the stored entry to take from + /// + /// \pre \p I is a valid index:\code + /// 0 <= I && I < numberOfEntries() + /// \endcode + const T &entry(const size_t I = 0) const noexcept override { + ASSERT(0 <= I && I < numberOfEntries()); // Boundary check. + return this->operator[](size() - I - 1); + } + + /// Removes all entries recorded in \p this object. + void clear() noexcept override { erase(begin(), end()); } + +private: + /// Pushes a new entry into the circular buffer. + /// + /// \note The earliest entry gets overwritten if the buffer is full. + /// + /// \param V value to push into the buffer + void pushBack(const T &V) noexcept override { + if (full()) { + erase(begin()); + } + push_back(V); + } + + /// Replaces the most recent entry in the history. + /// + /// \param V value to replace the most current value with + void replaceFront(const T &V) noexcept override { + (void)pop_back(); + push_back(V); + } + +public: + /// Resizes the History length. If the new length is smaller than the number + /// of currently stored values, values are deleted according to the + /// HistoryPolicy. + /// + /// @param NewLength The new Length of the History. + void setLength(size_t NewLength) noexcept { + Length = NewLength; + if (NewLength < numberOfEntries()) { + switch (P) { + default: + ROSA_CRITICAL("unkown HistoryPolicy"); + case HistoryPolicy::LIFO: + case HistoryPolicy::SRWF: + // Delete last numberOfEntries() - NewLength items from the back + erase(begin() + NewLength, end()); + break; + case HistoryPolicy::FIFO: + // Delete last numberOfEntries() - NewLength items from the front + erase(begin(), begin() + (numberOfEntries() - NewLength)); + break; + } + } + this->resize(Length); + } +}; + +/// Adds a new entry to a \c rosa::agent::DynamicLengthHistory instance. +/// +/// \note The result of \c rosa::agent::DynamicLengthHistory::addEntry is +/// ignored. +/// +/// \tparam T type of values stored in \p H +/// \tparam P retention policy followed by \p H when capacity is reached +/// +/// \param H to add a new entry to +/// \param V value to add to \p H +/// +/// \return \p H after adding \p V to it +template +DynamicLengthHistory &operator<<(DynamicLengthHistory &H, + const T &V) noexcept { + H.addEntry(V); + return H; +} } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_HISTORY_HPP diff --git a/include/rosa/agent/RangeConfidence.hpp b/include/rosa/agent/RangeConfidence.hpp new file mode 100644 index 0000000..60a53c1 --- /dev/null +++ b/include/rosa/agent/RangeConfidence.hpp @@ -0,0 +1,108 @@ +//===-- rosa/agent/RangeConfidence.hpp --------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/agent/RangeConfidence.hpp +/// +/// \author Benedikt Tutzer (benedikt.tutzer@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Definition of *RangeConfidence* *functionality*. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_AGENT_RANGECONFIDENCE_HPP +#define ROSA_AGENT_RANGECONFIDENCE_HPP + +#include "rosa/agent/Abstraction.hpp" +#include "rosa/agent/FunctionAbstractions.hpp" +#include "rosa/agent/Functionality.h" + +#include "rosa/support/debug.hpp" + +#include +#include +#include +#include + +namespace rosa { +namespace agent { + +/// Evaluates a map of ID's to Abstractions at a given value and returns the +/// results as a map from ID's to results of the corresponding Abstraction +/// +/// \note This implementation is supposed to be used to abstract ranges of +/// arithmetic types into maps whose values are of another arithmetic type, +/// which is statically enforced. +/// +/// \tparam D type to abstract from +/// \tparam I type the type of the ID's +/// \tparam R type of the range +template +class RangeConfidence : protected Abstraction>, + private std::map> { + // Make sure the actual type arguments are matching our expectations. + STATIC_ASSERT((std::is_arithmetic::value), "abstracting not arithmetic"); + STATIC_ASSERT((std::is_arithmetic::value), + "abstracting not to arithmetic"); + +private: + /// Wether to include default results in the result-map or not + bool IgnoreDefaults; + +public: + /// Creates an instance by Initializing the underlying \c Abstraction and + /// \c std::map. + /// + /// \param Abstractions the Abstractions to be evaluated + /// \param IgnoreDefaults wether to include default results in the result-map + /// or not (defaults to false). + RangeConfidence(const std::map> &Abstractions, + bool IgnoreDefaults = false) + : Abstraction>({}), std::map>( + Abstractions), + IgnoreDefaults(IgnoreDefaults) {} + + /// Destroys \p this object. + ~RangeConfidence(void) = default; + + /// Checks wether all Abstractions evaluate to default at the given position + /// + /// \param V the value at which to check if the functions falls back to it's + /// default value. + /// + /// \return true, if all Abstractions evaluate to default + bool isDefaultAt(const D &V) const noexcept override { + for (auto const &P : ((std::map>)*this)) { + if (!P.second.isDefaultAt(V)) + return false; + } + return true; + } + + /// All Abstractions stored in the underlying \c std::map are evaluated for + /// the given value. Their results are stored in another map, with + /// corresponding keys. + /// If IgnoreDefaults is set, Abstractions that default for that value are not + /// evaluated and inserted into the resulting \c std::map + /// + /// \param V value to abstract + /// + /// \return a \c std::map containing the results of the stored Abstractions, + /// indexable by the key's the Abstractions are associated with + std::map operator()(const D &V) const noexcept override { + std::map Ret; + for (auto const &P : ((std::map>)*this)) { + if (!IgnoreDefaults || !P.second.isDefaultAt(V)) + Ret.insert(std::pair(P.first, P.second(V))); + } + return Ret; + } +}; +} // End namespace agent +} // End namespace rosa + +#endif // ROSA_AGENT_RANGECONFIDENCE_HPP diff --git a/include/rosa/core/Invoker.hpp b/include/rosa/core/Invoker.hpp index 9f35ca3..10786aa 100644 --- a/include/rosa/core/Invoker.hpp +++ b/include/rosa/core/Invoker.hpp @@ -1,258 +1,258 @@ //===-- rosa/core/Invoker.hpp -----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/core/Invoker.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Facilities for providing actual arguments for functions as /// \c rosa::Messageobjects. /// //===----------------------------------------------------------------------===// #ifndef ROSA_CORE_INVOKER_HPP #define ROSA_CORE_INVOKER_HPP #include "rosa/core/MessageMatcher.hpp" #include "rosa/support/log.h" #include "rosa/support/sequence.hpp" #include #include namespace rosa { /// Wraps a function and provides a simple interface to invoke the stored /// function by passing actual arguments as a \c rosa::Message object. /// /// \note A \c rosa::Invoker instance is supposed to be owned by a /// \c rosa::MessageHandler instance, and not being used directly from user /// code. class Invoker { protected: /// Creates an instance. /// /// \note Protected constructor restricts instantiation to derived classes. Invoker(void) noexcept; public: /// Destroys \p this object. virtual ~Invoker(void); /// Possible results of an invocation. enum class Result { NoMatch, ///< The wrapped function could not be invoked Invoked ///< The wrapped function has been invoked }; /// Type alias for a smart-pointer for \c rosa::Invoker. using invoker_t = std::unique_ptr; /// Type alias for \c rosa::Invoker::Result. using result_t = Result; /// Tells if a \c rosa::Message object can be used to invoke the function /// wrapped in \p this object. /// /// \param Msg \c rosa::Message to check /// /// \return whether \p Msg can be used to invoke the wrapped function virtual bool match(const Message &Msg) const noexcept = 0; /// Tries to invoke the wrapped function with a \c rosa::Message object. /// /// The wrapped function is invoked if the actual \c rosa::Message object can /// be used to invoke it. /// /// \param Msg \c rosa::Message to try to invoke the wrapped function with /// /// \return whether the wrapped function could be invoked with \p Msg virtual result_t operator()(const Message &Msg) const noexcept = 0; /// Instantiates an implementation of \c rosa::Invoker with the given /// function. /// /// \note As there is no empty \c rosa::Message, no \c rosa::Invoker wraps a /// function without any argument. /// /// \todo Enforce F does not potentially throw exception. /// /// \tparam T type of the first mandatory argument /// \tparam Ts types of any further arguments /// /// \param F function to wrap /// /// \return new \c rosa::Invoker::invoker_t object created from the given /// function template static invoker_t wrap(std::function &&F) noexcept; /// Convenience template alias for casting callable stuff to function objects /// for wrapping. /// /// \tparam Ts types of arguments /// /// \todo Should make it possible to avoid using an explicit conversion for /// the arguments of wrap. template using F = std::function; /// Convenience template for preparing non-static member functions into /// function objects for wrapping. /// /// \tparam C type whose non-static member the function is /// \tparam Ts types of arguments /// /// \see \c THISMEMBER template static inline F M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept; }; /// Convenience preprocessor macro for the typical use of \c rosa::Invoker::M. /// It can be used inside a class to turn a non-static member function into a /// function object capturing this pointer, so using the actual object when /// handling a \c rosa::Message. /// /// \param FUN the non-static member function to wrap /// /// \note Inside the class \c MyClass, use\code /// THISMEMBER(fun) /// \endcode instead of\code /// Invoker::M(this, &MyClass::fun) /// \endcode #define THISMEMBER(FUN) \ Invoker::M(this, &std::decay::type::FUN) /// Nested namespace with implementation of \c rosa::Invoker and helper /// templates, consider it private. namespace { /// \defgroup InvokerImpl Implementation for rosa::Invoker /// /// Implements the \c rosa::Invoker interface for functions with different /// signatures. /// ///@{ /// Declaration of \c rosa::InvokerImpl implementing \c rosa::Invoker. /// /// \tparam Fun function to wrap template class InvokerImpl; /// Implementation of \c rosa::InvokerImpl for \c std::function. /// /// \tparam T type of the first mandatory argument /// \tparam Ts types of further arguments /// /// \note As there is no empty \c rosa::Message, no \c rosa::Invoker wraps a /// function without any argument, i.e., no /// \c std::function. template class InvokerImpl> final : public Invoker { /// Type alias for the stored function. using function_t = std::function; /// Type alias for correctly typed argument-tuples as obtained from /// \c rosa::Message. using args_t = std::tuple; /// Alias for \c rosa::MessageMatcher for the arguments of the stored /// function. using Matcher = MsgMatcher; /// The wrapped function. const function_t F; /// Invokes \c InvokerImpl::F by unpacking arguments from a \c std::tuple with /// the help of the actual template arguments. /// /// \tparam S sequence of numbers indexing \c std::tuple for arguments /// /// \param Args arguments to invoke \c InvokerImpl::F with /// /// \pre the length of \p S and size of \p Args are matching:\code /// sizeof...(S) == std::tuple_size::value /// \endcode template inline void invokeFunction(Seq, const args_t &Args) const noexcept; public: /// Creates an instance. /// /// \param F function to wrap /// /// \pre \p F is valid:\code /// bool(F) /// \endcode InvokerImpl(function_t &&F) noexcept : F(F) { ASSERT(bool(F)); // Sanity check. } /// Destroys \p this object. ~InvokerImpl(void) = default; /// Tells if a \c rosa::Message object can be used to invoke the function /// wrapped in \p this object. /// /// \param Msg \c rosa::Message to check /// /// \return whether \p Msg can be used to invoke the wrapped function bool match(const Message &Msg) const noexcept override { return Matcher::doesStronglyMatch(Msg); - }; + } /// Tries to invoke the wrapped function with a \c rosa::Message object. /// /// The wrapped function is invoked if the actual \c rosa::Message object can /// be used to invoke it. /// /// \param Msg \c rosa::Message to try to invoke the wrapped function with /// /// \return whether the wrapped function could be invoked with \p Msg result_t operator()(const Message &Msg) const noexcept override { if (match(Msg)) { LOG_TRACE("Invoking with matching arguments"); - invokeFunction(typename GenSeq::Type(), - Matcher::extractedValues(Msg)); + invokeFunction(seq_t(), Matcher::extractedValues(Msg)); return result_t::Invoked; } else { LOG_TRACE("Tried to invoke with non-matching arguments"); return result_t::NoMatch; } } }; template template void InvokerImpl>::invokeFunction( Seq, const args_t &Args) const noexcept { - ASSERT(sizeof...(S) == std::tuple_size::value); // Sanity check. + STATIC_ASSERT(sizeof...(S) == std::tuple_size::value, + "wrong number of type parameters"); F(std::get(Args)...); } ///@} } // End namespace template Invoker::invoker_t Invoker::wrap(std::function &&F) noexcept { return std::unique_ptr( new InvokerImpl>(std::move(F))); } template Invoker::F Invoker::M(C *O, void (C::*Fun)(Ts...) noexcept) noexcept { return [ O, Fun ](Ts... Vs) noexcept->void { (O->*Fun)(Vs...); }; } } // End namespace rosa #endif // ROSA_CORE_INVOKER_HPP diff --git a/include/rosa/core/Message.hpp b/include/rosa/core/Message.hpp index ce9e6ec..7a1fb74 100644 --- a/include/rosa/core/Message.hpp +++ b/include/rosa/core/Message.hpp @@ -1,258 +1,259 @@ //===-- rosa/core/Message.hpp -----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/core/Message.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Declaration of \c rosa::Message base-class. /// //===----------------------------------------------------------------------===// #ifndef ROSA_CORE_MESSAGE_HPP #define ROSA_CORE_MESSAGE_HPP #include "rosa/support/log.h" #include "rosa/support/tokenized_storages.hpp" #include "rosa/core/forward_declarations.h" namespace rosa { /// *Message* interface. /// /// The interface provides means to check the type of the stored values, but /// actual data is to be managed by derived implementations. /// /// A \c rosa::Message instance is an immutable data object that obtains its /// data upon creation and provides only constant references for the stored /// values. /// /// \note Any reference obtained from a \c rosa::Message instance remains valid /// only as long as the owning \c rosa::Message object is not destroyed. /// /// \todo Some member functions of \c rosa::Message duplicate member functions /// of \c rosa::TokenizedStorage, which cannot be easily factored out into a /// common base class due to eventual diamond inheritance issues in derived /// classes. Could this duplication be avoided? class Message { protected: /// Creates a new instance. /// /// \note No implementation for empty list. /// /// \tparam Type type of the mandatory first argument /// \tparam Types types of any further arguments /// /// \note the actual arguments are ignored by the constructor it is only /// their type that matters. The actual values are supposed to be handled by /// any implementation derived from \c rosa::Message. /// /// \pre \p Type and \p Types are all built-in types and the number of stored /// values does not exceed \c rosa::token::MaxTokenizableListSize. template Message(const Type &, const Types &...) noexcept; /// No copying and moving of \c rosa::Message instances. ///@{ Message(const Message &) = delete; Message(Message &&) = delete; Message &operator=(const Message &) = delete; Message &operator=(Message &&) = delete; ///@} public: /// Creates a \c rosa::message_t object from constant lvalue references. /// /// \tparam Type type of the mandatory first argument /// \tparam Types types of any further arguments /// /// \param T the first value to include in the \c rosa::Message /// \param Ts optional further values to include in the \c rosa::Message /// /// \return new \c rosa::message_t object created from the given arguments template static message_t create(const Type &T, const Types &... Ts) noexcept; /// Creates a \c rosa::message_t object from rvalue references. /// /// \tparam Type type of the mandatory first argument /// \tparam Types types of any further arguments /// /// \param T the first value to include in the \c rosa::Message /// \param Ts optional further values to include in the \c rosa::Message /// /// \return new \c rosa::message_t object created from the given arguments template static message_t create(Type &&T, Types &&... Ts) noexcept; /// Represents the types of the values stored in \p this object. /// /// A valid, non-empty \c rosa::Token representing the types of the values /// stored in \p this object. const Token T; /// The number of values stored in \p this object. /// /// That is the number of types encoded in \c rosa::Message::T. - const size_t Size; + const token_size_t Size; /// Destroys \p this object. virtual ~Message(void); /// Tells if the value stored at a given index is of a given type. /// /// \note Any \c rosa::AtomConstant is encoded in \c rosa::Token as /// the \c rosa::AtomValue wrapped into it. /// /// \tparam Type type to match against /// /// \param Pos index the type of the value at is to be matched against \p Type /// /// \return if the value at index \p Pos of type \p Type /// /// \pre \p Pos is a valid index:\code /// Pos < Size /// \endcode - template bool isTypeAt(const size_t Pos) const noexcept; + template bool isTypeAt(const token_size_t Pos) const noexcept; /// Gives a constant reference of a value of a given type stored at a given /// index. /// /// \tparam Type type to give a reference of /// /// \param Pos index to set the reference for /// /// \return constant reference of \p Type for the value stored at index \p Pos /// /// \pre \p Pos is a valid index and the value at index \p Pos is of type /// \p Type: /// \code /// Pos < Size && isTypeAt(Pos) /// \endcode - template const Type &valueAt(const size_t Pos) const noexcept; + template + const Type &valueAt(const token_size_t Pos) const noexcept; protected: /// Provides an untyped pointer for the value at a given index. /// /// \param Pos index to take a pointer for /// /// \return untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < Size /// \endcode - virtual const void *pointerTo(const size_t Pos) const noexcept = 0; + virtual const void *pointerTo(const token_size_t Pos) const noexcept = 0; }; /// Nested namespace with implementation for \c rosa::Message, consider it /// private. namespace { /// Template class for an implementation of \c rosa::Message. /// /// \tparam Types types whose values are to be stored template class LocalMessage; /// Implementation of the template \c rosa::LocalMessage providing facilities /// for storing values as a \c rosa::Message object. /// /// \tparam Type type of the first mandatory value of the \c rosa::Message /// \tparam Types of any further values template class LocalMessage final : public Message, private TokenizedStorage { public: /// Creates an instance from constant lvalue references. /// /// \param T the mandatory first value to store in the \c rosa::Message object /// \param Ts optional further values to store in the \c rosa::Message object LocalMessage(const Type &T, const Types &... Ts) noexcept : Message(T, Ts...), TokenizedStorage(T, Ts...) { ASSERT(this->T == this->ST && Size == this->size()); // Sanity check. } /// Creates an instance from rvalue references. /// /// \param T the mandatory first value to store in the \c rosa::Message object /// \param Ts optional further values to store in the \c rosa::Message object LocalMessage(Type &&T, Types &&... Ts) noexcept : Message(T, Ts...), TokenizedStorage(std::move(T), std::move(Ts)...) { ASSERT(this->T == this->ST && Size == this->size()); // Sanity check. } /// Provides an untyped pointer for the constant value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return untyped pointer for the constant value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < Size /// \endcode - const void *pointerTo(const size_t Pos) const noexcept override { + const void *pointerTo(const token_size_t Pos) const noexcept override { ASSERT(Pos < Size); return TokenizedStorage::pointerTo(Pos); } /// Aborts the program! /// /// Since \c rosa::Message instances are supposed to be immutable, the /// non-const inherited function is overridden so that it aborts execution. - void *pointerTo(const size_t) noexcept override { + void *pointerTo(const token_size_t) noexcept override { ROSA_CRITICAL("Unallowed operation of rosa::LocalMessage"); } }; } // End namespace template Message::Message(const Type &, const Types &...) noexcept : T(TypeToken::type, typename std::decay::type...>::Value), Size(lengthOfToken(T)) { ASSERT(validToken(T) && lengthOfToken(T) == (1 + sizeof...(Types))); // Sanity check. - LOG_TRACE("Creating Message with Token(" + to_string(T) + ")"); + LOG_TRACE("Creating Message with Token(" + std::to_string(T) + ")"); } /// \note The implementation instantiates a private local template class /// \c LocalMessage. template message_t Message::create(const Type &T, const Types &... Ts) noexcept { return message_t(new LocalMessage(T, Ts...)); } /// \note The implementation instantiates a private local template class /// \c LocalMessage. template message_t Message::create(Type &&T, Types &&... Ts) noexcept { return message_t( new LocalMessage(std::move(T), std::move(Ts)...)); } template -bool Message::isTypeAt(const size_t Pos) const noexcept { +bool Message::isTypeAt(const token_size_t Pos) const noexcept { ASSERT(Pos < Size); Token TT = T; dropNOfToken(TT, Pos); return isHeadOfTokenTheSameType(TT); } template -const Type &Message::valueAt(const size_t Pos) const noexcept { +const Type &Message::valueAt(const token_size_t Pos) const noexcept { ASSERT(Pos < Size && isTypeAt(Pos)); return *static_cast(pointerTo(Pos)); } } // End namespace rosa #endif // ROSA_CORE_MESSAGE_HPP diff --git a/include/rosa/core/MessageMatcher.hpp b/include/rosa/core/MessageMatcher.hpp index 71bd69f..0092cb9 100644 --- a/include/rosa/core/MessageMatcher.hpp +++ b/include/rosa/core/MessageMatcher.hpp @@ -1,201 +1,201 @@ //===-- rosa/core/MessageMatcher.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/core/MessageMatcher.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Facilities for checking and matching types of values stored in /// \c rosa::Message instances. /// //===----------------------------------------------------------------------===// #ifndef ROSA_CORE_MESSAGEMATCHER_HPP #define ROSA_CORE_MESSAGEMATCHER_HPP #include "rosa/core/Message.hpp" #include namespace rosa { /// Provides features to type-check a \c rosa::Message instance and extract /// stored values from it into an \c std::tuple instance with matching type /// arguments. /// /// \tparam List \c rosa::TypeList to check the stored values against template struct MessageMatcher; /// Definition of \c rosa::MessageMatcher for non-empty lists of types, like /// \c rosa::Message itself. /// /// \tparam Type first mandatory type /// \tparam Types any further types template struct MessageMatcher> { /// \c rosa::Token associated to the given \c rosa::TypeList. static constexpr Token T = TypeToken::Value; /// Tells if the values stored in a \c rosa::Message instance are matching /// types given as \c rosa::TypeList, considering /// \c rosa::AtomConstant instead of \c rosa::AtomValue. /// /// \param Msg \c rosa::Message to match /// /// \return whether the types of values stored in \p Msg matches /// \c rosa::TypeList static inline bool doesStronglyMatch(const Message &Msg) noexcept; /// Gives a \c std::tuple with references to the values stored in a /// type-matching instance of \c rosa::Message. /// /// \param Msg \c rosa::Message to extract values from /// /// \return \c std::tuple with references to the values stored in \p Msg /// /// \pre Types of the values stored in \p Msg matches /// \c rosa::TypeList:\code /// doesStronglyMatch(Msg) /// \endcode static inline std::tuple extractedValues(const Message &Msg) noexcept; }; /// Turns a list of types into a \c rosa::TypeList for \c rosa::MessageMatcher. template using MsgMatcher = MessageMatcher>; /// Nested namespace with implementation for features of /// \c rosa::MessageMatcher, consider it private. namespace { /// \defgroup MessageMatcherImpl Implementation for rosa::MessageMatcher /// /// An implementation of type-checking and value extraction for /// \c rosa::MessageMatcher. /// ///@{ /// Template declaration of \c MessageMatcherImpl. /// /// \tparam List \c rosa::TypeList to match against template struct MessageMatcherImpl; /// Specialization for \c rosa::EmptyTypeList. template <> struct MessageMatcherImpl { static bool doesStronglyMatchFrom(const Message &Msg, - const size_t Pos) noexcept { + const token_size_t Pos) noexcept { // Matching EmptyTypeList only if reached the end of the stored types. return Pos == Msg.Size; } static std::tuple<> extractedValuesFrom(const Message &Msg, - const size_t Pos) noexcept { + const token_size_t Pos) noexcept { // It is valid to extract an empty list only if we reached the end of // stored values. ASSERT(doesStronglyMatchFrom(Msg, Pos)); return std::tie(); } }; /// Specialization for \c rosa::AtomValue in the head. template struct MessageMatcherImpl, Ts...>> { static bool doesHeadStronglyMatchAt(const Message &Msg, - const size_t Pos) noexcept { + const token_size_t Pos) noexcept { // Matching a \c rosa::AtomConstant in the head if there is a type stored at // \p Pos, the stored type is \c rosa::AtomValue, and the corresponding // value matches the \c rosa::AtomValue \p V. return Pos < Msg.Size && Msg.isTypeAt(Pos) && Msg.valueAt(Pos) == V; } static bool doesStronglyMatchFrom(const Message &Msg, - const size_t Pos) noexcept { + const token_size_t Pos) noexcept { // Matching a non-empty list if the head is matching and the rest of the // list is matching. return doesHeadStronglyMatchAt(Msg, Pos) && MessageMatcherImpl>::doesStronglyMatchFrom(Msg, Pos + 1); } static std::tuple &, const Ts &...> - extractedValuesFrom(const Message &Msg, const size_t Pos) noexcept { + extractedValuesFrom(const Message &Msg, const token_size_t Pos) noexcept { // Extracting for a non-empty list with a matching \c rosa::AtomConstant in // the head by getting the encoded \c rosa::AtomConstant and concatenating // it with values extracted for the rest of the list. ASSERT(doesHeadStronglyMatchAt(Msg, Pos)); return std::tuple_cat( std::tie(AtomConstant::Value), MessageMatcherImpl>::extractedValuesFrom(Msg, Pos + 1)); } }; /// Definition for the general case when a regular built-in type (not a /// \c rosa::AtomConstant) is in the head. template struct MessageMatcherImpl> { static bool doesHeadStronglyMatchAt(const Message &Msg, - const size_t Pos) noexcept { + const token_size_t Pos) noexcept { // Matching the head if there is a type stored at \p Pos, and the stored // type is \p T. return Pos < Msg.Size && Msg.isTypeAt(Pos); } static bool doesStronglyMatchFrom(const Message &Msg, - const size_t Pos) noexcept { + const token_size_t Pos) noexcept { // Matching a non-empty list if the head is matching and the rest of the // list is matching. return doesHeadStronglyMatchAt(Msg, Pos) && MessageMatcherImpl>::doesStronglyMatchFrom(Msg, Pos + 1); } static std::tuple - extractedValuesFrom(const Message &Msg, const size_t Pos) noexcept { + extractedValuesFrom(const Message &Msg, const token_size_t Pos) noexcept { // Extracting for a non-empty list with a matching head by getting the // value for the head and concatenating it with values extracted for the // rest of the list. ASSERT(doesHeadStronglyMatchAt(Msg, Pos)); return std::tuple_cat( std::tie(Msg.valueAt(Pos)), MessageMatcherImpl>::extractedValuesFrom(Msg, Pos + 1)); } }; ///@} } // End namespace template bool MessageMatcher>::doesStronglyMatch( const Message &Msg) noexcept { // \note Fail quick on \c rosa::MessageMatcher::T, then match against list // with squashed integers the way \c rosa::Token is generated. return T == Msg.T && MessageMatcherImpl>::Type>::doesStronglyMatchFrom(Msg, 0); } template std::tuple MessageMatcher>::extractedValues( const Message &Msg) noexcept { ASSERT(doesStronglyMatch(Msg)); // \note Match against a list with squashed integers as \c rosa::Token is // generated. return MessageMatcherImpl>::Type>::extractedValuesFrom(Msg, 0); } } // End namespace rosa #endif // ROSA_CORE_MESSAGEMATCHER_HPP diff --git a/include/rosa/core/SystemBase.hpp b/include/rosa/core/SystemBase.hpp index a5ca0d2..4b6720e 100644 --- a/include/rosa/core/SystemBase.hpp +++ b/include/rosa/core/SystemBase.hpp @@ -1,138 +1,138 @@ //===-- rosa/core/SystemBase.hpp -----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/core/SystemBase.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Base implementation of the \c rosa::System interface. /// //===----------------------------------------------------------------------===// #ifndef ROSA_CORE_SYSTEMBASE_HPP #define ROSA_CORE_SYSTEMBASE_HPP #include "rosa/core/System.hpp" #include namespace rosa { /// Base implementation of the \c rosa::System interface. /// /// This implementation provides only equality checking and *name* for /// \c rosa::System, identifiers for \c rosa::Unit instances, and marking the /// \c rosa::System cleaned for destruction. /// /// \note Actual implementations of \c rosa::System and derived interfaces are /// supposed to inherit from this implementation. class SystemBase : public System { protected: /// Creates an instance. /// /// \note Protected constructor restrict instantiation for subclasses. /// /// \param Name name of the new instance SystemBase(const std::string &Name) noexcept; public: /// Destroys \p this object. /// /// \pre \p this object is marked cleaned:\code /// isSystemCleaned() /// \endcode ~SystemBase(void); /// Tells whether \p this object is the same as \p Other. /// /// Two \c rosa::System instances are considered equal if they share a common /// \c rosa::SystemBase::Name member field. That should do among various /// subclasses. /// /// \param Other another \c rosa::System instance to compare to /// /// \return whether \p this object and \p Other is the same bool operator==(const System &Other) const noexcept override; protected: /// The textual name of \p this object implementing \c rosa::System. const std::string Name; private: /// Number of \c rosa::Unit instances constructed by \p this object. /// /// \note Should never be decremented! std::atomic UnitCount; /// Indicates that \p this object has been cleaned and is ready for /// destruction. /// /// The field is initialized as \c false and can be set by /// \c rosa::SystemBase::markCleaned. /// /// \note Subclasses must set the flag upon destructing their instances, which /// indicates to the destructor of the base-class that all the managed /// resources has been properly released. std::atomic SystemIsCleaned; public: /// Tells the name of \p this object /// /// \note The returned reference remains valid as long as \p this object is /// not destroyed. /// /// \return reference to \c rosa::SystemBase::Name const std::string &name(void) const noexcept override; protected: /// Tells the next unique identifier to be used for a newly created /// \c rosa::Unit. /// /// The functions takes the current value of the internal counter /// \c rosa::SystemBase::UnitCount and then increments it. /// /// \note This is the only function modifying /// \c rosa::SystemBase::UnitCount. /// /// \return \c rosa::id_t which is unique within the context of \p this /// object. id_t nextId(void) noexcept override; /// Tells if \p this object has been marked cleaned and is ready for /// destruction. /// /// \return if \p this object is marked clean. bool isSystemCleaned(void) const noexcept override; /// Marks \p this object cleaned by setting /// \c rosa::SystemBase::SystemIsCleaned. /// /// \note Can be called only once when the System does not have any live /// \c rosa::Unit instances. /// /// \pre \p this object has not yet been marked as cleaned and it has no /// \c rosa::Unit instances registered:\code /// !isSystemCleaned() && empty() /// \endcode /// /// \post \p this object is marked cleaned:\code /// isSystemCleaned() /// \endcode void markCleaned(void) noexcept override; /// Tells the number of \c rosa::Unit instances constructed in the context of /// \p this object so far, including those being already destroyed. /// /// \return current value of \c rosa::SystemBase::UnitCount that is the number /// of \c rosa::Unit instances created so far size_t numberOfConstructedUnits(void) const noexcept override; }; } // End namespace rosa -#endif // ROSA_LIB_CORE_SYSTEMBASE_HPP +#endif // ROSA_CORE_SYSTEMBASE_HPP diff --git a/include/rosa/core/Unit.h b/include/rosa/core/Unit.h index 377443a..47c42f6 100644 --- a/include/rosa/core/Unit.h +++ b/include/rosa/core/Unit.h @@ -1,114 +1,114 @@ //===-- rosa/core/Unit.h ----------------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/core/Unit.h /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017 /// /// \brief Declaration of \c rosa::Unit base-class. /// //===----------------------------------------------------------------------===// #ifndef ROSA_CORE_UNIT_H #define ROSA_CORE_UNIT_H #include "rosa/support/atom.hpp" #include "rosa/core/forward_declarations.h" #include #include namespace rosa { /// Base class for every entity in a \c rosa::System that has to be identified /// and traced. /// /// \note Life-cycle of \c rosa::Unit instances is supposed to be managed by the /// \c rosa::System owning the instance, do not create and destroy any /// \c rosa::Unit directly. class Unit { public: /// Identifies the *kind* of \p this object. /// /// \note Kind is dependent on the \c rosa::System owning \p this object. const AtomValue Kind; /// Unique identifier for \p this object. /// /// \note The unique identifier is assigned by the \c rosa::System owning /// \p this object upon creation. const id_t Id; /// Textual identifier of \p this object. /// /// \note Textual identifiers of \c rosa::Unit instances are not necessarily /// unique in their owning \c rosa::System. const std::string Name; protected: /// The \c rosa::System owning \p this object. System &S; public: - /// Full qualified name of \p this object. + /// Fully qualified name of \p this object. const std::string FullName; public: /// Creates a new instnace. /// /// \param Kind the kind of the new instance /// \param Id the unique identifier of the new instance /// \param Name the name of the new instance /// \param S \c rosa::System owning the new instance /// /// \pre \p Name is not empty:\code /// !Name.empty() /// \endcode Unit(const AtomValue Kind, const id_t Id, const std::string &Name, System &S) noexcept; /// No copying and moving of \c rosa::Unit instances is possible. ///@{ Unit(const Unit &) = delete; Unit(Unit &&) = delete; Unit &operator=(const Unit &) = delete; Unit &operator=(Unit &&) = delete; ///@} /// Destroys \p this object. virtual ~Unit(void); /// Dumps \p this object into a \c std::string for tracing purposes. /// /// Subclasses are supposed to override this function. /// /// \return \c std::string representing the state of \p this object virtual std::string dump(void) const noexcept; protected: /// Returns a reference to the \c rosa::System owning \p this object. /// /// \note Subclasses may override the function to return a reference of a /// subtype of \c rosa::System. /// /// \return reference of \c rosa::Unit::S virtual System &system() const noexcept; }; /// Dumps a \c rosa::Unit instance to a given \c std::ostream. /// /// \param [in,out] OS output stream to dump to /// \param U \c rosa::Unit to dump /// /// \return \p OS after dumping \p U to it std::ostream &operator<<(std::ostream &OS, const Unit &U); } // End namespace rosa #endif // ROSA_CORE_UNIT_H diff --git a/include/rosa/deluxe/DeluxeAgent.hpp b/include/rosa/deluxe/DeluxeAgent.hpp old mode 100755 new mode 100644 index 0e83a7c..7a94a57 --- a/include/rosa/deluxe/DeluxeAgent.hpp +++ b/include/rosa/deluxe/DeluxeAgent.hpp @@ -1,673 +1,1479 @@ //===-- rosa/deluxe/DeluxeAgent.hpp -----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeAgent.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Specialization of \c rosa::Agent for *agent* role of the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXEAGENT_HPP #define ROSA_DELUXE_DELUXEAGENT_HPP #include "rosa/core/Agent.hpp" #include "rosa/deluxe/DeluxeAtoms.hpp" +#include "rosa/deluxe/DeluxeExecutionPolicy.h" +#include "rosa/deluxe/DeluxeTuple.hpp" #include /// Local helper macros to deal with built-in types. /// ///@{ /// Creates function name for member functions in \c rosa::deluxe::DeluxeAgent. /// /// \param N name suffix to use -#define DAHANDLERNAME(N) handleSlave_##N +#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 DAHANDLERDEFN(T, N) \ - void DAHANDLERNAME(N)(atoms::Slave, id_t SlaveId, T Value) noexcept { \ - saveInput(SlaveId, Value); \ +#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 DAHANDLERDEFN with identical arguments. +/// Convenience macro for \c DASLAVEHANDLERDEFN with identical arguments. /// -/// \see \c DAHANDLERDEFN +/// \see \c DASLAVEHANDLERDEFN /// -/// This macro can be used instead of \c DAHANDLERDEFN if the actual value of -/// \p T can be used as a part of a valid identifier. +/// 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 DAHANDLERDEF(T) DAHANDLERDEFN(T, T) +#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 DAHANDLERDEFN. +/// \c DASLAVEHANDLERDEFN. /// /// Used in the constructor of \c rosa::deluxe::DeluxeAgent to initialize super -/// class \c rosa::Agent with member function defined by \c DAHANDLERDEFN. +/// class \c rosa::Agent with member function defined by \c DASLAVEHANDLERDEFN. /// -/// \see \c DAHANDLERDEFN, \c THISMEMBER +/// \see \c DASLAVEHANDLERDEFN, \c THISMEMBER /// /// \param N name suffix for the function identifier -#define DAHANDLERREF(N) THISMEMBER(DAHANDLERNAME(N)) +#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 All input-related container objects have a size matching -/// \c rosa::deluxe::DeluxeAgent::NumberOfInputs, thus having a corresponding -/// entry for each input. Types of input values are consistent throughout all -/// the input-related containers. No *slave* is registered at more than one -/// input position. *Slave* registrations and corresponding reverse lookup +/// \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; -public: - /// Template alias for function objects used to process input and generate - /// output for \c rosa::deluxe::DeluxeAgent. - /// - /// The output generated by the function is optional as an agent may decide - /// not to output anything at some situation. - /// - /// \note The function used for \c D is to be \c noexcept. - /// - /// \tparam T type of output - /// \tparam As types of input values - template - using D = std::function(std::pair...)>; + /// The \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 type of values \p this object sends to its *master*. + /// 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 TypeNumber OutputType; + const Token OutputType; /// Number of inputs processed by \p this object. const size_t NumberOfInputs; -private: + /// 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::TypeNumber values stored here match the corresponding - /// values in \c rosa::deluxe::DeluxeAgent::InputValues. + /// \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 type in the \c std::vector indicates which - /// argument of \p this object's processing function it belongs to. See also - /// \c rosa::deluxe::DeluxeAgent::D. - const std::vector InputTypes; + /// \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::D. + /// \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::deluxe::DeluxeAgent::InputTypes. + /// \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 argument of \p this object's processing function it is. - /// See also \c rosa::deluxe::DeluxeAgent::D. + /// indicates which element of the tuple of which argument of \p this object's + /// processing function it is. See also \c + /// rosa::deluxe::DeluxeAgent::DeluxeAgent. const std::unique_ptr InputValues; + /// Indicates which element of the master-input is expected from the *master*. + /// + /// The *master* is supposed to send one \c rosa::deluxe::DeluxeTuple value + /// element by element in their order of definition. This member field tells + /// the element at which position should be received next. + /// + /// \p this object is supposed to be triggered only when a complete + /// master-input has been received, that is the field should hold the value + /// `0`. + /// + /// \see \c rosa::deluxe::DeluxeAgent::handleTrigger + /// \c rosa::deluxe::DeluxeAgent::saveMasterInput + token_size_t MasterInputNextPos; + + /// Indicates whether the input value from the *master* has been changed since + /// the last trigger received from the system. + /// + /// The flag is reset to \c false upon handling a trigger and then set to \c + /// true by \c rosa::deluxe::DeluxeAgent::saveMasterInput when storig a new + /// input value in \c rosa::deluxe::DeluxeAgent::MasterInputValue. + bool MasterInputChanged; + + /// Stores the actual input value from *master*. + /// + /// \note The type of the stored value matches the types indicated by \c + /// rosa::deluxe::DeluxeAgent::MasterInputType. + const std::unique_ptr MasterInputValue; + + /// Types of output values produced by \p this object for its *slaves*. + /// + /// That is the types of values \p this object sends to its *slaves* in a \c + /// rosa::deluxe::DeluxeTuple. + /// + /// \note The position of a type in the \c std::vector indicates which + /// *slave* of \p this object the type belongs to. See also + /// \c rosa::deluxe::DeluxeAgent::DeluxeAgent. + const std::vector MasterOutputTypes; + /// Alias for function objects used as trigger handler for /// \c rosa::deluxe::DeluxeAgent. /// /// \note The function used for \c H is to be \c noexcept. /// /// \see \c rosa::deluxe::DeluxeAgent::FP using H = std::function; /// Handles trigger from the system. /// - /// The actual function processing *slave* inputs and generating optional - /// output to *master* is captured in a lambda expression that is in turn - /// wrapped in a \c std::function object. The lambda expression calls the - /// processing function with the actual input data and sends its result -- if - /// any -- to *master* by calling \c rosa::deluxe::DeluxeAgent::sendToMaster. - /// Also, all the flags stored in \c rose::deluxe::DeluxeAgent::InputChanged - /// are reset when the current input values are processed. The function - /// \c rosa::deluxe::DeluxeAgent::handleTrigger needs only to call the + /// 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::triggerHandlerFromProcessingFunction + /// \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::D. + /// \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 whether types \p As... match the input types of \p this object. + /// Tells the unique identifier of the *master* of \p this object, if any + /// registered. /// - /// \tparam As types to match against values in - /// \c rosa::deluxe::DeluxeAgent::InputTypes + /// \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. /// - /// \return if types \p As... match \c rosa::TypeNumber values stored in + /// \tparam As \c rosa::TypeList containing types to match against values in /// \c rosa::deluxe::DeluxeAgent::InputTypes - template bool inputTypesMatch(void) const noexcept; + /// + /// \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 The type arguments \p As... match the input types of \p this object - /// and the provided indices \p S0... constitute a proper sequence for - /// accessing input values and their change flags: \code - /// inputTypesMatch() && sizeof...(As) == sizeof...(S0) + /// \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 output and input types of - /// \p this object with actual arguments provided in a \c std::tuple. + /// 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 Optional invokeWithTuple(D F, - std::tuple...> Args, - Seq) noexcept; + 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 a processing function into a trigger handler. + /// 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 /// - /// \pre Template arguments \p T and \p As... match the corresponding - /// types \p this object was created with: \code - /// OutputType == TypeNumberOf::Value && inputTypesMatch() + /// \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 triggerHandlerFromProcessingFunction(D &&F) noexcept; + 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*. /// - /// \todo Enforce F does not potentially throw exception. + /// 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 T and \p As... - /// is not a built-in type. + /// \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 F function to process input values and generate output with - /// - /// \pre Statically, all of the type arguments \p T and \p As... is a - /// built-in type: \code - /// TypeListSubsetOf, BuiltinTypes>::Value - /// \endcode Dynamically, the instance is created as of kind - /// \c rosa::deluxe::atoms::AgentKind: \code + /// \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 - template , BuiltinTypes>::Value>> - DeluxeAgent(const AtomValue Kind, const id_t Id, const std::string &Name, - MessagingSystem &S, D &&F) noexcept; + TypeListAllDeluxeTuple>::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 type of values consumed from the *slave* at a position. + /// 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 by its - /// *slave* registered at position \p Pos. + /// 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::TypeNumber representing the type of values consumed from + /// \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 - TypeNumber inputType(const size_t Pos) const noexcept; + 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: + /// 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]) || + /// 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])) + /// 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. /// - /// \tparam T type of the value to send + /// 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 /// - /// \pre \p T matches \c rosa::deluxe::DeluxeiAgent::OutputType: \code - /// OutputType == TypeNumberOf::Value + /// \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 T &Value) noexcept; + 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 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 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 and the input from it is - /// expected to be of type \p T: \code + /// \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() && - /// InputTypes[SlaveIds.find(Id)->second] == TypeNumberOf::Value + /// Pos == InputNextPos[SlaveIds.find(Id)->second] && + /// typeAtPositionOfToken(InputTypes[SlaveIds.find(Id)->second], Pos) == + /// TypeNumberOf::Value /// \endcode - template void saveInput(id_t Id, T Value) noexcept; + template + void saveInput(id_t Id, token_size_t Pos, T Value) noexcept; - /// \defgroup DeluxeAgentInputHandlers Input handlers of rosa::deluxe::DeluxeAgent + /// 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. 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. + /// 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 DAHANDLERDEF. + /// \note The member functions in this group are defined by \c + /// DASLAVEHANDLERDEF. /// /// \note Keep these definitions in sync with \c rosa::BuiltinTypes. /// ///@{ - DAHANDLERDEF(AtomValue) - DAHANDLERDEF(int16_t) - DAHANDLERDEF(int32_t) - DAHANDLERDEF(int64_t) - DAHANDLERDEF(int8_t) - DAHANDLERDEFN(long double, long_double) - DAHANDLERDEFN(std::string, std__string) - DAHANDLERDEF(uint16_t) - DAHANDLERDEF(uint32_t) - DAHANDLERDEF(uint64_t) - DAHANDLERDEF(uint8_t) - DAHANDLERDEF(unit_t) - DAHANDLERDEF(bool) - DAHANDLERDEF(double) - DAHANDLERDEF(float) + 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::inputTypesMatch, consider it private. +/// 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 { -/// Template \c struct whose specializations provide a recursive implementation -/// for \c rosa::deluxe::DeluxeAgent::inputTypesMatch. +/// Calculates storage offsets for values of \p Ts... stored in a \c +/// rosa::TokenizedStorage. /// -/// \note Matching a list of types \p As... against a \c std::vector of -/// \c rosa::TypeNumber values, \c InputTypes, like \code -/// bool match = InputTypesMatchImpl::f(InputTypes, 0); +/// \note Utilized by \c rosa::deluxe::DeluxeAgnet::DeluxeAgent to initialize \c +/// rosa::deluxe::DeluxeAgent::InputStorageOffsets. +/// +/// \tparam Ts types whose offsets to calculate +/// \tparam S0 indices for referring to positions in \p Ts... +/// +/// \note Instantiation fails if any of the type arguments \p Ts... is not an +/// instance of \c rosa::deluxe::DeluxeTuple. +/// +/// \note The only argument provides indices statically as template +/// arguments \p S0..., so its actual value is ignored. +/// +/// \return \c std::vector containing the calculated offsets +/// +/// \pre Statically, all the type arguments \p Ts... are instances of \c +/// rosa::deluxe::DeluxeTuple and the indices match the types: \code +/// TypeListAllDeluxeTuple>::Value && +/// sizeof...(Ts) == sizeof...(S0) /// \endcode +template < + typename... Ts, size_t... S0, + typename = std::enable_if_t>::Value>> +static std::vector storageOffsets(Seq) noexcept { + STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent arguments"); + std::vector Offsets(sizeof...(Ts)); + // Do nothing for no types. + if constexpr (sizeof...(Ts) != 0) { + Offsets[0] = 0; // The offset of the very first value is always `0`. + // Calculate further offsets... + (((S0 != sizeof...(Ts) - 1) && + (Offsets[S0 + 1] = Offsets[S0] + Ts::Length)), + ...); + } + return Offsets; +} + +/// Template \c struct whose specializations provide a recursive implementation +/// for \c TypesMatchList. /// /// \tparam As types to match -template struct InputTypesMatchImpl; +template struct TypesMatchImpl; -/// Template specialization for the general case, when at least one type is to -/// be matched. +/// 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 A first type to match +/// \tparam Ts types of elements in the \c rosa::deluxe::DeluxeTuple to match /// \tparam As further types to match -template struct InputTypesMatchImpl { - /// Tells whether types \p A, \p As... match \c rosa::TypeNumber values - /// stored in \p InputTypes starting at position \p Pos. +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 \p A against \c rosa::TypeNumber at position \p Pos of \p - /// InputTypes, then further types \p As.. are matched recursively starting - /// at position \c (Pos + 1). - /// - /// \param InputTypes container of \c rosa::TypeNumber values to match - /// types against - /// \param Pos position in \p InputTypes to start matching at - /// - /// \return if types \p A, \p As... match \c rosa::TypeNumber values stored - /// in \p InputTypes starting at position \p Pos - static bool f(const std::vector &InputTypes, - size_t Pos) noexcept { - return Pos < InputTypes.size() && - TypeNumberOf::Value == InputTypes[Pos] && - InputTypesMatchImpl::f(InputTypes, Pos + 1); + /// 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 InputTypesMatchImpl<> { - /// Tells whether \p Pos is the number of values stored in \p InputTypes. +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 matchi because all the + /// 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 InputTypes. That is true if \p Pos points exactly to the end of - /// \p InputTypes. + /// \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. /// - /// \param InputTypes container of \c rosa::TypeNumber values to match - /// types against - /// \param Pos position in \p InputTypes to start matching at + /// The function unwraps the types from \c rosa::TypeList and utilizes \c + /// TypesMatchImpl to do the check. /// - /// \return if \p Pos is the number of values stored in \p InputTypes - static bool f(const std::vector &InputTypes, - size_t Pos) noexcept { - return Pos == InputTypes.size(); + /// \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 +template bool DeluxeAgent::inputTypesMatch(void) const noexcept { - return InputTypesMatchImpl::f(InputTypes, 0); + return TypesMatchList::f(InputTypes); +} + +template +bool DeluxeAgent::masterOutputTypesMatch(void) const noexcept { + return TypesMatchList::f(MasterOutputTypes); +} + +template +DeluxeTuple DeluxeAgent::prepareInputValueAtPos(TypeList, + Seq) const + noexcept { + using T = DeluxeTuple; + STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent type arguments"); + ASSERT(inv() && Pos < NumberOfInputs && T::TT == InputTypes[Pos]); + + const token_size_t StorageOffset = InputStorageOffsets[Pos]; + + // The below should hold because of the above, just leave it for sanity check. + ASSERT((true && ... && + (static_cast(static_cast(S0)) == S0))); + + // Get all elements of the tuple in a fold expression. + return T(*static_cast(InputValues->pointerTo( + static_cast(StorageOffset + S0)))...); } template std::tuple...> - DeluxeAgent::prepareCurrentInputs(Seq) const noexcept { - // Need to indirectly reference \c rosa::deluxe::DeluxeAgent::inputTypesMatch - // inside \c ASSERT because of the comma in its template argument list. - auto MFP = &DeluxeAgent::inputTypesMatch; - ASSERT(inv() && (this->*MFP)() && sizeof...(As) == sizeof...(S0)); - - return std::make_tuple( - std::make_pair(*static_cast(InputValues->pointerTo(S0)), - InputChanged[S0])...); +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 -Optional DeluxeAgent::invokeWithTuple( - D F, - std::tuple...> Args, - Seq) noexcept { - ASSERT(sizeof...(As) == sizeof...(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 -DeluxeAgent::H -DeluxeAgent::triggerHandlerFromProcessingFunction(D &&F) noexcept { - // Need to indirectly reference \c rosa::deluxe::DeluxeAgent::inputTypesMatch - // inside \c ASSERT because of the comma in its template argument list. - auto MFP = &DeluxeAgent::inputTypesMatch; - ASSERT(OutputType == TypeNumberOf::Value && (this->*MFP)()); - - return [ this, F ]() noexcept { - using Indices = typename GenSeq::Type; - auto Args = prepareCurrentInputs(Indices()); - std::fill(InputChanged.begin(), InputChanged.end(), false); - Optional R = invokeWithTuple(F, Args, Indices()); - if (R) { - sendToMaster(*R); +template +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, - D &&F) noexcept +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), - DAHANDLERREF(AtomValue), DAHANDLERREF(int16_t), - DAHANDLERREF(int32_t), DAHANDLERREF(int64_t), DAHANDLERREF(int8_t), - DAHANDLERREF(long_double), DAHANDLERREF(std__string), - DAHANDLERREF(uint16_t), DAHANDLERREF(uint32_t), - DAHANDLERREF(uint64_t), DAHANDLERREF(uint8_t), DAHANDLERREF(unit_t), - DAHANDLERREF(bool), DAHANDLERREF(double), DAHANDLERREF(float)), - OutputType(TypeNumberOf::Value), - NumberOfInputs(sizeof...(As)), - InputTypes({TypeNumberOf::Value...}), - InputChanged(NumberOfInputs, false), - InputValues(new TokenizedStorage()), - FP(triggerHandlerFromProcessingFunction(std::move(F))), + DASLAVEHANDLERREF(AtomValue), DASLAVEHANDLERREF(int16_t), + DASLAVEHANDLERREF(int32_t), DASLAVEHANDLERREF(int64_t), + DASLAVEHANDLERREF(int8_t), DASLAVEHANDLERREF(long_double), + DASLAVEHANDLERREF(std__string), DASLAVEHANDLERREF(uint16_t), + DASLAVEHANDLERREF(uint32_t), DASLAVEHANDLERREF(uint64_t), + DASLAVEHANDLERREF(uint8_t), DASLAVEHANDLERREF(unit_t), + DASLAVEHANDLERREF(bool), DASLAVEHANDLERREF(double), + DASLAVEHANDLERREF(float), DAMASTERHANDLERREF(AtomValue), + DAMASTERHANDLERREF(int16_t), DAMASTERHANDLERREF(int32_t), + DAMASTERHANDLERREF(int64_t), DAMASTERHANDLERREF(int8_t), + DAMASTERHANDLERREF(long_double), DAMASTERHANDLERREF(std__string), + DAMASTERHANDLERREF(uint16_t), DAMASTERHANDLERREF(uint32_t), + DAMASTERHANDLERREF(uint64_t), DAMASTERHANDLERREF(uint8_t), + DAMASTERHANDLERREF(unit_t), DAMASTERHANDLERREF(bool), + DAMASTERHANDLERREF(double), DAMASTERHANDLERREF(float)), + ExecutionPolicy(DeluxeExecutionPolicy::decimation(1)), OutputType(T::TT), + NumberOfInputs(sizeof...(As)), MasterInputType(MT::TT), + NumberOfMasterOutputs(NumberOfInputs), InputTypes({As::TT...}), + InputNextPos(NumberOfInputs, 0), InputChanged(NumberOfInputs, false), + InputStorageOffsets(storageOffsets(seq_t())), + InputValues(new typename TokenizedStorageForTypeList< + typename TypeListUnwrapDeluxeTuple>::Type>:: + Type()), + MasterInputNextPos(0), MasterInputChanged(false), + MasterInputValue(new typename TokenizedStorageForTypeList< + typename UnwrapDeluxeTuple::Type>::Type()), + MasterOutputTypes({Ts::TT...}), + FP(triggerHandlerFromProcessingFunctions(std::move(MF), std::move(F), + seq_t())), Slaves(NumberOfInputs) { ASSERT(Kind == atoms::AgentKind); - LOG_TRACE("DeluxeAgent is created."); + LOG_TRACE_STREAM << "DeluxeAgent " << FullName << " is created." << std::endl; ASSERT(inv()); } -template -void DeluxeAgent::sendToMaster(const T &Value) noexcept { - ASSERT(inv() && OutputType == TypeNumberOf::Value); +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) { - Master->sendMessage(Message::create(atoms::Slave::Value, Id, Value)); + // 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, T Value) noexcept { +template +void DeluxeAgent::saveInput(id_t Id, token_size_t Pos, T Value) noexcept { ASSERT(inv() && SlaveIds.find(Id) != SlaveIds.end() && - InputTypes[SlaveIds.find(Id)->second] == TypeNumberOf::Value); + Pos == InputNextPos[SlaveIds.find(Id)->second] && + typeAtPositionOfToken(InputTypes[SlaveIds.find(Id)->second], Pos) == + TypeNumberOf::Value); + + size_t SlavePos = SlaveIds.at(Id); + + LOG_TRACE_STREAM << "DeluxeAgent " << FullName << "(" << Id + << ") saves value from slave at position " << SlavePos + << ": (" << static_cast(Pos) << ") " << Value + << std::endl; + + // Save value. + size_t StoragePos = (size_t)InputStorageOffsets[SlavePos] + Pos; + // This assert must hold if \p this object was successfully constructed. + ASSERT(static_cast(static_cast(StoragePos)) == + StoragePos); + *static_cast( + InputValues->pointerTo(static_cast(StoragePos))) = Value; + + // Update position of next value. + if (++InputNextPos[SlavePos] == lengthOfToken(InputTypes[SlavePos])) { + InputNextPos[SlavePos] = 0; + } - size_t Pos = SlaveIds.at(Id); - *static_cast(InputValues->pointerTo(Pos)) = Value; - InputChanged[Pos] = true; + // 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 DAHANDLEREF -#undef DAHANDLEDEF -#undef DAHANDLEDEFN -#undef DAHANDLENAME +#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/DeluxeAtoms.hpp b/include/rosa/deluxe/DeluxeAtoms.hpp index a2aab57..70963b2 100755 --- a/include/rosa/deluxe/DeluxeAtoms.hpp +++ b/include/rosa/deluxe/DeluxeAtoms.hpp @@ -1,61 +1,59 @@ //===-- rosa/deluxe/DeluxeAtoms.hpp -----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeAtoms.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Definition of \c rosa::AtomValue values and \c rosa::AtomConstant /// types for the implementation of the *deluxe interface*. /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXEATOMS_HPP #define ROSA_DELUXE_DELUXEATOMS_HPP #include "rosa/support/atom.hpp" namespace rosa { namespace deluxe { /// Contains some definitions used in the implementation of the *deluxe /// interface* to denote various roles and events /// /// \see \c rosa::deluxe::DeluxeContext /// /// \note Do not apply `using namespace` to this namespace as that may result in /// some identifiers in the original namespace being hidden by those of /// \c rosa::deluxe::atoms. namespace atoms { /// Value to be used as the *kind* of \c rosa::deluxe::DeluxeSensor. /// /// \see \c rosa::Unit::Kind constexpr AtomValue SensorKind = atom("dl_sensor"); /// Value to be used as the *kind* of \c rosa::deluxe::DeluxeAgent. /// /// \see \c rosa::Unit::Kind constexpr AtomValue AgentKind = atom("dl_agent"); /// Type alias denoting system trigger messages. using Trigger = AtomConstant; /// Type alias denoting messages from a slave. using Slave = AtomConstant; /// Type alias denoting messages from a master. -/// -/// \note This one is not used at the moment. using Master = AtomConstant; } // End namespace atoms } // End namespace deluxe } // End namespace rosa #endif // ROSA_DELUXE_DELUXEATOMS_HPP diff --git a/include/rosa/deluxe/DeluxeContext.hpp b/include/rosa/deluxe/DeluxeContext.hpp old mode 100755 new mode 100644 index ed34f14..bb91eeb --- a/include/rosa/deluxe/DeluxeContext.hpp +++ b/include/rosa/deluxe/DeluxeContext.hpp @@ -1,365 +1,985 @@ //===-- 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 + 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 /// - /// \todo The current implementation is an intermediate state, handles - /// built-in types and std::tuple, but the latter with with one element only. - /// Fix it when merging with DeluxeTuple. + /// \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 - AgentHandle createSensor(const std::string &Name, - DeluxeSensor::D &&F = [](void) { - return T(); - }) noexcept; + 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 - AgentHandle createAgent(const std::string &Name, - DeluxeAgent::D &&F) noexcept; + 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; - /// Connectes a *sensor* to an *agent* in the context of \p this object. + /// 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 + /// \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 than the output type of \p Sensor + /// `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 + /// \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 + /// `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. + /// 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; - /// Registers a stream providing values for a *sensor* during simulation. +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 of values \p Sensor is operating on, always use + /// default! + /// + /// \note Instantiation fails if type argument \p T is neither a built-in type + /// nor an instance of \c rosa::deluxe::DeluxeTuple with at least one element. /// /// \param Sensor the *sensor* to register values for /// \param Start provides values for \p Sensor /// \param End denotes the end of stream of values - /// \param Default value to be used when input stream is depleted during - /// simulation + /// \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 + /// `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)>> 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. + /// 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. + /// \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 -AgentHandle DeluxeContext::createSensor(const std::string &Name, - DeluxeSensor::D &&F) noexcept { - AgentHandle H = System->createSensor(Name, std::move(F)); - DeluxeUnits.emplace(H); - return H; +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>()...); } -template -AgentHandle DeluxeContext::createAgent(const std::string &Name, - DeluxeAgent::D &&F) noexcept { - AgentHandle H = System->createAgent(Name, std::move(F)); +} // 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; } -/// Anonymous namespace for helper facilities, consider it private. -namespace { - -///\defgroup UnwrapSensorType Type helper for implementing \c -/// rosa::deluxe::DeluxeContext::registerSensorValue() -/// -///@{ +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 declaration. -/// -/// \tparam T type to obtain matching sensor type for -/// -/// Obtain a sensor type for a type \p T by: \code -/// typename UnwrapSensorType::Type -/// \endcode -template struct UnwrapSensorType; +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"); + } +} -/// Implementation for the general case. -template struct UnwrapSensorType { - /// The type to use is the \p T itself. - using Type = T; -}; +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 specialization for \c std::tuple. -template struct UnwrapSensorType> { - /// The type to use is the type of the first element of the tuple - using Type = typename std::tuple_element<0, std::tuple>::type; -}; +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"); + } +} -/// Convenience template alias to use \c UnwrapSensorType easily. -template -using sensor_t = typename UnwrapSensorType::Type; +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"); + } +} -} // End namespace +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 +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"); // \note This constexpr variable is defined in the lambda below for MSVC. // Keep that definition in sync with this one. constexpr bool isBuiltin = TypeListContains::Value; if constexpr (!isBuiltin) { // T must be a std::tuple. STATIC_ASSERT(std::tuple_size::value == 1, "Wrong tuple type"); STATIC_ASSERT( (TypeListContains::type>::Value), "Wrong element type in tuple"); } using TT = sensor_t; // Make sure preconditions are met. if (!System->isDeluxeSensor(Sensor)) { DCRETERROR(ErrorCode::NotSensor); } auto S = System->getDeluxeSensor(Sensor); ASSERT(S); // Sanity check. +<<<<<<< HEAD if (S->OutputType != TypeNumberOf::Value) { DCRETERROR(ErrorCode::TypeMismatch); } else if (S->simulationDataSourceIsSet()) { DCRETERROR(ErrorCode::AlreadyHasValueStream); } // Register input stream. // \note Need to capture parameters by value so having local copies. S->registerSimulationDataSource( DeluxeSensor::D([=](void) mutable noexcept { #ifdef ROSA_WINDOWS // MSVC has problem propagating constexpr into the lambda; repeat it. // Keep this definition in sync with the original above. constexpr bool isBuiltin = TypeListContains::Value; #endif // defined ROSA_WINDOWS if (Start != End) { TT Value; if constexpr (isBuiltin) { Value = *Start; } else { Value = std::get<0>(*Start); } ++Start; LOG_TRACE_STREAM << "Reading next value for sensor '" << S->FullName << "': " << Value << '\n'; return Value; } else { TT Value; if constexpr (isBuiltin) { Value = Default; } else { Value = std::get<0>(Default); } LOG_TRACE_STREAM << "Providing default value for sensor '" << S->FullName << "': " << Value << '\n'; return Value; } })); +======= + 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) { + DCRETERROR(ErrorCode::TypeMismatch); + } + + // Register input stream. + // \note Need to capture parameters by value so having local copies. + S->registerSimulationDataSource( + std::function([=](void) mutable noexcept->T { + if (Start != End) { + LOG_TRACE_STREAM << "Reading next value for sensor '" << S->FullName + << "': " << *Start << '\n'; + return *Start++; + } else { + LOG_TRACE_STREAM << "Providing default value for sensor '" + << S->FullName << "': " << Default << '\n'; + return Default; + } + })); + + } else { + ASSERT(false && "Unexpected type argument"); + } + +>>>>>>> master 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/DeluxeExecutionPolicy.h b/include/rosa/deluxe/DeluxeExecutionPolicy.h new file mode 100644 index 0000000..caaad92 --- /dev/null +++ b/include/rosa/deluxe/DeluxeExecutionPolicy.h @@ -0,0 +1,195 @@ +//===-- rosa/deluxe/DeluxeExecutionPolicy.h ---------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/deluxe/DeluxeExecutionPolicy.h +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Public interface of *execution policies* in the *deluxe interface*. +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_DELUXE_DELUXEEXECUTIONPOLICY_H +#define ROSA_DELUXE_DELUXEEXECUTIONPOLICY_H + +#include "rosa/core/AgentHandle.hpp" + +#include +#include +#include +#include + +namespace rosa { +namespace deluxe { + +// Forward declaration of DeluxeSystem. Do not include the corresponding header +// in this file because of cyclic dependency. +class DeluxeSystem; + +/// *Execution policy* that controls how *agents* and *sensors* call their +/// processing functions. +/// +/// An *execution policy* can be applied to a deluxe *unit* only if \c +/// deluxe::rosa::DeluxeExecutionPolicy::canHandle() allows it. Each deluxe +/// *unit* must have a compatible *execution policy* associated to it, and the +/// *unit* queries \c rosa::deluxe::DeluxeExecutionPolicy::shouldProcess() on each +/// triggering and calls its processing funtion only if it is allowed by the +/// *execution policy*. +/// +/// \see rosa::deluxe::DeluxeExecutionPolicy::decimation() +/// \see rosa::deluxe::DeluxeExecutionPolicy::awaitAll() +/// \see rosa::deluxe::DeluxeExecutionPolicy::awaitAny() +/// +/// \todo Extend the interface with query functions about what kind of +/// execution policy is behind the interface. This can be done in relation +/// to the existing factory functions; for example, if the actual object is +/// decimation and with what rate. +class DeluxeExecutionPolicy { +protected: + + /// Protected constructor, only implementations can instantiate the class. + DeluxeExecutionPolicy(void) noexcept = default; + +private: + /// No instance can be copy-constructed, move-constructed, copied, and moved. + /// + ///@{ + DeluxeExecutionPolicy(const DeluxeExecutionPolicy &) = delete; + DeluxeExecutionPolicy(DeluxeExecutionPolicy &&) = delete; + DeluxeExecutionPolicy &operator=(const DeluxeExecutionPolicy &) = delete; + DeluxeExecutionPolicy &operator=(DeluxeExecutionPolicy &&) = delete; + ///@} + +public: + /// Virtual destructor for subclasses. + virtual ~DeluxeExecutionPolicy(void) noexcept = default; + + /// Creates an *execution policy* that allows execution with decimation of + /// triggering. + /// + //// *Decimation* can handle both *agents* and *sensors*. + /// Processing functions are executed only on every \p D th + /// triggering. In the case of *sensors* in simulation, the simulation data + /// source is read on each triggering as it provides values with respect to + /// the highest execution frequency, but output is generated by the *sensor* + /// only on every \p D th triggering. + /// + /// \note A rate of \c 0 is allowed as actual argument and is treated as rate + /// \c 1 (i.e., execute processing functions on each triggering). + /// + /// \param D the rate of *decimation* + /// + /// \return an *execution policy* implementing *decimation* with rate \p D + static std::unique_ptr decimation(const size_t D); + + /// Creates an *execution policy* that allows execution only if all defined + /// *slave* positions has new input. + /// + /// *Await all* can handle only *agents* and only if the particular *agent* + /// has at least as many *slave* positions as the largest position defined in + /// \p S. Processing functions are executed only if new input has been + /// received for all defined *slave* positions. + /// + /// \param S set of *slave* positions to await input from + /// + /// \return an *execution policy* implementing *awaiting all* input from set + /// \p S + static std::unique_ptr + awaitAll(const std::set &S); + + /// Creates an *execution policy* that allows execution if any of the defined + /// *slave* positions has new input. + /// + /// *Await any* can handle only *agents* and only if the particular *agent* + /// has at least as many *slave* positions as the largest position defined in + /// \p S. Processing functions are executed if new input has been received for + /// any of the defined *slave* positions. + /// + /// \param S set of *slave* positions to await input from + /// + /// \return an *execution policy* implementing *awaiting any* input from set + /// \p S + static std::unique_ptr + awaitAny(const std::set &S); + + /// Tells if \p this object can handle the deluxe *unit* referred by \p H. + /// + /// The *execution policy* implemented by \p this object is applicable to the + /// given deluxe *unit* referred by \p H only if the function returns \c true. + /// + /// \param H reference to the *unit* to check + /// \param S the system owning the *unit* referred by \p H + /// + /// \return if \p this object can handle the *unit* referred by \p H + virtual bool canHandle(const AgentHandle H, const DeluxeSystem &S) const + noexcept = 0; + + /// Tells if processing function should be executed on the current triggering. + /// + /// The function is to be called on each triggering of the deluxe *unit*. + /// Decision about execution of processing function is done by \p this object + /// according to the implemented *execution policy*. + /// + /// \param InputChanged flags indicating whether new input has been received + /// at *slave* positions + /// + /// \return if to execute processing function + virtual bool shouldProcess(const std::vector &InputChanged) noexcept = 0; + + /// Dumps \p this object into textual representation. + /// + /// \return textual representation of \p this object + virtual std::string dump(void) const noexcept = 0; + +protected: + /// Tells whether the *unit* referred by \p H is a \c + /// rosa::deluxe::DeluxeAgent. + /// + /// \param H reference to the *unit* to check + /// \param S the system owning the *unit* referred by \p H + /// + /// \return if the *unit* referred by \p H is a \c rosa::deluxe::DeluxeAgent + bool isDeluxeAgent(const AgentHandle H, const DeluxeSystem &S) const noexcept; + + /// Tells the number of inputs handled by the *unit* referred by \p H. + /// + /// If \p H refers to a \c rosa::deluxe::DeluxeAgent, the function returns the + /// number of inputs (i.e., *slave* positions) of the *agent*. Otherwise, the + /// function returns \c 0. + /// + /// \param H reference to the *unit* to check + /// \param S the system owning the *unit* referred by \p H + /// + /// \return the number of inputs handled by the *unit* referred by \p H + size_t numberOfDeluxeAgentInputs(const AgentHandle H, + const DeluxeSystem &S) const noexcept; +}; + +} // End namespace deluxe +} // End namespace rosa + +namespace std { + +/// Converts a \c rosa::deluxe::DeluxeExecutionPolicy into \c std::string. +/// +/// \param EP \c rosa::deluxe::DeluxeExecutionPolicy to convert +/// +/// \return \c std::string representing \p EP +string to_string(const rosa::deluxe::DeluxeExecutionPolicy &EP); + +/// Dumps a \c rosa::deluxe::DeluxeExecutionPolicy to a given \c std::ostream. +/// +/// \param [in,out] OS output stream to dump to +/// \param EP \c rosa::deluxe::DeluxeExecutionPolicy to dump +/// +/// \return \p OS after dumping \p EP to it +ostream &operator<<(ostream &OS, const rosa::deluxe::DeluxeExecutionPolicy &EP); + +} // End namespace std + +#endif // ROSA_DELUXE_DELUXEEXECUTIONPOLICY_H diff --git a/include/rosa/deluxe/DeluxeSensor.hpp b/include/rosa/deluxe/DeluxeSensor.hpp old mode 100755 new mode 100644 index 46ca0c1..95bb94d --- a/include/rosa/deluxe/DeluxeSensor.hpp +++ b/include/rosa/deluxe/DeluxeSensor.hpp @@ -1,257 +1,668 @@ //===-- rosa/deluxe/DeluxeSensor.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeSensor.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Specialization of \c rosa::Agent for *sensor* role of the the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXESENSOR_HPP #define ROSA_DELUXE_DELUXESENSOR_HPP #include "rosa/core/Agent.hpp" #include "rosa/deluxe/DeluxeAtoms.hpp" +#include "rosa/deluxe/DeluxeExecutionPolicy.h" +#include "rosa/deluxe/DeluxeTuple.hpp" + +/// Local helper macros to deal with built-in types. +/// +///@{ + +/// Creates function name for member functions in \c rosa::deluxe::DeluxeSensor. +/// +/// \param N name suffix to use +#define DSMASTERHANDLERNAME(N) handleMaster_##N + +/// Defines member functions for handling messages from *master* in +/// \c rosa::deluxe::DeluxeSensor. +/// +/// \see \c DeluxeSensorMasterInputHandlers +/// +/// \note No pre- and post-conditions are validated directly by these functions, +/// they rather rely on \c rosa::deluxe::DeluxeSensor::saveMasterInput to do +/// that. +/// +/// \param T the type of input to handle +/// \param N name suffix for the function identifier +#define DSMASTERHANDLERDEFN(T, N) \ + void DSMASTERHANDLERNAME(N)(atoms::Master, id_t MasterId, token_size_t Pos, \ + T Value) noexcept { \ + saveMasterInput(MasterId, Pos, Value); \ + } + +/// Convenience macro for \c DSMASTERHANDLERDEFN with identical arguments. +/// +/// \see \c DSMASTERHANDLERDEFN +/// +/// This macro can be used instead of \c DSMASTERHANDLERDEFN if the actual value +/// of \p T can be used as a part of a valid identifier. +/// +/// \param T the type of input to handle +#define DSMASTERHANDLERDEF(T) DSMASTERHANDLERDEFN(T, T) + +/// Results in a \c THISMEMBER reference to a member function defined by +/// \c DSMASTERHANDLERDEFN. +/// +/// Used in the constructor of \c rosa::deluxe::DeluxeSensor to initialize super +/// class \c rosa::Agent with member function defined by \c DSMASTERHANDLERDEFN. +/// +/// \see \c DSMASTERHANDLERDEFN, \c THISMEMBER +/// +/// \param N name suffix for the function identifier +#define DSMASTERHANDLERREF(N) THISMEMBER(DSMASTERHANDLERNAME(N)) + +///@} namespace rosa { namespace deluxe { /// Specialization of \c rosa::Agent for *sensor* role of the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext +/// +/// \invariant There is a compatible *execution policy* set; the actual value in +/// \c rosa::deluxe::DeluxeSensor::MasterInputNextPos is valid with respect to +/// the corresponding types. +/// +/// \see Definition of \c rosa::deluxe::DeluxeSensor::inv on the class invariant +/// +/// \note All member functions validate the class invariant as part of their +/// precondition. Moreover, non-const functions validate the invariant before +/// return as their postcondition. class DeluxeSensor : public Agent { -public: - /// Template alias for function objects used as data source for - /// \c rosa::deluxe::DeluxeSensor. + /// Checks whether \p this object holds the class invariant. /// - /// \note The function used for \c D is to be \c noexcept. + /// \see Invariant of the class \c rosa::deluxe::DeluxeSensor /// - /// \tparam T type of data provided by the function - template using D = std::function; + /// \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 type of values \p this object sends to its *master*. + /// That is the types of values \p this object sends to its *master* in a + /// \c rosa::deluxe::DeluxeTuple. /// /// \see \c rosa::deluxe::DeluxeSensor::master - const TypeNumber OutputType; + const Token OutputType; + + /// The type of values \p this object processes from its *master*. + /// + /// That is the types of values \p this object receives from its *master* in a + /// \c rosa::deluxe::DeluxeTuple. + /// + /// \see \c rosa::deluxe::DeluxeSensor::master + const Token MasterInputType; private: + /// Indicates which element of the master-input is expected from the *master*. + /// + /// The *master* is supposed to send one \c rosa::deluxe::DeluxeTuple value + /// element by element in their order of definition. This member field tells + /// the element at which position should be received next. + /// + /// \p this object is supposed to be triggered only when a complete + /// master-input has been received, that is the field should hold the value + /// `0`. + /// + /// \see \c rosa::deluxe::DeluxeSensor::handleTrigger + /// \c rosa::deluxe::DeluxeSensor::saveMasterInput + token_size_t MasterInputNextPos; + + /// Indicates whether the input value from the *master* has been changed since + /// the last trigger received from the system. + /// + /// The flag is reset to \c false upon handling a trigger and then set to \c + /// true by \c rosa::deluxe::DeluxeSensor::saveMasterInput when storig a new + /// input value in \c rosa::deluxe::DeluxeSensor::MasterInputValue. + bool MasterInputChanged; + + /// Stores the actual input value from *master*. + /// + /// \note The type of the stored value matches the types indicated by \c + /// rosa::deluxe::DeluxeSensor::MasterInputType. + const std::unique_ptr MasterInputValue; + /// Alias for function objects used as trigger handler for /// \c rosa::deluxe::DeluxeSensor. /// /// \note The function used for \c H is to be \c noexcept. /// /// \see \c DeluxeSensorTriggerHandlers using H = std::function; - /// \defgroup DeluxeSensorTriggerHandlers Trigger handlers of rosa::deluxe::DeluxeSensor + /// \defgroup DeluxeSensorTriggerHandlers Trigger handlers of + /// rosa::deluxe::DeluxeSensor /// /// \brief Trigger handler functions of \c rosa::deluxe::DeluxeSensor /// - /// The actual data source functions are captured in a lambda expression that - /// is in turn wrapped in a \c std::function object. The lambda expression - /// calls the data source function to obtain the next sensory value and sends - /// it to *master* by calling \c rosa::deluxe::DeluxeSensor::sendToMaster. The - /// function \c rosa::deluxe::DeluxeSensor::handleTrigger needs only to call - /// the proper function object. + /// The actual data source functions and master-input processing function are + /// captured in lambda expressions that are in turn wrapped in \c + /// std::function objects. The lambda expression calls a processing function, + /// either to handle master-input or obtain the next sensory value from data + /// source. The next sensory value is sent it to *master* by calling \c + /// rosa::deluxe::DeluxeSensor::sendToMaster. Also, the flag \c + /// rosa::deluxe::DeluxeSensor::MasterInputChanged is reset when the current + /// value is passed to the master-input processing function. The function \c + /// rosa::deluxe::DeluxeSensor::handleTrigger needs only to call the proper + /// function object. + + /// Processes master-input. + /// + /// \ingroup DeluxeSensorTriggerHandlers + /// + /// The function is called upon the sensor is trigged by the system. + const H MFP; - /// Handles trigger during normal execution. + /// Produces the next sensory value during normal execution. /// /// \ingroup DeluxeSensorTriggerHandlers /// /// The function is used during normal execution. During simulation, the /// simulation environment sets \c rosa::deluxe::DeluxeSensor::SFP, which is /// used instead of \c rosa::deluxe::DeluxeSensor::FP. const H FP; - /// Handles trigger during simulation. + /// Produces the next sensory value during simulation. /// /// \ingroup DeluxeSensorTriggerHandlers /// /// The function is empty by default. The simulation environment sets it to be /// used during simulation. H SFP; /// The *master* to send values to. /// /// \note *Masters* are set dynamically, hence it is possible that a /// \c rosa::deluxe::DeluxeSensor instance does not have any *master* at a /// given moment. Optional Master; + /// Tells the unique identifier of the *master* of \p this object, if any + /// registered. + /// + /// \return the unique identifier of the *master* + /// + /// \pre A *master* is registered for \p this object: \code + /// Master + /// \endcode + id_t masterId(void) const noexcept; + + /// Wraps a master-input processing function into a trigger handler. + /// + /// \see \c rosa::deluxe::DeluxeSensor::MFP and \c DeluxeSensorTriggerHandlers + /// + /// \tparam Ts types of elements of master-input processed by \p MF + /// \tparam S0 indices for accessing master-input values + /// + /// \param MF function that processes master-input + /// + /// \note The second argument provides indices statically as template + /// arguments \p S0..., so its actual value is ignored. + /// + /// \note A master-input type of \c rosa::deluxe::EmptyDeluxeTuple indicates + /// that \p this object does not receive master-input, \p MF is never called + /// if \p Ts is empty. + /// + /// \return trigger handler function based on \p MF + /// + /// \pre Statically, the indices match the elements: \code + /// sizeof...(Ts) == sizeof...(S0) + /// \endcode Dynamically, \p Ts... match \c + /// rosa::deluxe::DeluxeSensor::MasterInputType: \code + /// MasterInputType == DeluxeTuple::TT + /// \endcode + template + H triggerHandlerFromProcessingFunction( + std::function, bool>)> &&MF, + Seq) noexcept; + /// Wraps a data source function into a trigger handler. /// - /// \see \c DeluxeSensorTriggerHandlers + /// \see \c rosa::deluxe::DeluxeSensor::FP, \c + /// rosa::deluxe::DeluxeSensor::SFP, and \c DeluxeSensorTriggerHandlers /// /// \tparam T type of data provided by \p F /// /// \param F function to generate value with + /// \param inSimulation if F is a data source for Simulation /// - /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code - /// OutputType == TypeNumberOf::Value + /// \return trigger handler function based on \p F + /// + /// \pre Statically, the type agument \p T is an instance of \c + /// rosa::deluxe::DeluxeTuple: \code + /// IsDeluxeTuple::Value + /// \endcode Dynamically, \p T matches \c + /// rosa::deluxe::DeluxeSensor::OutputType: \code + /// OutputType == T::TT /// \endcode - template H triggerHandlerFromDataSource(D &&F) noexcept; + template + H triggerHandlerFromDataSource(std::function &&F, + bool inSimulation) noexcept; public: /// Creates a new instance. /// /// The constructor instantiates the base-class with functions to handle /// messages as defined for the *deluxe interface*. /// - /// \todo Enforce F does not potentially throw exception. + /// \todo Enforce \p F and \p MF do not potentially throw exception. /// + /// \tparam MT type of master-input handled by \p MF /// \tparam T type of data to operate on /// + /// \note Instantiation fails if any of the type arguments \p MT and \p T is + /// not an instance of \c rosa::deluxe::DeluxeTuple or \p T is \c + /// rosa::deluxe::EmptyDeluxeTuple. + /// + /// \note If \p MT is \c rosa::deluxe::EmptyDeluxeTuple, the constructed + /// object does not receive master-input. + /// /// \param Kind kind of the new \c rosa::Unit instance /// \param Id unique identifier of the new \c rosa::Unit instance /// \param Name name of the new \c rosa::Unit instance /// \param S \c rosa::MessagingSystem owning the new instance + /// \param MF function to process master-input values with /// \param F function to generate the next value with during normal operation /// - /// \pre Statically, \p T is a built-in type:\code - /// TypeListContains::Value + /// \pre Statically, \p MT and \p T are instances of \c + /// rosa::deluxe::DeluxeTuple and \p T contains at least one element:\code + /// TypeListAllDeluxeTuple>::Value && T::Length > 0 /// \endcode /// Dynamically, the instance is created as of kind /// \c rosa::deluxe::atoms::SensorKind: /// \code /// Kind == rosa::deluxe::atoms::SensorKind /// \endcode - template ::Value>> + /// + /// \see \c rosa::deluxe::DeluxeTuple + template < + typename MT, typename T, + typename = std::enable_if_t< + TypeListAllDeluxeTuple>::Value && (T::Length > 0)>> DeluxeSensor(const AtomValue Kind, const id_t Id, const std::string &Name, - MessagingSystem &S, D &&F) noexcept; + MessagingSystem &S, + std::function)> &&MF, + std::function &&F) noexcept; /// Destroys \p this object. ~DeluxeSensor(void) noexcept; + /// Returns the current execution policy of \p this object. + /// + /// \see \c rosa::deluxe::DeluxeExecutionPolicy + /// + /// \note The returned reference is valid only as long as \c + /// rosa::deluxe::DeluxeSensor::setExecutionPolicy() is not called and \p this + /// object is not destroyed. + /// + /// \return \c rosa::deluxe::DeluxeSensor::ExecutionPolicy + const DeluxeExecutionPolicy &executionPolicy(void) const noexcept; + + /// Sets the current execution policy of \p this object to \p EP. + /// + /// \see \c rosa::deluxe::DeluxeExecutionPolicy + /// + /// \note \p EP is set only if it can handle \p this object. + /// + /// \param EP the new execution policy for \p this object + /// + /// \return if \p EP was successfully set for \p this object. + bool setExecutionPolicy(std::unique_ptr &&EP) noexcept; + /// The *master* of \p this object, if any. /// /// \see \c rosa::deluxe::DeluxeSensor::registerMaster /// /// \return the *master* registered for \p this object Optional master(void) const noexcept; /// Registers a *master* for \p this object. /// /// The new *master* is registered by overwriting the reference to any /// already registered *master*. One can clear the registered reference by /// passing an *empty* \c rosa::Optional object as actual argument. /// /// \note The role of the referred *master* is validated by checking its /// *kind*. /// + /// \note Any call to \c rosa::deluxe::DeluxeSensor::registerMaster should be + /// paired with a corresponding call of \c + /// rosa::deluxe::DeluxeAgent::registerSlave, which validates that + /// input/output types of master and slave matches. + /// /// \param _Master the *master* to register /// /// \pre \p Master is empty or of kind \c rosa::deluxe::atoms::AgentKind: /// \code /// !_Master || unwrapAgent(*_Master).Kind == rosa::deluxe::atoms::AgentKind /// \endcode void registerMaster(const Optional _Master) noexcept; /// Clears the simulation trigger handler of \p this object. /// /// The function assigns \c rosa::deluxe::DeluxeSensor::SFP with \c nullptr. void clearSimulationDataSource(void) noexcept; /// Tells whether a simulation trigger handler is set for \p this object. /// /// The function returns whether \c rosa::deluxe::DeluxeSensor::SFP is not /// \c nullptr. /// /// \return if a simulation trigger handler is set for \p this object. bool simulationDataSourceIsSet(void) const noexcept; /// Registers a simulation data source for \p this object. /// /// A new simulation trigger handler wrapping \p SF is stored in /// \c rosa::deluxe::DeluxeSensor::SFP by overwriting any already registered /// simulation data source. /// /// \todo Enforce SF does not potentially throw exception. /// - /// \tparam T type of data provided by \p SF + /// \tparam Ts types of elements of values provided by \p SF /// /// \param SF function to generate value with /// - /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code - /// OutputType == TypeNumberOf::Value + /// \pre \p Ts... match \c rosa::deluxe::DeluxeSensor::OutputType: \code + /// OutputType == TypeToken::Value /// \endcode - template void registerSimulationDataSource(D &&SF) noexcept; + template + void registerSimulationDataSource( + std::function(void)> &&SF) noexcept; private: /// Sends a value to the *master* of \p this object. /// /// \p Value is getting sent to \c rosa::deluxe::DeluxeSensor::Master if it /// contains a valid handle for a \c rosa::deluxe::DeluxeAgent. The function /// does nothing otherwise. /// - /// \tparam T type of the value to send + /// 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 /// - /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code - /// OutputType == TypeNumberOf::Value + /// \note The second argument provides indices statically as template + /// arguments \p S0..., so its actual value is ignored. + /// + /// \pre Statically, the indices match the elements: \code + /// sizeof...(Ts) == sizeof...(S0) + /// \endcode Dynamically, \p Ts match \c + /// rosa::deluxe::DeluxeSensor::OutputType: \code + /// OutputType == TypeToken::Value /// \endcode - template void sendToMaster(const T &Value) noexcept; + template + void sendToMaster(const DeluxeTuple &Value, Seq) noexcept; - /// Generates the next sensory value upon trigger from the system. + /// Handles master-input and generates the next sensory value upon trigger + /// from the system. /// - /// Executes \c rosa::deluxe::DeluxeSensor::FP or - /// \c rosa::deluxe::DeluxeSensor::SFP if set. + /// Executes \c rosa::deluxe::DeluxeSensor::MFP for processing master-input + /// and data generating function \c rosa::deluxe::DeluxeSensor::FP or \c + /// rosa::deluxe::DeluxeSensor::SFP if set. /// /// \note The only argument is a \c rosa::AtomConstant, hence its actual /// value is ignored. + /// + /// \pre Master-input is supposed to be completely received upon triggering: + /// \code + /// MasterInputNextPos == 0 + /// \endcode void handleTrigger(atoms::Trigger) noexcept; + + /// Stores a new input value from the *master*. + /// + /// The function stores \p Value at position \p Pos in \c + /// rosa::deluxe::DeluxeSensor::MasterInputValue and also sets the + /// flag \c rosa::deluxe::DeluxeSensor::MasterInputChanged. The function also + /// takes care of checking and updating \c + /// rosa::deluxe::DeluxeSensor::MasterInputNextPos: increments its value and + /// resets it to `0` when the last element is received. + /// + /// \note Utilized by member functions of group \c + /// DeluxeSensorMasterInputHandlers. + /// + /// \tparam T type of input to store + /// + /// \param Id unique identifier of the *master* + /// \param Pos position of the value in the \c rosa::deluxe::DeluxeTuple + /// \param Value the input value to store + /// + /// \pre The *master* with \p Id is registered, \p Pos is the expected + /// position of master-input, and the input from the *master* at position \p + /// Pos is expected to be of type \p T: \code + /// Master && masterId() == Id && Pos == MasterInputNextPos && + /// typeAtPositionOfToken(MasterInputType, Pos) == TypeNumberOf::Value + /// \endcode + template + void saveMasterInput(id_t Id, token_size_t Pos, T Value) noexcept; + + /// \defgroup DeluxeSensorMasterInputHandlers Master-input handlers of + /// rosa::deluxe::DeluxeSensor + /// + /// Definition of member functions handling messages from the *master* with + /// different types of input + /// + /// A *slave* generally needs to be prepared to deal with values of any + /// built-in type to handle messages from its *master*. Each type requires a + /// separate message handler, which are implemented by these functions. The + /// functions instantiate \c rosa::deluxe::DeluxeSensor::saveMasterInput with + /// the proper template argument and pass the content of the message on for + /// processing. + /// + /// \note The member functions in this group are defined by \c + /// DSMASTERHANDLERDEF. + /// + /// \note Keep these definitions in sync with \c rosa::BuiltinTypes. + /// + ///@{ + + DSMASTERHANDLERDEF(AtomValue) + DSMASTERHANDLERDEF(int16_t) + DSMASTERHANDLERDEF(int32_t) + DSMASTERHANDLERDEF(int64_t) + DSMASTERHANDLERDEF(int8_t) + DSMASTERHANDLERDEFN(long double, long_double) + DSMASTERHANDLERDEFN(std::string, std__string) + DSMASTERHANDLERDEF(uint16_t) + DSMASTERHANDLERDEF(uint32_t) + DSMASTERHANDLERDEF(uint64_t) + DSMASTERHANDLERDEF(uint8_t) + DSMASTERHANDLERDEF(unit_t) + DSMASTERHANDLERDEF(bool) + DSMASTERHANDLERDEF(double) + DSMASTERHANDLERDEF(float) + + /// @} }; +template +DeluxeSensor::H DeluxeSensor::triggerHandlerFromProcessingFunction( + std::function, bool>)> &&MF, + Seq) noexcept { + using MT = DeluxeTuple; + STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent arguments"); + ASSERT(MasterInputType == MT::TT); + + // NOTE: Clang 6 warns about unused lambda captures; we suppress that + // warning (those variables need to be captured). +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-lambda-capture" +#endif // defined __clang__ + return [ this, MF ](void) noexcept { + // Do not do anything for master-input type \c + // rosa::deluxe::EmptyDeluxeTuple. + if constexpr (!std::is_same::value) { + LOG_TRACE_STREAM << "DeluxeSensor " << FullName + << " handles master-input." << std::endl; + // The assert must hold if \p this object was successfuuly constructed. + ASSERT((true && ... && + (static_cast(static_cast(S0)) == S0))); + const auto MasterInputArg = std::make_pair( + // Get all elements of the tuple in a fold expression. + DeluxeTuple(*static_cast( + MasterInputValue->pointerTo(static_cast(S0)))...), + MasterInputChanged); + MasterInputChanged = false; + MF(MasterInputArg); + } + }; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif // defined __clang__ +} + template -DeluxeSensor::H DeluxeSensor::triggerHandlerFromDataSource(D &&F) noexcept { - ASSERT(OutputType == TypeNumberOf::Value); - return [this, F](void) noexcept { sendToMaster(F()); }; +DeluxeSensor::H +DeluxeSensor::triggerHandlerFromDataSource(std::function &&F, + bool inSimulation) noexcept { + STATIC_ASSERT(IsDeluxeTuple::Value, "not tuple type argument"); + ASSERT(OutputType == T::TT); + return [ this, F, inSimulation ](void) noexcept { + // Get value and send it to master only if \p ExecutionPolicy allows it. + if (ExecutionPolicy->shouldProcess({})) { + LOG_TRACE_STREAM << "DeluxeSensor " << Name << " obtains next value." + << std::endl; + sendToMaster(F(), seq_t()); + } else { + LOG_TRACE_STREAM << "DeluxeSensor " << Name << " skips next value." + << std::endl; + if (inSimulation) { + // But read input value in Simulation anyway as input values are + // provided for the highest execution frequency for simulation + F(); + } + } + }; } -template +template DeluxeSensor::DeluxeSensor(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, - D &&F) noexcept - : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger)), - OutputType(TypeNumberOf::Value), - FP(triggerHandlerFromDataSource(std::move(F))), - SFP(nullptr) { + std::function)> &&MF, + std::function &&F) noexcept + : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger), + DSMASTERHANDLERREF(AtomValue), DSMASTERHANDLERREF(int16_t), + DSMASTERHANDLERREF(int32_t), DSMASTERHANDLERREF(int64_t), + DSMASTERHANDLERREF(int8_t), DSMASTERHANDLERREF(long_double), + DSMASTERHANDLERREF(std__string), DSMASTERHANDLERREF(uint16_t), + DSMASTERHANDLERREF(uint32_t), DSMASTERHANDLERREF(uint64_t), + DSMASTERHANDLERREF(uint8_t), DSMASTERHANDLERREF(unit_t), + DSMASTERHANDLERREF(bool), DSMASTERHANDLERREF(double), + DSMASTERHANDLERREF(float)), + ExecutionPolicy(DeluxeExecutionPolicy::decimation(1)), OutputType(T::TT), + MasterInputType(MT::TT), MasterInputChanged(false), + MasterInputValue(new typename TokenizedStorageForTypeList< + typename UnwrapDeluxeTuple::Type>::Type()), + MFP(triggerHandlerFromProcessingFunction(std::move(MF), + seq_t())), + FP(triggerHandlerFromDataSource(std::move(F), false)), SFP(nullptr) { ASSERT(Kind == atoms::SensorKind); - LOG_TRACE("DeluxeSensor is created."); + LOG_TRACE_STREAM << "DeluxeSensor " << FullName << " is created." + << std::endl; + ASSERT(inv()); } -template -void DeluxeSensor::registerSimulationDataSource(D &&SF) noexcept { - ASSERT(OutputType == TypeNumberOf::Value); - SFP = triggerHandlerFromDataSource(std::move(SF)); +template +void DeluxeSensor::registerSimulationDataSource( + std::function(void)> &&SF) noexcept { + ASSERT(OutputType == TypeToken::Value); + SFP = triggerHandlerFromDataSource(std::move(SF), true); + ASSERT(inv()); } -template -void DeluxeSensor::sendToMaster(const T &Value) noexcept { - ASSERT(OutputType == TypeNumberOf::Value); +template +void DeluxeSensor::sendToMaster(const DeluxeTuple &Value, + Seq) noexcept { + STATIC_ASSERT(sizeof...(Ts) == sizeof...(S0), "inconsistent arguments"); + ASSERT(OutputType == TypeToken::Value); + + // The assert must hold if \p this object was successfuuly constructed. + ASSERT((true && ... && + (static_cast(static_cast(S0)) == S0))); + // Create a static constant array for these indices to be available as lvalue + // references when creating messages below. \c S0... when used directly in a + // fold expression is a temporary value, which would result in \c + // rosa::Message instances being created with rvalue references. Further, all + // other values would to copied into a temporary variable for making them + /// available as rvalue references (they are constant lvalue references here). + static constexpr std::array Indices{{S0...}}; + + LOG_TRACE_STREAM << "DeluxeSensor " << FullName << "(" << Id + << ") sends to master(" + << static_cast(Master && *Master) << "): " << Value + << std::endl; // There is a handle and the referred *master* is in a valid state. if (Master && *Master) { - Master->sendMessage(Message::create(atoms::Slave::Value, Id, Value)); + // Handle each element of the tuple in a fold expression. + (Master->sendMessage(Message::create(atoms::Slave::Value, Id, Indices[S0], + std::get(Value))), + ...); } + ASSERT(inv()); +} + +template +void DeluxeSensor::saveMasterInput(id_t Id, token_size_t Pos, + T Value) noexcept { + ASSERT(Master && masterId() == Id && Pos == MasterInputNextPos && + typeAtPositionOfToken(MasterInputType, Pos) == TypeNumberOf::Value); + + LOG_TRACE_STREAM << "DeluxeSensor " << FullName << "(" << Id + << ") saves value from master: (" << static_cast(Pos) + << ") " << Value << std::endl; + + // Save value. + *static_cast(MasterInputValue->pointerTo(Pos)) = Value; + + // Update position of next value. + if (++MasterInputNextPos == lengthOfToken(MasterInputType)) { + MasterInputNextPos = 0; + } + + // Set flag. + MasterInputChanged = true; } } // End namespace deluxe } // End namespace rosa +#undef DSMASTERHANDLEREF +#undef DSMASTERHANDLEDEF +#undef DSMASTERHANDLEDEFN +#undef DSMASTERHANDLENAME + #endif // ROSA_DELUXE_DELUXESENSOR_HPP diff --git a/include/rosa/deluxe/DeluxeSystem.hpp b/include/rosa/deluxe/DeluxeSystem.hpp old mode 100755 new mode 100644 index 4388a4e..bc51cdf --- a/include/rosa/deluxe/DeluxeSystem.hpp +++ b/include/rosa/deluxe/DeluxeSystem.hpp @@ -1,211 +1,240 @@ //===-- rosa/deluxe/DeluxeSystem.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeSystem.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// /// \date 2017-2019 /// /// \brief Specialization of \c rosa::MessagingSystem for the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// //===----------------------------------------------------------------------===// #ifndef ROSA_DELUXE_DELUXESYSTEM_HPP #define ROSA_DELUXE_DELUXESYSTEM_HPP #include "rosa/core/MessagingSystem.hpp" #include "rosa/deluxe/DeluxeAgent.hpp" #include "rosa/deluxe/DeluxeSensor.hpp" namespace rosa { namespace deluxe { /// Implements and extends the \c rosa::MessagingSystem interface to be /// used by \c rosa::deluxe::DeluxeContext. /// /// The class is a specialization of \c rosa::MessagingSystem, where objects /// of two specialized subtypes of \c rosa::Agent, \c rosa::deluxe::DeluxeSensor /// and \c rosa::deluxe::DeluxeAgent, constitute a system. The class extends the /// \c rosa::MessagingSystem interface with features required to implement the /// *deluxe interface*. /// /// \see rosa::deluxe::DeluxeContext class DeluxeSystem : public MessagingSystem { friend class DeluxeContext; + friend class DeluxeExecutionPolicy; public: /// Returns an object implementing the \c rosa::deluxe::DeluxeSystem /// interface. /// /// \param Name name of the new instance /// /// \return \c std::unique_ptr for the new instance of /// \c rosa::DeluxeSystem static std::unique_ptr createSystem(const std::string &Name) noexcept; protected: /// Creates a new instance. /// /// \note Protected constructor restricts instantiation for subclasses. DeluxeSystem(void) noexcept = default; public: /// Creates a \c rosa::deluxe::DeluxeSensor instance owned by \p this object /// and returns a \p rosa::AgentHandle for it. /// + /// \tparam MT type of master-input the new \c rosa::deluxe::DeluxeSensor + /// receives /// \tparam T type of data the new \c rosa::deluxe::DeluxeSensor operates on /// + /// \note Type arguments \p MT and \p T must be instances of \c + /// rosa::deluxe::DeluxeTuple. + /// /// \param Name name of the new \c rosa::deluxe::DeluxeSensor + /// \param MF function to process master-input values /// \param F function to generate the next value with during normal operation /// + /// \see \c rosa::deluxe::DeluxeSensor::DeluxeSensor. + /// /// \return \c rosa::AgentHandle for new \c rosa::deluxe::DeluxeSensor - template + template AgentHandle createSensor(const std::string &Name, - DeluxeSensor::D &&F) noexcept; + std::function)> &&MF, + std::function &&F) noexcept; /// Creates a \c rosa::deluxe::DeluxeAgent instance owned by \p this object /// and returns a \c rosa::AgentHandle for it. /// + /// \tparam MT type of master-input the new \c rosa::deluxe::DeluxeAgent + /// receives /// \tparam T type of data the new \c rosa::deluxe::DeluxeAgent outputs + /// \tparam Ts types of master-output the new \c rosa::deluxe::DeluxeAgent + /// produces /// \tparam As types of inputs the new \c rosa::deluxe::DeluxeAgent takes /// + /// \note Type arguments \p MT, \p T, \p Ts..., and \p As... must be + /// instances of \c rosa::deluxe::DeluxeTuple. + /// /// \param Name name of the new \c rosa::deluxe::DeluxeAgent + /// \param MF function for the new \c rosa::deluxe::DeluxeAgent to process + /// master-input values and generate master-output with /// \param F function for the new \c rosa::deluxe::DeluxeAgent to process - /// input values and generate output with + /// input values and generate output and master-output with + /// + /// \see \c rosa::deluxe::DeluxeAgent::DeluxeAgent. /// /// \return \c rosa::AgentHandle for new \c rosa::deluxe::DeluxeAgent - template - AgentHandle createAgent(const std::string &Name, - DeluxeAgent::D &&F) noexcept; + template + AgentHandle createAgent( + const std::string &Name, + std::function...>(std::pair)> &&MF, + std::function, Optional...>( + std::pair...)> &&F) noexcept; protected: /// Tells whether a \c rosa::AgentHandle refers to a /// \c rosa::deluxe::DeluxeSensor owned by \p this object. /// /// \param H \c rosa::AgentHandle to check /// /// \return whether \p H refers to a \c rosa::deluxe::DeluxeSensor owned by /// \p this object virtual bool isDeluxeSensor(const AgentHandle &H) const noexcept = 0; /// Extracts a const qualified \c rosa::deluxe::DeluxeSensor reference from a /// const qualified \c rosa::AgentHandle if possible. /// /// The function returns a \c rosa::Optional object containing a const /// qualified reference to a \c rosa::deluxe::DeluxeSensor object extracted /// from a const qualified \c rosa::AgentHandle instance if the referred /// object is of type \c rosa::deluxeDeluxeSensor and owned by \p this object. /// The returned \c rosa::Optional object is empty otherwise. /// /// \see rosa::deluxe::DeluxeSystem::isDeluxeSensor /// /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeSensor /// from /// /// \return const qualified reference to \c rosa::deluxe::DeluxeSensor if /// \p H refers to an object which is of that type and is owned by \p this /// object Optional getDeluxeSensor(const AgentHandle &H) const noexcept; /// Extracts a \c rosa::deluxe::DeluxeSensor reference from a /// \c rosa::AgentHandle if possible. /// /// The function returns a \c rosa::Optional object containing a reference to /// a \c rosa::deluxe::DeluxeSensor object extracted from a /// \c rosa::AgentHandle instance if the referred object is of type /// \c rosa::deluxeDeluxeSensor and owned by \p this object. The returned /// \c rosa::Optional object is empty otherwise. /// /// \see rosa::deluxe::DeluxeSystem::isDeluxeSensor /// /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeSensor /// from /// /// \return reference to \c rosa::deluxe::DeluxeSensor if \p H refers to an /// object which is of that type and is owned by \p this object Optional getDeluxeSensor(AgentHandle &H) const noexcept; /// Tells whether a \c rosa::AgentHandle refers to a /// \c rosa::deluxe::DeluxeAgent owned by \p this object. /// /// \param H \c rosa::AgentHandle to check /// /// \return whether \p H refers to a \c rosa::deluxe::DeluxeAgent owned by /// \p this object virtual bool isDeluxeAgent(const AgentHandle &H) const noexcept = 0; /// Extracts a const qualified \c rosa::deluxe::DeluxeAgent reference from a /// const qualified \c rosa::AgentHandle if possible. /// /// The function returns a \c rosa::Optional object containing a const /// qualified reference to a \c rosa::deluxe::DeluxeAgent object extracted /// from a const qualified \c rosa::AgentHandle instance if the referred /// object is of type \c rosa::deluxeDeluxeAgent and owned by \p this object. /// The returned \c rosa::Optional object is empty otherwise. /// /// \see rosa::deluxe::DeluxeSystem::isDeluxeAgent /// /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeAgent /// from /// /// \return const qualified reference to \c rosa::deluxe::DeluxeAgent if \p H /// refers to an object which is of that type and is owned by \p this object Optional getDeluxeAgent(const AgentHandle &H) const noexcept; /// Extracts a \c rosa::deluxe::DeluxeAgent reference from a /// \c rosa::AgentHandle if possible. /// /// The function returns a \c rosa::Optional object containing a reference to /// a \c rosa::deluxe::DeluxeAgent object extracted from a /// \c rosa::AgentHandle instance if the referred object is of type /// \c rosa::deluxeDeluxeAgent and owned by \p this object. The returned /// \c rosa::Optional object is empty otherwise. /// /// \see rosa::deluxe::DeluxeSystem::isDeluxeAgent /// /// \param H \c rosa::AgentHandle to extract a \c rosa::deluxe::DeluxeAgent /// from /// /// \return reference to \c rosa::deluxe::DeluxeAgent if \p H refers to an /// object which is of that type and is owned by \p this object Optional getDeluxeAgent(AgentHandle &H) const noexcept; }; -template -AgentHandle DeluxeSystem::createSensor(const std::string &Name, - DeluxeSensor::D &&F) noexcept { +template +AgentHandle +DeluxeSystem::createSensor(const std::string &Name, + std::function)> &&MF, + std::function &&F) noexcept { Agent &DS = createUnit( [&](const id_t Id, MessagingSystem &S) { - return new DeluxeSensor(atoms::SensorKind, Id, Name, S, std::move(F)); + return new DeluxeSensor(atoms::SensorKind, Id, Name, S, std::move(MF), + std::move(F)); }); return {DS}; } -template -AgentHandle -DeluxeSystem::createAgent(const std::string &Name, - DeluxeAgent::D &&F) noexcept { - +template +AgentHandle DeluxeSystem::createAgent( + const std::string &Name, + std::function...>(std::pair)> &&MF, + std::function, Optional...>( + std::pair...)> &&F) noexcept { Agent &DA = createUnit( [&](const id_t Id, DeluxeSystem &S) { - return new DeluxeAgent(atoms::AgentKind, Id, Name, S, std::move(F)); + return new DeluxeAgent(atoms::AgentKind, Id, Name, S, std::move(MF), + std::move(F)); }); return {DA}; } } // End namespace deluxe } // End namespace rosa -#endif // ROSA_LIB_DELUXE_DELUXESYSTEM_HPP +#endif // ROSA_DELUXE_DELUXESYSTEM_HPP diff --git a/include/rosa/deluxe/DeluxeTuple.hpp b/include/rosa/deluxe/DeluxeTuple.hpp new file mode 100644 index 0000000..e57d29b --- /dev/null +++ b/include/rosa/deluxe/DeluxeTuple.hpp @@ -0,0 +1,359 @@ +//===-- rosa/deluxe/DeluxeTuple.hpp -----------------------------*- C++ -*-===// +// +// The RoSA Framework +// +//===----------------------------------------------------------------------===// +/// +/// \file rosa/deluxe/DeluxeTuple.hpp +/// +/// \author David Juhasz (david.juhasz@tuwien.ac.at) +/// +/// \date 2019 +/// +/// \brief Facilities for handling multiple input/output values for connections +/// in the *deluxe interface*. +/// +/// \see \c rosa::deluxe::DeluxeContext +/// +//===----------------------------------------------------------------------===// + +#ifndef ROSA_DELUXE_DELUXETUPLE_HPP +#define ROSA_DELUXE_DELUXETUPLE_HPP + +#include "rosa/support/sequence.hpp" +#include "rosa/support/type_token.hpp" +#include +#include + +namespace rosa { +namespace deluxe { + +/// A tuple to manage multiple input/output values in the *deluxe interface*. +/// +/// \tparam Ts types of elements of the tuple +/// +/// \note The template may be instantiated only with built-in types and the +/// number of those type may not exceed the capacity of a \c rosa::Token. +template +struct DeluxeTuple : public std::tuple { + // Statically enforce that the class template is instantiated only with + // built-in types. + STATIC_ASSERT((TypeListSubsetOf, BuiltinTypes>::Value), + "not built-in types"); + // Statically enforce that the class template is instantiated with not too + // many types. + // \note Instantiation would fail on \c rosa::deluxe::DeluxeTuple::TT if there + // are too any types; this assertion is for more readable error reporting. + STATIC_ASSERT(sizeof...(Ts) <= token::MaxTokenizableListSize, + "Too many types"); + + /// How many elements the instance has. + static constexpr token_size_t Length = sizeof...(Ts); + + /// What types the class contains. + /// + /// Type information encoded as \c rosa::Token. + static constexpr Token TT = TypeToken::Value; + + /// Default constructor, zero-initializes elements. + DeluxeTuple(void) = default; + + /// Constructor, initializes the underlying \c std::tuple with lvalue + /// references. + /// + /// \param Args value references to the values to store + DeluxeTuple(const std::decay_t &... Args) : std::tuple(Args...) {} + + /// Constructor, initializes the underlying \c std::tuple with rvalue + /// references. + /// + /// \param Args rvalue references to the values to store + DeluxeTuple(std::decay_t &&... Args) + : std::tuple(std::move(Args)...) {} + + /// Default copy-constructor. + DeluxeTuple(const DeluxeTuple &) = default; + + /// Default move-constructor. + DeluxeTuple(DeluxeTuple &&) = default; + + /// Default copy-assignment. + DeluxeTuple &operator=(const DeluxeTuple &) = default; + + /// Default move-assignment. + DeluxeTuple &operator=(DeluxeTuple &&) = default; + +private: + /// Dumps \p this object to a given \c std::ostream. + /// + /// \note Provides implementation for \c rosa::deluxe::DeluxeTuple::dump. + /// + /// \tparam S0 Indices for accessing elements. + /// + /// \param [in,out] OS output stream to dump to + /// + /// \note The second argument provides indices statically as template + /// arguments \p S0..., so its actual value is ignored. + /// + /// \pre Statically, \p S0... matches number of types \p this object was + /// created: \code + /// sizeof...(S0) == sizeof...(Ts) + /// \endcode + template + void dump(std::ostream &OS, Seq) const noexcept; + +public: + /// Dumps \p this object to a given \c std::ostream. + /// + /// \param [in,out] OS output stream to dump to + void dump(std::ostream &OS) const noexcept; +}; + +template +template +void DeluxeTuple::dump(std::ostream &OS, Seq) const noexcept { + STATIC_ASSERT(sizeof...(S0) == sizeof...(Ts), "inconsistent type arguments"); + // Convert value to std::string with std::to_string except for a value of + // std::string that does not need conversion. + auto dump_to_string = [](const auto &V) { + if constexpr (std::is_same, std::string>::value) { + return V; + } else { + return std::to_string(V); + } + }; + OS << "{"; + (OS << ... << (" " + dump_to_string(std::get(*this)))); + OS << " }"; +} + +template +void DeluxeTuple::dump(std::ostream &OS) const noexcept { + dump(OS, seq_t()); +} + +/// Type alias for a \c rosa::deluxe::DeluxeTuple that contains no elements. +using EmptyDeluxeTuple = DeluxeTuple<>; + +/// Template specialization for \c rosa::deluxe::EmptyDeluxeTuple. +template <> struct DeluxeTuple<> : public std::tuple<> { + /// How many elements the instance has. + static constexpr token_size_t Length = 0; + + /// What types the class contains. + /// + /// Type information encoded as \c rosa::Token. + static constexpr Token TT = TypeToken<>::Value; + + /// Constructor, initializes the underlying \c std::tuple. + DeluxeTuple(void) : std::tuple<>() {} + + /// Default copy-constructor. + DeluxeTuple(const DeluxeTuple &) = default; + + // Default move-constructor. + DeluxeTuple(DeluxeTuple &&) = default; + + /// Default copy-assignment. + DeluxeTuple &operator=(const DeluxeTuple &) = default; + + // Default move-assignment, + DeluxeTuple &operator=(DeluxeTuple &&) = default; + + /// Dumps \p this object to a given \c std::ostream. + /// + /// \param [in,out] OS output stream to dump to + static void dump(std::ostream &OS) noexcept; +}; + +/// Creates a \c rosa::deluxe::DeluxeTuple instance from the given lvalues +/// references. +/// +/// \tparam Ts types of elements of the tuple +/// +/// \see \c rosa::deluxe::DeluxeTuple +/// +/// \param Args values to store in the tuple +/// +/// \return an instance of \c rosa::deluxe::DeluxeTuple with \p Args as +/// elements +template +inline DeluxeTuple make_deluxe_tuple(const Ts &... Args) noexcept { + return DeluxeTuple(Args...); +} + +/// Creates a \c rosa::deluxe::DeluxeTuple instance from the given rvalue +/// references. +/// +/// \tparam Ts types of elements of the tuple +/// +/// \see \c rosa::deluxe::DeluxeTuple +/// +/// \param Args values to store in the tuple +/// +/// \return an instance of \c rosa::deluxe::DeluxeTuple with \p Args as +/// elements +template +inline DeluxeTuple make_deluxe_tuple(Ts&&... Args) noexcept { + return DeluxeTuple(std::move(Args)...); +} + +/// \defgroup UnwrapDeluxeTuple Implementation of +/// rosa::deluxe::UnwrapDeluxeTuple +/// +/// \brief Unwraps element types from an instance of \c +/// rosa::deluxe::DeluxeTuple into a \c rosa::TypeList +/// +/// Types can be unwrapped from a \c rosa::deluxe::DeluxeTuple instance as \code +/// typename UnwrapDeluxeTuple::Type +/// \endcode +/// +/// For example, the following expression evaluates to `true`: \code +/// std::is_same>::Type, +/// TypeList>::value +/// \endcode +///@{ + +/// Declaration of the template. +/// +/// \tparam Tuple \c rosa::deluxe::DeluxeTuple to unwrap +template struct UnwrapDeluxeTuple; + +/// Implementation of the template for \c rosa::deluxe::DeluxeTuple instances. +template struct UnwrapDeluxeTuple> { + using Type = TypeList; +}; + +///@} + +/// \defgroup TypeListUnwrapDeluxeTuple Implementation of +/// \c rosa::deluxe::TypeListUnwrapDeluxeTuple +/// +/// \brief Unwraps element types from instances of \c +/// rosa::deluxe::DeluxeTuple in a \c rosa::TypeList. +/// +/// Types can be unwrapped from \c rosa::deluxe::DeluxeTuple instances as \code +/// typename TypeListUnwrapDeluxeTuple::Type +/// \endcode +/// +/// For example, the following expression evaluates to `true`: \code +/// std::is_same< +/// typename TypeListUnwrapDeluxeTuple, +/// T3>>::Type, +/// TypeList +/// >::value +/// \endcode +///@{ + +/// Declaration of the template. +/// +/// \tparam List \c rosa::TypeList to check +template struct TypeListUnwrapDeluxeTuple; + +/// Specialization for \c rosa::EmptyTypeList. +template <> struct TypeListUnwrapDeluxeTuple { + using Type = EmptyTypeList; +}; + +/// Specialization for the case when the first type in \p List is an instance of +/// \c rosa::deluxe::DeluxeTuple. +template +struct TypeListUnwrapDeluxeTuple, Ts...>> { + using Type = typename TypeListConcat< + typename UnwrapDeluxeTuple>::Type, + typename TypeListUnwrapDeluxeTuple>::Type>::Type; +}; + +/// Implementation for a general first type in \p List. +template +struct TypeListUnwrapDeluxeTuple> { + using Type = typename TypeListPush< + T, typename TypeListUnwrapDeluxeTuple>::Type>::Type; +}; + +///@} + +/// \defgroup IsDeluxeTuple Implementation of \c rosa::deluxe::IsDeluxeTuple +/// +/// \brief Tells if a type is an instance of \c rosa::deluxe::DeluxeTuple. +/// +/// Whether a type \c T is an instance of \c rosa::deluxe::DeluxeTuple can be +/// checked as \code +/// IsDeluxeTuple::Value +/// \endcode +///@{ + +/// Declaration of the template. +/// +/// \tparam T type to check +template struct IsDeluxeTuple; + +/// Specialization for the case when the type is an instance of \c +/// rosa::deluxe::DeluxeTuple. +template +struct IsDeluxeTuple> { + static constexpr bool Value = true; +}; + +/// Implementation for a general case of type \p T. +template +struct IsDeluxeTuple { + static constexpr bool Value = false; +}; + +///@} + +/// \defgroup TypeListAllDeluxeTuple Implementation of +/// \c rosa::deluxe::TypeListAllDeluxeTuple +/// +/// \brief Tells if all types in a \c rosa::TypeList is an instance of \c +/// rosa::deluxe::DeluxeTuple. +/// +/// Whether a \c rosa::TypeList \c List contains instances of \c +/// rosa::deluxe::DeluxeTuple only can be checked as \code +/// TypeListAllDeluxeTuple::Value +/// \endcode +///@{ + +/// Declaration of the template. +/// +/// \tparam List \c rosa::TypeList to check +template struct TypeListAllDeluxeTuple; + +/// Specialization for \c rosa::EmptyTypeList. +template <> struct TypeListAllDeluxeTuple { + static constexpr bool Value = true; +}; + +/// Implementation for the general case when there is at leasst one element in +/// the list. +template +struct TypeListAllDeluxeTuple> { + static constexpr bool Value = + IsDeluxeTuple::Value && TypeListAllDeluxeTuple>::Value; +}; + +///@} + +} // End namespace deluxe +} // End namespace rosa + +namespace std { + +/// Dumps a \c rosa::deluxe::Deluxe instance to a given \c std::ostream. +/// +/// \param [in,out] OS output stream to dump to +/// \param Tuple \c rosa::deluxe::Deluxe to dump +/// +/// \return \p OS after dumping \p Tuple to it +template +ostream &operator<<(ostream &OS, + const rosa::deluxe::DeluxeTuple &Tuple) { + Tuple.dump(OS); + return OS; +} + +} // End namespace std + +#endif // ROSA_DELUXE_DELUXETUPLE_HPP diff --git a/include/rosa/support/atom.hpp b/include/rosa/support/atom.hpp index 60fbe06..1dfe3e0 100644 --- a/include/rosa/support/atom.hpp +++ b/include/rosa/support/atom.hpp @@ -1,179 +1,209 @@ //===-- rosa/support/atom.hpp -----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/atom.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Facility for *atoms*, short strings statically encoded as integers. /// /// \note This implementation is based on the \c atom implementation of CAF. /// \todo Check license. /// /// *Atoms* can be used to turn short string literals into statically generated /// types. The literals may consist of at most \c 10 non-special characters, /// legal characters are \c _0-9A-Za-z and the whitespace character. Special /// characters are turned into whitespace, which may result in different string /// literals being encoded into the same integer value, if any of those contain /// at least one special character. /// /// \note The usage of special characters in the string literals used to create /// *atoms* cannot be checked by the compiler. /// /// Example: /// /// \code /// constexpr AtomValue NameValue = atom("name"); /// using NameAtom = AtomConstant; /// /// [](NameAtom){ std::cout << "Argument of type NameAtom"; }(NameAtom::Value) /// \endcode /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_ATOM_HPP #define ROSA_SUPPORT_ATOM_HPP #include "rosa/support/debug.hpp" namespace rosa { /// Maximal length of valid atom strings. constexpr size_t MaxAtomLength = 10; /// Underlying integer type of atom values. using atom_t = uint64_t; /// Turn \c rosa::atom_t into a strongly typed enumeration. /// /// Values of \c rosa::atom_t casted to \c rosa::AtomValue may be used in a /// type-safe way. enum class AtomValue : atom_t {}; /// Anonymous namespace with implementational details, consider it private. namespace { // clang-format off /// Encodes ASCII characters to 6-bit encoding. constexpr unsigned char AtomEncodingTable[] = { /* ..0 ..1 ..2 ..3 ..4 ..5 ..6 ..7 ..8 ..9 ..A ..B ..C ..D ..E ..F */ /* 0.. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1.. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2.. */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3.. */ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, /* 4.. */ 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /* 5.. */ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 0, 0, 0, 0, 37, /* 6.. */ 0, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, /* 7.. */ 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 0, 0, 0, 0, 0}; // clang-format on /// Decodes 6-bit characters to ASCII constexpr char AtomDecodingTable[] = " 0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ_" "abcdefghijklmnopqrstuvwxyz"; /// Encodes one character and updates the integer representation. /// /// \param Current an encoded value /// \param CharCode a character to add to \p Current /// /// \return \p Current updated with \p CharCode constexpr atom_t nextInterim(atom_t Current, size_t CharCode) { return (Current << 6) | AtomEncodingTable[(CharCode <= 0x7F) ? CharCode : 0]; } /// Encodes a C-string into an integer value to be used as \c rosa::AtomValue. /// /// \param CStr a string to encode /// \param Interim encoded value to add \p CStr to it /// /// \return \p Interim updated with \p CStr constexpr atom_t atomValue(const char *CStr, atom_t Interim = 0xF) { return (*CStr == '\0') ? Interim : atomValue(CStr + 1, nextInterim(Interim, static_cast(*CStr))); } } // End namespace -/// Converts a \c rosa::AtomValue into \c std::string. -/// -/// \param What value to convert -/// -/// \return \c std::string encoded in \p What -std::string to_string(const AtomValue &What); - /// Converts a \c std::string into a \c rosa::AtomValue. /// /// \param S \c std::string to convert /// /// \return \c rosa::AtomValue representing \p S AtomValue atom_from_string(const std::string &S); /// Converts a string-literal into a \c rosa::AtomValue. /// /// \tparam Size the length of \p Str /// /// \param Str the string-literal to convert /// /// \return \c rosa::AtomValue representating \p Str /// /// \pre \p Str is not too long:\code /// Size <= MaxAtomLength + 1 /// \endcode template constexpr AtomValue atom(char const (&Str)[Size]) { // Last character is the NULL terminator. STATIC_ASSERT(Size <= MaxAtomLength + 1, "Too many characters in atom definition"); return static_cast(atomValue(Str)); } /// Lifts a \c rosa::AtomValue to a compile-time constant. /// /// \tparam V \c rosa::AtomValue to lift template struct AtomConstant { /// Constructor has to do nothing. constexpr AtomConstant(void) {} /// Returns the wrapped value. /// /// \return \p V constexpr operator AtomValue(void) const { return V; } /// Returns the wrapped value as of type \c rosa::atom_t. /// /// \return \c rosa::atom_t value from \p V static constexpr atom_t value() { return static_cast(V); } /// An instance *of this constant* (*not* a \c rosa::AtomValue). static const AtomConstant Value; }; // Implementation of the static member field \c rosa::AtomConstant::Value. template const AtomConstant AtomConstant::Value = AtomConstant{}; +} // End namespace rosa + +namespace std { + +/// Converts a \c rosa::AtomValue into \c std::string. +/// +/// \param What value to convert +/// +/// \return \c std::string encoded in \p What +string to_string(const rosa::AtomValue &What); + +/// Dumps a \c rosa::AtomValue to a given \c std::ostream. +/// +/// \param [in,out] OS output stream to dump to +/// \param A \c rosa::AtomValue to dump +/// +/// \return \p OS after dumping \p N to it +inline ostream &operator<<(ostream &OS, const rosa::AtomValue &A) { + OS << to_string(A); + return OS; +} + /// Converts a \c rosa::AtomConstant into \c std::string. /// /// \tparam V \c rosa::AtomValue to convert /// /// \note The actual argument of type `const rosa::AtomConstant` is ignored /// because the \c rosa::AtomValue to convert is encoded in the type itself. /// /// \return the original string encoded in \p V -template std::string to_string(const AtomConstant &) { +template string to_string(const rosa::AtomConstant &) { return to_string(V); } -} // End namespace rosa +/// Dumps a \c rosa::AtomConstant to a given \c std::ostream. +/// +/// \tparam V the \c rosa::AtomValue to dump +/// +/// \param [in,out] OS output stream to dump to +/// \param A \c rosa::AtomConstant providing \p V +/// +/// \return \p OS after dumping \p V to it +template +inline ostream &operator<<(ostream &OS, const rosa::AtomConstant &A) { + (void)A; // Shut compiler about unused parameter. + OS << to_string(V); + return OS; +} + +} // End namespace std #endif // ROSA_SUPPORT_ATOM_HPP diff --git a/include/rosa/support/sequence.hpp b/include/rosa/support/sequence.hpp index abdc699..bf61487 100755 --- a/include/rosa/support/sequence.hpp +++ b/include/rosa/support/sequence.hpp @@ -1,56 +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 +/// \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; } // End namespace rosa #endif // ROSA_SUPPORT_SEQUENCE_HPP diff --git a/include/rosa/support/squashed_int.hpp b/include/rosa/support/squashed_int.hpp index 48df20a..2d69d4b 100644 --- a/include/rosa/support/squashed_int.hpp +++ b/include/rosa/support/squashed_int.hpp @@ -1,132 +1,140 @@ //===-- rosa/support/squashed_int.hpp ---------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/squashed_int.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Facilities for squashing integer types into standard equivalents. /// /// \note This implementation is partially based on the \c squashed_int /// implementation of CAF. /// \todo Check license. /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_SQUASHED_INT_HPP #define ROSA_SUPPORT_SQUASHED_INT_HPP #include "rosa/support/type_list.hpp" #include "rosa/support/type_pair.hpp" namespace rosa { /// Compile-time list of integer types. /// /// \note This list is used to select a proper type as \c rosa::type_nr_t, /// always make sure that \c rosa::type_nr_t remains correct whenever changing /// the list. using IntegerTypesBySize = TypeList< // bytes none_t, // 0 TypePair, // 1 TypePair, // 2 none_t, // 3 TypePair, // 4 none_t, // 5 none_t, // 6 none_t, // 7 TypePair // 8 >; -/// Squashes integer types into \c [u]int_[8|16|32|64]_t equivalents. +/// Squashes integral types (except \c bool) into \c [u]int_[8|16|32|64]_t +/// equivalents. /// /// The squashed type for a type \c T can be obtained as \code /// typename SquashedInt::Type /// \endcode /// -/// \tparam T the integer type to squash +/// \tparam T the integral type to squash /// /// \pre \p T is an integral type:\code /// std::is_integral::value /// \endcode template struct SquashedInt { - STATIC_ASSERT((std::is_integral::value), "squashing a non-integral type"); + STATIC_ASSERT((std::is_integral::value && !std::is_same::value), + "squashing a non-integral type or bool"); using TPair = typename TypeListAt::Type; using Type = typename std::conditional::value, typename TPair::First, typename TPair::Second>::type; }; /// Convenience alias for obtaining a squashed integer type. template using squashed_int_t = typename SquashedInt::Type; /// \defgroup SquashedType Implementation for squashing types /// /// \brief Squashes a type. /// /// The squashed type for a type \c T can be obtained as \code /// typename SquashedType::Type /// \endcode /// The resulting type is squashed with \c rosa::SquashedInt if \c T is -/// integral, and remains \p T otherwise. +/// integral but not \c bool, and remains \p T otherwise. ///@{ /// Definition for the general case, when squashing a non-integral type. /// /// \tparam T the type to squash /// \tparam IsIntegral Always use the default value! template ::value> struct SquashedType { using Type = T; }; /// Specialization for the case when squashing an integral type. /// /// \tparam T the type to squash template struct SquashedType { using Type = squashed_int_t; }; +/// Specialization for the type \c bool. +/// +/// \note The type \c bool is an integral type and would be squashed by the +/// general case to \c uint8_t without this specialization. +template <> struct SquashedType { using Type = bool; }; + ///@} /// Convenience alias for obtaining a squashed type. template using squashed_t = typename SquashedType::Type; /// \defgroup SquashedTypeList Implementation for squashing lists of types /// /// \brief Squashes a \c rosa::TypeList elementwise. /// /// Replaces all types in a \c rosa::TypeList with their corresponding squashed /// types by using \c rosa::SquashedType. The squashed \c rosa::TypeList /// corresponding to \c List can be obtained as \code /// typename SquashedTypeList::Type /// \endcode ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to squash template struct SquashedTypeList; // Specialization for \c rosa::EmptyTypeList. template <> struct SquashedTypeList { using Type = EmptyTypeList; }; /// Specialization for non-empty \c rosa::TypeList. template struct SquashedTypeList> { using Type = typename TypeListPush< squashed_t, typename SquashedTypeList>::Type>::Type; }; ///@} } // End namespace rosa #endif // ROSA_SUPPORT_SQUASHED_INT_HPP diff --git a/include/rosa/support/tokenized_storages.hpp b/include/rosa/support/tokenized_storages.hpp index 0f5bdbf..dca7f4e 100755 --- a/include/rosa/support/tokenized_storages.hpp +++ b/include/rosa/support/tokenized_storages.hpp @@ -1,510 +1,620 @@ //===-- rosa/support/tokenized_storages.hpp ---------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/tokenized_storages.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Definition of storage helper template for storing values in a /// type-safe way based on type tokens. /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_TOKENIZED_STORAGES_HPP #define ROSA_SUPPORT_TOKENIZED_STORAGES_HPP #include "rosa/support/type_token.hpp" #include #include namespace rosa { /// Defines a simple interface for storing and accessing values of different /// types. /// /// While the interface provides features to access values and know their /// types, it is the users responsibility to use particular values according to /// their actual types. No facilities for type-safe access of values is /// provided by the class. /// /// \see \c rosa::TokenizedStorage for a type-safe specialization of the /// interface. class AbstractTokenizedStorage { protected: /// Protected constructor restricts instantiation for derived classes. AbstractTokenizedStorage(void) noexcept = default; public: /// No copying and moving of \c rosa::AbstractTokenizedStorage instances. ///@{ AbstractTokenizedStorage(const AbstractTokenizedStorage&) = delete; AbstractTokenizedStorage &operator=(const AbstractTokenizedStorage&) = delete; AbstractTokenizedStorage(AbstractTokenizedStorage&& Other) = delete; AbstractTokenizedStorage &operator=(AbstractTokenizedStorage&&) = delete; ///@} /// Destroys \p this object. virtual ~AbstractTokenizedStorage(void) noexcept = default; /// Tells how many values are stored in \p this object. /// /// \return number of values stored in \p this object virtual size_t size(void) const noexcept = 0; /// Tells the type of the value stored at a position. /// /// \param Pos the index of the value whose type is to returned /// /// \return \c rosa::TypeNumber for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < size() /// \endcode - virtual TypeNumber typeAt(const size_t Pos) const noexcept = 0; + virtual TypeNumber typeAt(const token_size_t Pos) const noexcept = 0; /// Provides an untyped pointer for the value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < size() /// \endcode - virtual void *pointerTo(const size_t Pos) noexcept = 0; + virtual void *pointerTo(const token_size_t Pos) noexcept = 0; /// Provides a constant untyped pointer for the value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return constant untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < Offsets.size() /// \endcode - virtual const void *pointerTo(const size_t Pos) const noexcept = 0; + virtual const void *pointerTo(const token_size_t Pos) const noexcept = 0; }; /// Template class storing values and providing dynamic type-safe access to /// them in a lightweight way based on type tokens. /// /// \see rosa/support/type_token.hpp /// /// \tparam Types types whose values are to be stored template class TokenizedStorage; +/// \defgroup TokenizedStorageForTypeList Implementation of +/// rosa::TokenizedStorageForTypeList +/// +/// \brief Transforms a \c rosa::TypeList instance to the corresponding +/// \c rosa::TokenizedStorage instance. +/// +/// A \c rosa::TypeList \c List instance can be turned into a corresponding \c +/// rosa::TokenizedStorage instance as \code +/// typename TokenizedStorageForTypeList::Type +/// \endcode +/// +/// For example, the following expression evaluates to `true`: \code +/// std::is_same>::Type, +/// TokenizedStorage>::value +/// \endcode +///@{ + +/// Declaration of the template. +/// +/// \tparam List \c rosa::TypeList to transform +template struct TokenizedStorageForTypeList; + +/// Implementation of the template for \c rosa::TypeList instances. +template +struct TokenizedStorageForTypeList> { + using Type = TokenizedStorage; +}; + +///@} + /// Nested namespace with implementation for \c rosa::TokenizedStorage, consider /// it private. namespace { /// Initializes a pre-allocated memory area with values from constant lvalue /// references. /// /// \tparam Types types whose values are to be stored /// /// \param Arena pre-allocated memory area to store values to /// \param Ts the values to store in \p Arena /// /// \note \p Arena needs to be a valid pointer to a memory area big enough for /// values of \p Types. template inline void createArenaElements(void *const Arena, const Types &... Ts) noexcept; /// \defgroup createLvalueArenaElement Implementation of creating lvalue arena elements /// /// Stores values from constant lvalue references into a pre-allocated memory /// area. /// /// \note To be used by the implementation of \c createArenaElements. /// /// \todo Document these functions. ///@{ /// \note This terminal case is used for both constant lvalue references and /// value references. template inline void createArenaElement(void *const, const std::vector &Offsets) { ASSERT(Pos == Offsets.size()); } template inline void createArenaElement(void *const Arena, const std::vector &Offsets, const Type &T, const Types &... Ts) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); new (static_cast(static_cast(static_cast(Arena) + Offsets[Pos]))) Type(T); createArenaElement(Arena, Offsets, Ts...); } template inline void createArenaElement(void *const Arena, const std::vector &Offsets, const AtomConstant &, const Types &... Ts) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); *static_cast( static_cast(static_cast(Arena) + Offsets[Pos])) = V; createArenaElement(Arena, Offsets, Ts...); } ///@} /// Implementation of the template. /// -/// \tparam Type the type of the mandatory first value to store -/// \tparam Types types of any further values to store +/// \tparam Types types of values to store /// /// \param Arena pre-allocated memory area to store values to -/// \param T the first value to store in \p Arena˛ -/// \param Ts optional further values to store in \p Arena +/// \param Ts values to store in \p Arena /// /// \pre \p Arena is not \p nullptr. -template -inline void createArenaElements(void *const Arena, const Type &T, +template +inline void createArenaElements(void *const Arena, const Types &... Ts) noexcept { ASSERT(Arena != nullptr); - createArenaElement<0>(Arena, TokenizedStorage::Offsets, T, - Ts...); + createArenaElement<0>(Arena, TokenizedStorage::Offsets, Ts...); } /// Initializes a pre-allocated memory area with values from rvalue references. /// /// \tparam Types types whose values are to be stored /// /// \param Arena pre-allocated memory area to store values to /// \param Ts the values to store in \p Arena /// /// \note \p Arena needs to be a valid pointer to a memory area big enough for /// values of \p Types. template inline void createArenaElements(void *const Arena, Types &&... Ts) noexcept; /// \defgroup createRvalueArenaElement Implementation of creating rvalue arena elements /// /// Stores values from rvalue references into a pre-allocated memory area. /// /// \note To be used by the implementation of \c createArenaElements. /// /// \todo Document these functions. ///@{ template inline void createArenaElement(void *const Arena, const std::vector &Offsets, Type &&T, Types &&... Ts) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); new (static_cast(static_cast( static_cast(Arena) + Offsets[Pos]))) Type(std::move(T)); createArenaElement(Arena, Offsets, std::move(Ts)...); } template inline void createArenaElement(void *const Arena, const std::vector &Offsets, AtomConstant &&, Types &&... Ts) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); *static_cast( static_cast(static_cast(Arena) + Offsets[Pos])) = V; createArenaElement(Arena, Offsets, std::move(Ts)...); } ///@} /// Implementation of the template. /// -/// \tparam Type the type of the mandatory first value to store -/// \tparam Types types of any further values to store +/// \tparam Types types of values to store /// /// \param Arena pre-allocated memory area to store values to -/// \param T the first value to store in \p Arena -/// \param Ts optional further values to store in \p Arena +/// \param Ts values to store in \p Arena /// /// \pre \p Arena is not \c nullptr. -template -inline void createArenaElements(void *const Arena, Type &&T, - Types &&... Ts) noexcept { +template +inline void createArenaElements(void *const Arena, Types &&... Ts) noexcept { ASSERT(Arena != nullptr); - createArenaElement<0>(Arena, TokenizedStorage::Offsets, - std::move(T), std::move(Ts)...); + createArenaElement<0>(Arena, TokenizedStorage::Offsets, + std::move(Ts)...); } /// Destroys values allocated by \c createArenaElements. /// -/// \tparam Type type of the mandatory first value stored in \p Arena -/// \tparam Types futher types whose values are stored in \p Arena +/// \tparam Types types whose values are stored in \p Arena /// /// \param Arena the memory area to destroy values from /// /// \note \p Arena needs to be a valid pointer to a memory area where values of /// \p Types are stored. -template +template inline void destroyArenaElements(void *const Arena) noexcept; /// \defgroup destroyArenaElement Implementation of destroying arena elements /// /// Destroys values from a memory area. /// /// \note To be used by the implementation of \c destroyArenaElements. /// /// \todo Document these functions. ///@{ template inline void destroyArenaElement(void *const, const std::vector &Offsets) noexcept { ASSERT(Pos == Offsets.size()); } template inline void destroyArenaElement(void *const Arena, const std::vector &Offsets) noexcept { ASSERT(Arena != nullptr && Pos < Offsets.size()); static_cast( static_cast(static_cast(Arena) + Offsets[Pos])) ->~Type(); destroyArenaElement(Arena, Offsets); } ///@} /// Implementation of the template. /// -/// \tparam Type the type of the mandatory first value to destroy -/// \tparam Types types of any further values to destroy +/// \tparam Types types of values to destroy /// /// \param Arena the memory area to destroy values from /// /// \pre \p Arena is not \c nullptr. -template +template inline void destroyArenaElements(void *const Arena) noexcept { ASSERT(Arena != nullptr); - destroyArenaElement<0, Type, Types...>( - Arena, TokenizedStorage::Offsets); + destroyArenaElement<0, Types...>(Arena, TokenizedStorage::Offsets); } } // End namespace /// Implementation of the template \c rosa::TokenizedStorage as a /// specialization of \c rosa::AbstractTokenizedStorage. /// /// The class provides facilities for storing values and providing type-safe /// access to them. /// -/// \tparam Type type of the first mandatory value to store -/// \tparam Types of any further values to store -template -class TokenizedStorage : public AbstractTokenizedStorage { +/// \tparam Types types of values to store +template +class TokenizedStorage : public AbstractTokenizedStorage { public: /// \c rosa::Token for the stored values. - static constexpr Token ST = - TypeToken::type, - typename std::decay::type...>::Value; + static constexpr Token ST = TypeToken...>::Value; /// Byte offsets to access stored values in \c rosa::TokenizedStorage::Arena. static const std::vector Offsets; private: /// A BLOB storing all the values one after the other. void *const Arena; /// Generates byte offsets for accessing values stored in /// \c rosa::TokenizedStorage::Arena. /// /// \return \c std::vector containing byte offsets for accessing values stored /// in \c rosa::TokenizedStorage::Arena static std::vector offsets(void) noexcept { Token T = ST; // Need a mutable copy. - const size_t N = lengthOfToken(T); // Number of types encoded in \c T. - size_t I = 0; // Start indexing from position \c 0. + const token_size_t N = lengthOfToken(T); // Number of types encoded in \c T. std::vector O(N); // Allocate vector of proper size. - O[0] = 0; // First offset is always \c 0. - while (I < N - 1) { - ASSERT(I + 1 < O.size() && lengthOfToken(T) == N - I); - // Calculate next offset based on the previous one. - // \note The offset of the last value is stored at `O[N - 1]`, which is - // set when `I == N - 2`. Hence the limit of the loop. - O[I + 1] = O[I] + sizeOfHeadOfToken(T); - dropHeadOfToken(T), ++I; + // Do nothing for 0 elements. + if (N > 0) { + token_size_t I = 0; // Start indexing from position \c 0. + O[0] = 0; // First offset is always \c 0. + while (I < N - 1) { + ASSERT(I + 1 < O.size() && lengthOfToken(T) == N - I); + // Calculate next offset based on the previous one. + // \note The offset of the last value is stored at `O[N - 1]`, which is + // set when `I == N - 2`. Hence the limit of the loop. + O[I + 1] = O[I] + sizeOfHeadOfToken(T); + dropHeadOfToken(T), ++I; + } + ASSERT(I + 1 == O.size() && lengthOfToken(T) == 1); } - ASSERT(I + 1 == O.size() && lengthOfToken(T) == 1); return O; } public: /// Creates an instance with default values. /// - /// \note This constructor requires that all actual template arguments \c Type - /// and \c Types... are default constructible. + /// \note This constructor requires that all actual template arguments \p + /// Types... are default constructible. TokenizedStorage(void) noexcept : Arena(::operator new(sizeOfValuesOfToken(ST))) { ASSERT(Arena != nullptr); // Sanity check. - createArenaElements(Arena, Type(), Types()...); + createArenaElements(Arena, Types()...); } /// Creates an instance from constant lvalue references. /// - /// \param T the mandatory first value to store - /// \param Ts optional further values to store - TokenizedStorage(const Type &T, const Types &... Ts) noexcept + /// \param Ts values to store + TokenizedStorage(const std::decay_t &... Ts) noexcept : Arena(::operator new(sizeOfValuesOfToken(ST))) { ASSERT(Arena != nullptr); // Sanity check. - createArenaElements(Arena, T, Ts...); + createArenaElements(Arena, Ts...); } /// Creates an instance from rvalue references. /// - /// \param T the mandatory first value to store - /// \param Ts optional further values to store - TokenizedStorage(Type &&T, Types &&... Ts) noexcept + /// \param Ts values to store + TokenizedStorage(std::decay_t &&... Ts) noexcept : Arena(::operator new(sizeOfValuesOfToken(ST))) { ASSERT(Arena != nullptr); // Sanity check. - createArenaElements(Arena, std::move(T), std::move(Ts)...); + createArenaElements(Arena, std::move(Ts)...); } /// No copying and moving of \c rosa::TokenizedStorage instances. /// /// \note This restriction may be relaxed as moving should be easy to /// implement, only requires the possiblity to validate Arena pointer. ///@{ TokenizedStorage(const TokenizedStorage&) = delete; TokenizedStorage &operator=(const TokenizedStorage&) = delete; TokenizedStorage(TokenizedStorage&& Other) = delete; TokenizedStorage &operator=(TokenizedStorage&&) = delete; ///@} // Destroys \p this object. ~TokenizedStorage(void) { - destroyArenaElements(Arena); + destroyArenaElements...>(Arena); ::operator delete(Arena); } /// Tells how many values are stored in \p this object. /// /// \return number of values stored in \p this object size_t size(void) const noexcept override { return Offsets.size(); } /// Tells the type of the value stored at a position. /// /// \param Pos the index of the value whose type is to returned /// /// \return \c rosa::TypeNumber for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < size() /// \endcode - TypeNumber typeAt(const size_t Pos) const noexcept override { + TypeNumber typeAt(const token_size_t Pos) const noexcept override { ASSERT(Pos < size()); Token TT = ST; dropNOfToken(TT, Pos); return headOfToken(TT); } /// Provides an untyped pointer for the value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < size() /// \endcode - void *pointerTo(const size_t Pos) noexcept override { + void *pointerTo(const token_size_t Pos) noexcept override { ASSERT(Pos < size()); return static_cast(Arena) + Offsets[Pos]; } /// Provides a constant untyped pointer for the value stored at a position. /// /// \param Pos the index of the value to return an untyped pointer for /// /// \return constant untyped pointer for the value stored at index \p Pos /// /// \pre \p Pos is a valid index:\code /// Pos < Offsets.size() /// \endcode - const void *pointerTo(const size_t Pos) const noexcept override { + const void *pointerTo(const token_size_t Pos) const noexcept override { ASSERT(Pos < size()); return static_cast(Arena) + Offsets[Pos]; } /// Tells if the value stored at a given index is of a given type. /// /// \note Any \c rosa::AtomConstant is encoded in \c rosa::Token as /// the \c rosa::AtomValue wrapped into it. /// /// \tparam T type to match against /// /// \param Pos index the type of the value at is to be matched against \p Type /// /// \return if the value at index \p Pos of type \p T /// /// \pre \p Pos is a valid index:\code /// Pos < Offsets.size() /// \endcode template bool isTypeAt(const size_t Pos) const noexcept { ASSERT(Pos < size()); Token TT = ST; dropNOfToken(TT, Pos); return isHeadOfTokenTheSameType(TT); } /// Gives a reference of a value of a given type stored at a given index. /// /// \note The constant variant of the function relies on this implementation, /// the function may not modify \p this object! /// /// \tparam T type to give a reference of /// /// \param Pos index to set the reference for /// - /// \return reference of \p Type for the value stored at index \p Pos + /// \return reference of \p T for the value stored at index \p Pos /// /// \pre \p Pos is a valid index and the value at index \p Pos is of type /// \p T: /// \code /// Pos < Size && isTypeAt(Pos) /// \endcode - template Type &valueAt(const size_t Pos) noexcept { - ASSERT(Pos < size() && isTypeAt(Pos)); - return *static_cast(pointerTo(Pos)); + template T &valueAt(const token_size_t Pos) noexcept { + ASSERT(Pos < size() && isTypeAt(Pos)); + return *static_cast(pointerTo(Pos)); } /// Gives a constant reference of a value of a given type stored at a given /// index. /// /// \tparam T type to give a reference of /// /// \param Pos index to set the reference for /// - /// \return constant reference of \p Type for the value stored at index \p Pos + /// \return constant reference of \p T for the value stored at index \p Pos /// /// \pre \p Pos is a valid index and the value at index \p Pos is of type /// \p T: /// \code /// Pos < Size && isTypeAt(Pos) /// \endcode - template const Type &valueAt(const size_t Pos) const noexcept { + template + const T &valueAt(const token_size_t Pos) const noexcept { // \note Just use the non-const implementation as that does not modify // \p this object. return const_cast(this)->valueAt(Pos); } }; // Implementation of the static member field \c rosa::TokenizedStorage::Offsets. -template -const std::vector TokenizedStorage::Offsets = - TokenizedStorage::offsets(); +template +const std::vector + TokenizedStorage::Offsets = TokenizedStorage::offsets(); + +/// Specialization of the template \c rosa::TokenizedStorage for storing +/// nothing. +/// +/// \note The specialization implements the interface defined by \c +/// rosa::AbstractTokenizedStorage but most of the functions cannot be called +/// because nothing is stored in instances of the class. +template <> class TokenizedStorage<> : public AbstractTokenizedStorage { +public: + /// \c rosa::Token for the stored values. + static constexpr Token ST = TypeToken<>::Value; + + /// Byte offsets to access stored values in \c rosa::TokenizedStorage::Arena. + static const std::vector Offsets; + + /// Creates an instance. + TokenizedStorage(void) noexcept {} + + /// No copying and moving of \c rosa::TokenizedStorage instances. + /// + /// \note This restriction may be relaxed as moving should be easy to + /// implement, only requires the possiblity to validate Arena pointer. + ///@{ + TokenizedStorage(const TokenizedStorage &) = delete; + TokenizedStorage &operator=(const TokenizedStorage &) = delete; + TokenizedStorage(TokenizedStorage &&Other) = delete; + TokenizedStorage &operator=(TokenizedStorage &&) = delete; + ///@} + + // Destroys \p this object. + ~TokenizedStorage(void) {} + + /// Tells how many values are stored in \p this object. + /// + /// \return `0` + size_t size(void) const noexcept override { return 0; } + + /// Tells the type of the value stored at a position. + /// + /// \pre Do not call. + TypeNumber typeAt(const token_size_t) const noexcept override { + ASSERT(false); + return TypeNumber(0); + } + + /// Provides an untyped pointer for the value stored at a position. + /// + /// \pre Do not call. + void *pointerTo(const token_size_t) noexcept override { + ASSERT(false); + return nullptr; + } + + /// Provides a constant untyped pointer for the value stored at a position. + /// + /// \pre Do not call. + const void *pointerTo(const token_size_t) const noexcept override { + ASSERT(false); + return nullptr; + } + + /// Tells if the value stored at a given index is of a given type. + /// + /// \pre Do not call. + template bool isTypeAt(const size_t) const noexcept { + ASSERT(false); + return false; + } + + /// Gives a reference of a value of a given type stored at a given index. + /// + /// \tparam T type to give a reference of + /// \pre Do not call. + template T &valueAt(const token_size_t) noexcept { + ASSERT(false); + return *static_cast(nullptr); + } + + /// Gives a constant reference of a value of a given type stored at a given + /// index. + /// + /// \tparam T type to give a reference of + /// + /// \pre Do not call. + template const T &valueAt(const token_size_t) const noexcept { + // \note Just use the non-const implementation as that does not modify + // \p this object. + return *static_cast(nullptr); + } +}; } // End namespace rosa #endif // ROSA_SUPPORT_TOKENIZED_STORAGES_HPP diff --git a/include/rosa/support/type_list.hpp b/include/rosa/support/type_list.hpp index bb50be3..b3e8107 100644 --- a/include/rosa/support/type_list.hpp +++ b/include/rosa/support/type_list.hpp @@ -1,444 +1,468 @@ //===-- rosa/support/type_list.hpp ------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/type_list.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Facilities for types representing lists of types. /// /// \note This implementation is partially based on the \c type_list /// implementation of CAF. /// \todo Check license. /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_TYPE_LIST_HPP #define ROSA_SUPPORT_TYPE_LIST_HPP #include "rosa/support/debug.hpp" #include "rosa/support/types.hpp" #include namespace rosa { /// A list of types. /// /// \tparam Ts types to make a list of template struct TypeList { /// Constructor, needs to do nothing. constexpr TypeList(void) {} }; /// The empty \c rosa::Typelist. using EmptyTypeList = TypeList<>; /// \defgroup TypeListAtImpl Implementation of rosa::TypeListAt /// /// \brief Gets the type at index \p Pos from a list of types. /// /// \note Only to be used by the implementation of \c rosa::TypeListAt. ///@{ /// Declaration of the template. /// /// \tparam Pos index to take the element from /// \tparam Ts types template struct TypeListAtImpl; /// Definition for the general case when \p Pos is not \c 0 and there is type in /// the list. template struct TypeListAtImpl { using Type = typename TypeListAtImpl::Type; }; /// Specialization for the case when \p Pos is \c 0. template struct TypeListAtImpl<0, T, Ts...> { using Type = T; }; /// Specialization for the case when there is no more type. /// /// In this case, the found type is \c rosa::none_t. template struct TypeListAtImpl { using Type = none_t; }; ///@} /// \defgroup TypeListAt Definition of rosa::TypeListAt /// /// \brief Gets the element at index \p Pos of \p List. /// /// /// The type at index \c Pos in a \c rosa::TypeList \c List can be obtained as /// \code /// typename TypeListAt::Type /// \endcode /// /// \note The resulting type is \c rosa::none_t if \code /// TypeListSize::Value < Pos /// \endcode ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to take an element from /// \tparam Pos index to take the element from template struct TypeListAt; /// Implementation using \c rosa::TypeListAtImpl. template struct TypeListAt, Pos> { using Type = typename TypeListAtImpl::Type; }; ///@} /// \defgroup TypeListIndexOfImpl Implementation of rosa::TypeListIndexOf /// /// \brief Tells the index of the first occurence of a type in a list of types. /// /// \note Only to be used by the implementation of \c rosa::TypeListIndexOf. ///@{ /// Declaration of the template. /// /// \tparam Pos the number types already being checked from the beginning of the /// list /// \tparam X type to search for /// \tparam Ts remaining list of types template struct TypeListIndexOfImpl; /// Specialization for the case when the list is over. /// /// In this case, the found index is \c -1. template struct TypeListIndexOfImpl { static constexpr int Value = -1; }; /// Specialization for the case when the first type in the remaining list /// is a match. template struct TypeListIndexOfImpl { static constexpr int Value = Pos; }; /// Implementation for the general case when need to continue looking. template struct TypeListIndexOfImpl { static constexpr int Value = TypeListIndexOfImpl::Value; }; ///@} /// \defgroup TypeListIndexOf Definition of rosa::TypeListIndexOf /// /// \brief Tells the index of the first occurence of type in a /// \c rosa::TypeList. /// /// The index of the first occurence of type \c T in \c rosa::TypeList \c List /// can be obtained as \code /// TypeListIndexOf::Value /// \endcode /// /// \note The resulting index is \c -1 if \c T is not present in \c List. ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to search in /// \tparam T type to search for template struct TypeListIndexOf; /// Implementation of the template using \c rosa::TypeListIndexOfImpl. template struct TypeListIndexOf, T> { static constexpr int Value = TypeListIndexOfImpl<0, T, Ts...>::Value; }; ///@} /// \defgroup TypeListHead Implementation of rosa::TypeListHead /// /// \brief Gets the first element of a \c rosa::TypeList. /// /// The first element of a \c rosa::TypeList \c List can be obtained as \code /// typename TypeListHead::Type /// \endcode /// /// \note The resulting type is \c rosa::none_t if \c List is /// \c rosa::EmptyTypeList. ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to get the first element of template struct TypeListHead; /// Specialization for \c rosa::EmptyTypeList. /// /// In this case, the found type is \c rosa::none_t. template <> struct TypeListHead { using Type = none_t; }; /// Implementation for a non-empty \c rosa::TypeList. template struct TypeListHead> { using Type = T; }; ///@} /// \defgroup TypeListTail Implementation of rosa::TypeListTail /// /// \brief Gets the tail of a \c rosa::TypeList. /// /// The tail of a \c rosa::TypeList \c List, that is \c List except for its /// first element, can be obtained as \code /// typename TypeListTail::Type /// \endcode /// /// \note If \c List is \c rosa::EmptyTypeList, then the resulting type is also /// \c rosa::EmptyTypeList. ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to take the tail of template struct TypeListTail; /// Specialization for \c rosa::EmptyTypeList. /// /// In this case, the resulting type is \c rosa::EmptyTypeList. template <> struct TypeListTail { using Type = EmptyTypeList; }; /// Implementation for a non-empty \c rosa::TypeList. template struct TypeListTail> { using Type = TypeList; }; ///@} /// \defgroup TypeListPush Implementation of rosa::TypeListPush /// /// \brief Extends a \c rosa::TypeList with a type. /// /// Whether the new type is pushed in the front or in the back of the /// \c rosa::TypeList depends on the order of template arguments, as shown in /// the following example: \code /// using List = TypeList /// typename TypeListPush::Type; // TypeList /// typename TypeListPush::Type; // TypeList /// \endcode ///@{ /// Declaration of the template. /// /// \tparam P a type if \p Q is a \c rosa::TypeList, a \c rosa::TypeList /// otherwise /// \tparam Q a type if \p P is a \c rosa::TypeList, a \c rosa::TypeList /// otherwise template struct TypeListPush; /// Implementation for the case when pushing at the back of the /// \c rosa::TypeList. template struct TypeListPush, T> { using Type = TypeList; }; /// Implementation for the case when pushing to the front of the /// \c rosa::TypeList. template struct TypeListPush> { using Type = TypeList; }; ///@} /// \defgroup TypeListDrop Implementation of rosa::TypeListDrop /// /// \brief Drops some elements from the beginning of a \c rosa::TypeList. /// /// The first \c N types of a \c rosa::TypeList \c List can be dropped as \code /// typename TypeListDrop::Type /// \endcode ///@{ /// Declaration of the template. /// /// \tparam N number of types to drop /// \tparam List \c rosa::TypeList to drop the first \p N element of template struct TypeListDrop; /// Specialization for \c rosa::EmptyTypeList. template struct TypeListDrop { using Type = EmptyTypeList; }; /// Implementation for a non-empty \c rosa::TypeList. template struct TypeListDrop> { using Type = typename std::conditional< - N == 0, TypeList, + N == 0, TypeList, typename TypeListDrop>::Type>::type; }; ///@} +/// \defgroup TypeListConcat Implementation of rosa::TypeListConcat +/// +/// \brief Concatenates two \c rosa::TypeList instances. +/// +/// Two instances of \c rosa::TypeList \c List1 and \c List2 can be +/// concatenated as \code +/// typename TypeListConcat::Type +/// \endcode +///@{ + +/// Declaration of the template +/// +/// \tparam List1 the first instance of \c rosa::TypeList +/// \tparam List2 the second instance of \c rosa::TypeList +template struct TypeListConcat; + +/// Implementation of the template for \c rosa::TypeList instances. +template +struct TypeListConcat, TypeList> { + using Type = TypeList; +}; + +///@} + /// \defgroup TypeListSize Implementation of rosa::TypeListSize /// /// \brief Tells the number of types stored in a \c rosa::TypeList. /// /// The size of a \c rosa::TypeList \c List can be obtained as \code /// TypeListSize::Value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to get the size of template struct TypeListSize; /// Implementation of the template. template struct TypeListSize> { static constexpr size_t Value = sizeof...(Ts); }; template constexpr size_t TypeListSize>::Value; ///@} /// Tests whether a \c rosa::TypeList is empty. /// /// \tparam List \c rosa::TypeList to check template struct TypeListEmpty { /// Denotes whether \p List is an empty \c rosa::TypeList or not. static constexpr bool Value = std::is_same::value; }; /// \defgroup TypeListContains Implementation of rosa::TypeListContains /// /// \brief Tells if a \c rosa::TypeList contains a given type. /// /// Whether a \c rosa::TypeList \c List contains the type \c T can be checked as /// \code /// TypeListContains::Value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam List \c rosa::TypeList to search in /// \tparam T type to search for template struct TypeListContains; /// Implementation of the template. template struct TypeListContains, T> { static constexpr bool Value = std::conditional, T>::Value == -1, std::false_type, std::true_type>::type::value; }; ///@} /// \defgroup TypeListSubsetOf Implementation of rosa::TypeListSubsetOf /// /// \brief Tells if a \c rosa::TypeList is a subset of another one. /// /// Whether a \c rosa::TypeList \c ListA is a subset of another /// \c rosa::TypeList \c ListB can be checked as \code /// TypeListSubsetOf::Value /// \endcode ///@{ /// Declaration of the template. /// /// \tparam ListA \c rosa::TypeList to check if is a subset of \p ListB /// \tparam ListB \c rosa::TypeList to check if is a superset of \p ListA /// \tparam Fwd always use the default value! template struct TypeListSubsetOf; /// Specialization for the case when all the elements of the original \p ListA /// was found in \p ListB. template struct TypeListSubsetOf { static constexpr bool Value = true; }; /// Specializaton for the case when an element of the original \p ListA cannot /// be found in \p ListB. template struct TypeListSubsetOf { static constexpr bool Value = false; }; /// Definition for the general case. template struct TypeListSubsetOf, List> : TypeListSubsetOf, List, TypeListContains::Value> {}; ///@} /// \defgroup TypeListFindImpl Implementation of rosa::TypeListFind /// /// \brief Finds the first type in a list of types that satisfies a predicate. /// /// \note Only to be used by the implementation of \c rosa::TypeListFind. ///@{ /// Declaration of the template. /// /// \tparam Pred the predicate to check types against /// \tparam Ts list of types to check template