diff --git a/CMakeLists.txt b/CMakeLists.txt index 846833f..d284dd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,186 +1,181 @@ cmake_minimum_required(VERSION 2.8.8) # 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_TOOLS_BINARY_DIR ${ROSA_RUNTIME_OUTPUT_INTDIR}) set(ROSA_LIBRARY_DIR ${ROSA_LIBRARY_OUTPUT_INTDIR}) set(ROSA_INCLUDE_DIR ${ROSA_BINARY_DIR}/include) # 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.") # 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 ) # They are not referenced. See set_output_directory(). set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ROSA_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ROSA_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${ROSA_BINARY_DIR}/lib) # Set include directories set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(${ROSA_INCLUDE_DIR} ${ROSA_MAIN_INCLUDE_DIR}) -# Set extra compiler options for Visual Studio -if ( MSVC ) - add_definitions(-EHsc) # Use Exception Handling -endif() - # 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) 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 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_TOOLS_BINARY_DIR}" "\nLibrary path: ${ROSA_LIBRARY_DIR}" "\nGenerator: ${CMAKE_GENERATOR}" "\n" "\n===========================================================\n") diff --git a/cmake/modules/HandleROSAOptions.cmake b/cmake/modules/HandleROSAOptions.cmake index 98102e8..c64f194 100644 --- a/cmake/modules/HandleROSAOptions.cmake +++ b/cmake/modules/HandleROSAOptions.cmake @@ -1,171 +1,204 @@ # 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() # 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++14" CXX_SUPPORTS_CXX14) - if ( CXX_SUPPORTS_CXX14 ) - append("-std=c++14" 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++14 support but the '-std=c++14' flag isn't supported.") + 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". - append("/Wall /WX" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) + # 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 -fno-exceptions". - append("/GR- /EHs-c-" CMAKE_CXX_FLAGS) + # 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 ) - # Clang is surely a new version, must know about these flags. - append("-Xclang -std=c++14" CMAKE_CXX_FLAGS) - append("-Xclang -std=c11" CMAKE_C_FLAGS) + + 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 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() - # No need to select C/C++ standard otherwise? 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 ) diff --git a/docs/Build.rst b/docs/Build.rst index 079af7d..6b84461 100755 --- a/docs/Build.rst +++ b/docs/Build.rst @@ -1,353 +1,318 @@ =========================== Building the RoSA Framework =========================== .. contents:: :local: +.. _build_deps: + Build Dependencies ================== In order to build RoSA, the following tools are required: * `CMake `_ (minimum version 3.6.0 if clang-tidy is used, 2.8.8 otherwise); * Build system of your choice that can be targeted by CMake -- including a - compiler supporting *C++14*. + compiler supporting *C++17*. - * Clang/LLVM -- minimum version 3.9.0 - [#f-clang_template_argument_deduction_DeluxeAgent]_ + * Clang/LLVM -- minimum version 5.0.0 + * Visual Studio 2017 -- minimum version 15.7 The following additional tools are required to generate documentation: * `Doxygen `_ -- for generating API documentation; * `Graphviz `_ -- not necessary, but the API documentation has nicer graphics when `dot` is available; * `Sphinx `_ (with *Python 2*) -- for generating documentation. The following additional tools are required to check and possibly enforce coding standard: * `clang-tidy `_ * `clang-format `_ General notes ============= * The framework is delivered with a CMake project, which can be used to generate build projects to build the framework. * The provided CMake project supports out-of-tree builds only, i.e. one must use a separate build directory outside of the RoSA source directory. .. _doxygen-warnings: * Doxygen warnings -- typically indicating actual errors -- are printed to `stderr`, do check that when generating API documentation. .. _cmake-variables: CMake Variables =============== Beyond the usual CMake variables, the following project-related options are available: `ROSA_INCLUDE_TOOLS` Generate build targets for RoSA tools. The tools are also built when the option is set to `ON`, which is the default setting. `ROSA_INCLUDE_EXAMPLES` Generate build targets for RoSA examples. The examples are also built when the option is set to `ON`, which is the default setting. `ROSA_ENABLE_PEDANTIC` Compile the framework with using `-pedantic` for GCC-compatible compilers, otherwise ignored. The option defaults to `ON`. `ROSA_ENABLE_ASSERTIONS` Enable assertions for non-Debug builds, which defaults to `OFF`. Note that assertions are always enabled for Debug builds and this option is ignored for such builds. .. _CMake_clang_tidy: `ROSA_ENABLE_CLANG_TIDY` Run *clang-tidy* checks when building RoSA, which defaults to `OFF`. When the variable is enabled, build targets created by *Makefile* and *Ninja* generators include calls for clang-tidy. Other generators ignore this option. Note that CMake variables `CMAKE__CLANG_TIDY` are set when enabled. Settings for clang-tidy are defined by the file `.clang-tidy` in the RoSA source directory. Consider the following options when the option is enabled: `ROSA_CLANG_TIDY_PATH` Custom path for `clang-tidy` executable. In order to use clang-tidy, CMake needs to find the `clang-tidy` executable. If `clang-tidy` to be used is available via `PATH`, just leave the option empty -- which is default. Set the absolute path of the directory containing the `clang-tidy` executable otherwise, in which case no default path is searched for `clang-tidy`. `ROSA_CLANG_TIDY_FIX` Apply suggested clang-tidy fixes to the sources, which defaults to `OFF`. Enable the option only if you know what you are doing. .. _CMake_clang_format: `ROSA_INCLUDE_CLANG_FORMAT` **[experimental]** Generate build target -- `format-rosa` -- for formatting RoSA sources with *clang-format*, which defatuls to `OFF`. When the variable is enabled and *CMake is not running on a Windows host*, a build target is generated which can be used to have all the RoSA sources formatted with clang-format. Settings for clang-format are defined by the file `.clang-format` in the RoSA source directory. Note that executing build target `format-rosa` will reformat all the source files *inplace*. Consider the following option when a build target for clang-format is to be generated: `ROSA_CLANG_FORMAT_PATH` Custom path for `clang-format` executable. In order to use clang-format, CMake needs to find the `clang-format` executable. If `clang-format` to be used is available via `PATH`, just leave the option empty -- which is default. Set the absolute path of the directory containing the `clang-format` executable otherwise, in which case no default path is search for `clang-format`. `ROSA_LOG_LEVEL` Level of logging to be used, use one of the following valid integer values. ======== ========= Variable Log Level ======== ========= `0` `ERROR` `1` `WARNING` `2` `INFO` `3` `DEBUG` `4` `TRACE` `5` *disabled* ======== ========= Level of logging defaults to *disabled*. `ROSA_INCLUDE_DOCS` Generate build targets for RoSA documentation, defaults to `ON`. Note that the automatic execution of the generated build targets is controlled by the option `ROSA_BUILD_DOCS`. The actual documentations to build are controlled by the options `ROSA_ENABLE_DOXYGEN` and `ROSA_ENABLE_SPHINX`. `ROSA_BUILD_DOCS` Build RoSA documentation automatically as part of the build process. The option defaults to `OFF` and takes effect only if the option `ROSA_INCLUDE_DOCS` is enabled. .. _CMake_doxygen: `ROSA_ENABLE_DOXYGEN` Use *doxygen* to generate RoSA API documentation. The option defaults to `OFF` and takes effect only if the option `ROSA_INCLUDE_DOCS` is enabled. Doxygen documentation may be generated by executing build target `doxygen-rosa`, which is done as part of the default build process if `ROSA_BUILD_DOCS` is enabled. Doxygen must be available via `PATH` if the option is enabled. The following options are also available to tune doxygen: `ROSA_DOXYGEN_SVG` Use *svg* instead of *png* files for doxygen graphs. The option defaults to `OFF` and takes effect if the tool *dot* is available via `PATH` to be used to generated graph images. `ROSA_DOXYGEN_EXTERNAL_SEARCH` Enable doxygen external search, which defatuls to `OFF`. The following options need to be set if the option is enabled: `ROSA_DOXYGEN_SEARCHENGINE_URL` URL to use for external search. `ROSA_DOXYGEN_SEARCH_MAPPINGS` Doxygen Search Mappings. .. _CMake_sphinx: `ROSA_ENABLE_SPHINX` Use *Sphinx* to generate RoSA documentation. The option defaults to `OFF` and takes effect only if the option `ROSA_INCLUDE_DOCS` is enabled. Sphinx must be available via `PATH` if the option is enabled. The following options are also available to tune Sphinx: `SPHINX_OUTPUT_HTML` Output standalone HTML files. The option defaults to `ON`. Documentation may be generated by executing build target `docs-rosa-html`, which is done as part of the default build process if `ROSA_BUILD_DOCS` is enabled. `SPHINX_OUTPUT_MAN` Output man pages for RoSA tools. The option defaults to `ON`. Man pages may be generated by executing build target `docs-rosa-man`, which is done as part of the default build process if `ROSA_BUILD_DOCS` is enabled. `SPHINX_WARNINGS_AS_ERRORS` When building documentation, treat Sphinx warnings as errors. The option defaults to `ON`. Building RoSA Step-by-Step ========================== Building on Linux with Make --------------------------- Configuring and building the framework on Linux using *Make* is a straightforward process which does not require performing any tricks. Set C and C++ compilers with the variables `CC` and `CXX`, respectively. Use the CMake variable `CMAKE_BUILD_TYPE` to set the type of build: `Debug`, `Release`. Follows an example on building the framework with all options turned on. CMake variables may be skipped as necessary. You need to have RoSA sources on your computer.:: rosa-src$ cd .. $ mkdir rosa-build $ cd rosa-build rosa-build$ CC= CXX= -G "Unix Makefiles" -DROSA_ENABLE_CLANG_TIDY=ON -DROSA_CLANG_TIDY_PATH= -DROSA_INCLUDE_CLANG_FORMAT=ON -DROSA_CLANG_FORMAT_PATH= -DROSA_LOG_LEVEL=4 -DROSA_ENABLE_DOXYGEN=ON -DROSA_DOXYGEN_SVG=ON -DROSA_ENABLE_SPHINX=ON -DCMAKE_BUILD_TYPE=Debug ../rosa-src [CMake configures and generates without errors] $ make [Make builds the project] You just need to re-run Make in order to re-build the project after changing the source code and the CMake project. In case the CMake project is changed, Make automatically calls CMake to update the build project. In order to build documentation and enforce coding standard, refer to corresponding :ref:`cmake-variables`. .. _Build_VS: Building on Windows with Visual Studio -------------------------------------- -Unfortunately, the native MSVC compiler cannot compile the framework. One needs -to use Clang with Visual Studio in order to build the framework. - -Microsoft recently started to bundle a special version of Clang to Visual Studio -as the *Clang/C2* module. That compiler is, however, several versions behind the -official LLVM releases as of Visual Studio 2017. Therefore, it is necessary to -use *LLVM for Windows* as an external toolset for Visual Studio. - -For using *LLVM for Windows*, one downloads the official binary release from -http://llvm.org and consults with its documentation. The release provides -integration for Visual Studio starting from Visual Studio 2010. - -Prepare your toolchain like this: - -#. Install Visual Studio. - - * If installing Visual Studio 2017, make sure the component - *VC++ 2015.3 v140 toolset (x86,x64)* on the *Individual components* tab is - selected in *Visual Studio Installer*. Installing the older *v140 toolset* - is necessary because the LLVM integration (as of version 4.0.1) does not - support the *v141 toolset*, which is default for Visual Studio 2017. - * Otherwise, default installation of Visual Studio should go. +This is how to use the native MSVC compiler to build RoSA. -#. Install *LLVM for Windows*. -#. Install the integration by executing as *Administrator*:: - - > \tools\msbuild\install.bat - -Having your build system prepared and RoSA sources fetched to your computer, -configure and build the framework like this: +Having your build system prepared (see `Build Dependencies`_) and RoSA sources +fetched to your computer, configure and build the framework like this: #. Generate Visual Studio solution with CMake: #. Start CMake. #. Define *source directory* and a separate *build directory* in CMake. #. Click *Configure*. #. Select the proper *generator* for your version of Visual Studio. - #. Define your optional toolset (argument for `-T`) as `LLVM-vs`. - - * Note that `` refers to the version of Visual Studio: `2010`, - `2012`, `2013`, and `2014`. Visual Studio 2017 with the *v140 toolset* - uses `2014`. - #. Click *Finish*. #. Tune CMake variables as you wish. * Note that Visual Studio Generators are multi-configuration generators, hence you cannot set `CMAKE_BUILD_TYPE`. You need to select a configuration to build in Visual Studio. #. Click *Generate*. #. Build the framework with Visual Studio: #. Open the generated `RoSA.sln` from the build directory with Visual Studio. #. Build the project `ALL_BUILD`. You just need to re-build the project in Visual Studio after changing the source code and the CMake project. In case the CMake project is changed, Visual Studio automatically calls CMake the update the build project. Build Result ============ The build process works in the build directory. After a successful build, one can find the following final outputs there -- besides some intermediate files. .. _Build_Result_Software: Software -------- In the build directory, `include` contains header files which are generated by CMake and provide configuration-specific information. The build process generates static libraries in `lib` and executables -- examples and tools -- in `bin`. Projects generated by a multi-configuration generator result in the actual libraries and executables being located in subdirectories corresponding to different build configurations. .. _Build_Result_Documentation: Documentation ------------- Documentation is generated in `docs`. The general documentation can be found in `docs/html`. Man pages for tools can be found in `docs/man`. The API documentation can be found in `docs/doxygen/html`. .. rubric:: Footnotes - -.. [#f-clang_template_argument_deduction_DeluxeAgent] - Clang breaks on template argument deduction when instantiating the class - `rosa::deluxe::DeluxeAgent`, the issue is gone with clang version 3.9.0 and - newer. Check the documentation on the relevant constructor for more details. diff --git a/docs/Dev.rst b/docs/Dev.rst index be05ab6..4da5fb0 100755 --- a/docs/Dev.rst +++ b/docs/Dev.rst @@ -1,357 +1,359 @@ ============================= 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`_. `tools` Contains `Tools`_ based on RoSA features. 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`_ and `Tools`_ using those `Libraries`_ are separated from the implementation of the RoSA features into different directories. 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. Tools ~~~~~ Tools, programs based on the RoSA libraries, are implemented in `tools`. .. _Coding_Standards: Coding Standards ---------------- -RoSA is implemented in standard *C++14* code. All the software sources are to +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`, and also update `lib/CMakeLists.txt` by adding or removing a `add_subdirectory` command for the library. 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 `examples` or `tools`, and also update `CMakeLists.txt` in the containing directory as for libraries. 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` [1]_. 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 .. [1] 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/docs/Issues.rst b/docs/Issues.rst index a0fd6bc..b3f6fd0 100755 --- a/docs/Issues.rst +++ b/docs/Issues.rst @@ -1,53 +1,53 @@ ================================================================== Known Issues with the Current Implementation of the RoSA Framework ================================================================== .. contents:: :local: TODO ==== * Project logo - `docs/_themes/rosa-theme/static/logo.png` * License? * Packaging with `CPack `_. * What about design documentation on the basics of RoSA? * What about testing the framework? Known Issues ============ -* CMake - - * VS2017 generates intermediate files for the `ZERO_CHECK` project out of the - build directory, see `CMake issue #16458`_. +The issues that are listed below are described assuming :ref:`minimal build +dependencies ` are met. * C++ - * Mangled names of function pointers with non-throwing exception specification - in function signature will change in C++17. That renders binaries generated - with C++14 and C++17 incompatible (for linking). + * The noexcept-specification is part of the function type since C++17 but + `std::function` is not defined for noexcept template arguments. So we cannot + enforce the expected noexcept semantics for functions stored in + `std::function`. See affected functions + `rosa::Invoker::wrap()`, `rosa::deluxe::DeluxeAgent::DeluxeAgent()`, + `rosa::deluxe::DeluxeSensor::DeluxeSensor()`, and + `rosa::deluxe::DeluxeSensor::registerSimulationDataSource()`. + +* MSVC - * Since version 4.0.0, Clang warns about this compatibility issue as part - of `-Wc++1z-compat`. That warning is turned off in the build scripts. - * The langauge standard for building RoSA libraries and applications needs - to be lockstepped: now use C++14 only and step to C++17 later when it is - properly supported by all major compilers. + * While :ref:`RoSA does not use exceptions `, we cannot + disable unwind semantics with MSVC because some included header files define + functions that use exceptions. * Doxygen * "Potential recursive class relation" is detected for `rosa::GenSeq`, which is true if one ignores the template specialization for the terminal case. It would be nice not to have this pointless warning. * clang-tidy * Clang-tidy reports warnings about `noexcept` marking for the move constructor and move assignment operator of `rosa::Optional` in some situations when the template with the non-specialized argument list is used -- for example, in the file `example/deluxe-interface/deluxe-interface.cpp`. However, the condition for the `noexcept` marking should be met and the warning is pointless. - -.. _CMake issue #16458: https://gitlab.kitware.com/cmake/cmake/issues/16458 diff --git a/examples/agent-modules/agent-modules.cpp b/examples/agent-modules/agent-modules.cpp index 04fce75..c559136 100644 --- a/examples/agent-modules/agent-modules.cpp +++ b/examples/agent-modules/agent-modules.cpp @@ -1,108 +1,108 @@ //===-- examples/agent-modules/agent-modules.cpp ----------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file examples/agent-modules/agent-modules.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::Module object as components. /// //===----------------------------------------------------------------------===// #include "rosa/agent/Abstraction.hpp" #include "rosa/agent/Confidence.hpp" #include "rosa/config/version.h" #include "rosa/core/Agent.hpp" #include "rosa/core/MessagingSystem.hpp" #include "rosa/support/log.h" #include "rosa/support/terminal_colors.h" #include using namespace rosa; using namespace rosa::agent; using namespace rosa::terminal; /// 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; Confidence C; RangeAbstraction A; 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'; } 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({{{10, 14}, Categories::Normal}, - {{15, 17}, Categories::Good}, - {{18, 19}, Categories::Normal}}, + 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) {} }; 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-modules 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}; 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 index 6f3b8e2..5e569ba 100755 --- a/examples/deluxe-interface/deluxe-interface.cpp +++ b/examples/deluxe-interface/deluxe-interface.cpp @@ -1,312 +1,312 @@ //===-- 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 /// /// \brief A simple example on the \c rosa::deluxe::DeluxeContext and related /// classes. //===----------------------------------------------------------------------===// #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; /// 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 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 << "deluxe-interface example" << 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("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 // 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, 28}, Emergency}, - {{28, 32}, High}, - {{32, 35}, Low}, - {{35, 38}, No}, - {{38, 39.5}, High}, - {{39.5, 100}, Emergency}}, + {{{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::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( [&ScoreWriter](std::pair Score) -> Optional { if (Score.second) { // The state of \p ScoreWriter is not checked, expecting good. ScoreWriter << Score.first; } return {}; })); // // Connect the high-level agent to the logger agent. // LOG_INFO("Connect the high-level agent to the logger agent."); C->connectAgents(LoggerAgent, 0, BodyAgent, "Body Agent Channel"); // // Do simulation. // LOG_INFO("Setting up and performing simulation."); // // Initialize deluxe context for simulation. // C->initializeSimulation(); // // Open CSV files and register them for their corresponding sensors. // // Type aliases for iterators. using CSVInt = csv::CSVFlatIterator; using CSVFloat = csv::CSVFlatIterator; 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/include/rosa/agent/History.hpp b/include/rosa/agent/History.hpp index b6d3877..1097ad0 100644 --- a/include/rosa/agent/History.hpp +++ b/include/rosa/agent/History.hpp @@ -1,292 +1,292 @@ //===-- 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 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 }; /// 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; public: /// Creates an instances by initializing the indices for the circular buffer. History(void) noexcept : Data(0), Space(0) {} /// Destroys \p this object. ~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; } /// 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; } /// 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; } /// 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; } /// 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 { 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]; } private: /// Tells if the circular buffer is full. /// /// \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 buffer is full. /// /// \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(); } } 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::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 /// \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 { STATIC_ASSERT((std::is_same::value), "not default template arg"); ASSERT(0 <= D && D < N); // 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 consequtive entries recorded + /// 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 /// \endcode template - typename std::enable_if::value, unsigned_t>::type + typename std::enable_if::value, size_t>::type averageAbsDiff(const size_t D = N - 1) const noexcept { STATIC_ASSERT((std::is_same::value), "not default template arg"); ASSERT(0 <= D && D < N); // 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. - unsigned_t Diffs = {}; // Init to zero. + 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; } } }; /// 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 { H.addEntry(V); return H; } } // End namespace agent } // End namespace rosa #endif // ROSA_AGENT_HISTORY_HPP diff --git a/include/rosa/core/Invoker.hpp b/include/rosa/core/Invoker.hpp index 32126ad..9f35ca3 100644 --- a/include/rosa/core/Invoker.hpp +++ b/include/rosa/core/Invoker.hpp @@ -1,256 +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 +/// \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; + 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; + 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. +/// \c std::function. template -class InvokerImpl> final +class InvokerImpl> final : public Invoker { /// Type alias for the stored function. - using function_t = std::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)); return result_t::Invoked; } else { LOG_TRACE("Tried to invoke with non-matching arguments"); return result_t::NoMatch; } } }; template template -void InvokerImpl>::invokeFunction( +void InvokerImpl>::invokeFunction( Seq, const args_t &Args) const noexcept { ASSERT(sizeof...(S) == std::tuple_size::value); // Sanity check. F(std::get(Args)...); } ///@} } // End namespace template Invoker::invoker_t -Invoker::wrap(std::function &&F) noexcept { +Invoker::wrap(std::function &&F) noexcept { return std::unique_ptr( - new InvokerImpl>(std::move(F))); + 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/System.hpp b/include/rosa/core/System.hpp index 7a5488d..b9869bc 100644 --- a/include/rosa/core/System.hpp +++ b/include/rosa/core/System.hpp @@ -1,240 +1,242 @@ //===-- rosa/core/System.hpp ------------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/core/System.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Declaration of *System* interface. /// //===----------------------------------------------------------------------===// #ifndef ROSA_CORE_SYSTEM_HPP #define ROSA_CORE_SYSTEM_HPP #include "rosa/config/config.h" #include "rosa/core/forward_declarations.h" #include "rosa/support/debug.hpp" #include "rosa/support/log.h" #include #include #include namespace rosa { /// Base interface for actual agent-systems. /// /// The class provides facilities to keep track of \c rosa::Unit instances owned /// by a \c rosa::System. /// /// \note Any subclass is supposed to provide thread-safe implementation. /// /// \note The class declares only an interface to avoid trouble with multiple /// inheritance in various subclasses as in derived interfaces and derived /// implementations. /// /// \note Actual implementations are supposed to derive from \c rosa::SystemBase /// implenenting a base feature-set. class System { public: /// Signature of creator functions for \c rosa::Unit instances. /// + /// \note The function is to be \c noexcept. + /// /// \tparam T type derived from \c rosa::Unit /// \tparam S type derived from \c rosa::System template - using UnitCreator = std::function; + using UnitCreator = std::function; /// Returns an object implementing the \c rosa::System interface. /// /// \param Name name of the new instance /// /// \return \c std::unique_ptr for a new instance of \c rosa::System static std::unique_ptr createSystem(const std::string &Name) noexcept; protected: /// Creates an instance. /// /// \note Protected constructor restricts instantiation for subclasses. System(void) noexcept = default; /// No copying and moving of \c rosa::System. ///@{ System(const System &) = delete; System(System &&) = delete; System &operator=(const System &) = delete; System &operator=(System &&) = delete; ///@} public: /// Destroys \p this object. /// /// \note Any implementation makes sure that a \c rosa::System can be /// destroyed only if it is marked *cleaned* /// \see \c rosa::System::isSystemCleaned virtual ~System(void) = default; /// Tells whether \p this object is the same as \p Other. /// /// \note Whenever checking equality of two objects, use the one with the /// more specialized static type on the left-hand side of the operator. The /// static type of the object on the right-hand side is better to be /// \c rosa::System, ambiguous conversion might happen otherwise. /// /// \param Other another \c rosa::System instance to compare to /// /// \return whether \p this object and \p Other is the same virtual bool operator==(const System &Other) const noexcept = 0; /// Tells whether \p this object is not the same as \p Other. /// /// \note Whenever checking inequality of two objects, use the one with the /// more specialized static type on the left-hand side of the operator. The /// static type of the object on the right-hand side is better to be /// \c rosa::System, ambiguous conversion might happen otherwise. /// /// \param Other another \c rosa::System instance to compare to /// /// \return whether \p this object and \p Other is not the same bool operator!=(const System &Other) const noexcept { return !operator==(Other); } protected: /// Tells the next unique identifier to be used for a newly created /// \c rosa::Unit. /// /// \return \c rosa::id_t which is unique within the context of \p this /// object. /// /// \note Never returs the same value twice. virtual id_t nextId(void) noexcept = 0; /// Tells if \p this object has been marked cleaned and is ready for /// destruction. /// /// \return if \p this object is marked clean. virtual bool isSystemCleaned(void) const noexcept = 0; /// Marks \p this object cleaned. /// /// \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 virtual void markCleaned(void) noexcept = 0; /// Registers a \c rosa::Unit instance to \p this object. /// /// \param U \c rosa::Unit to register /// /// \pre \p this object has not yet been marked as cleaned and \p U is not /// registered yet:\code /// !isSystemCleaned() && !isUnitRegistered(U) /// \endcode /// /// \post \p U is registered:\code /// isUnitRegistered(U) /// \endcode virtual void registerUnit(Unit &U) noexcept = 0; /// Unregisters and destroys a registered \c rosa::Unit instance. /// /// \param U \c rosa::Unit to destroy /// /// \pre \p U is registered:\code /// isUnitRegistered(U) /// \endcode /// /// \post \p U is not registered and also destroyed. virtual void destroyUnit(Unit &U) noexcept = 0; /// Tells if a \c rosa::Unit is registered in \p this object. /// /// \param U \c rosa::Unit to check /// /// \return whether \p U is registered in \p this object virtual bool isUnitRegistered(const Unit &U) const noexcept = 0; /// Creates a \c rosa::Unit instance with the given /// \c rosa::System::UnitCreator and registers the new instance. /// /// \tparam T type of the actual \c rosa::Unit to instantiate /// \tparam S type of the actual \c rosa::System instantiating /// /// \param C function creating an instance of type \p T /// /// \note \p S must be the actual subclass that wants to instantiate /// \c rosa::Unit. That cannot be statically enforced, it is the /// reponsibility of the caller to provide the proper \c rosa::System /// subclass. /// /// \pre Statically, \p T is a subclass of \c rosa::Unit and \p S is a /// subclass of \c rosa::System: /// \code /// std::is_base_of::value && std::is_base_of::value /// \endcode /// Dynamically, \p this object has not yet been marked cleaned:\code /// !isSystemCleaned() /// \endcode template T &createUnit(UnitCreator C) noexcept; public: /// Tells the name of \p this object /// /// \note The returned reference remains valid as long as \p this object is /// not destroyed. /// /// \return name of \p this object virtual const std::string &name(void) const noexcept = 0; /// Tells the number of \c rosa::Unit instances constructed in the context of /// \p this object so far, including those being already destroyed. /// /// \return number of \c rosa::Unit instances created so far virtual size_t numberOfConstructedUnits(void) const noexcept = 0; /// Tells the number of live \c rosa::Unit instances in the context \p this /// object, those being constructed and not destroyed yet. /// /// \return number of \c rosa::Unit instances alive virtual size_t numberOfLiveUnits(void) const noexcept = 0; /// Tells if \p this object has no live \c rosa::Unit instances. /// /// \return whether \p this object has any live \c rosa::Unit instances virtual bool empty(void) const noexcept = 0; }; template T &System::createUnit(UnitCreator C) noexcept { STATIC_ASSERT((std::is_base_of::value), "not a Unit"); STATIC_ASSERT((std::is_base_of::value), "not a System"); if (isSystemCleaned()) { ROSA_CRITICAL("Trying to create a Unit in a cleaned System '" + name() + "'"); } const id_t Id = nextId(); T *U = C(Id, static_cast(*this)); registerUnit(*U); LOG_TRACE("Unit created and registered '" + U->FullName + "'"); return *U; } } // End namespace rosa #endif // ROSA_CORE_SYSTEM_HPP diff --git a/include/rosa/deluxe/DeluxeAgent.hpp b/include/rosa/deluxe/DeluxeAgent.hpp index 720576f..0e83a7c 100755 --- a/include/rosa/deluxe/DeluxeAgent.hpp +++ b/include/rosa/deluxe/DeluxeAgent.hpp @@ -1,680 +1,673 @@ //===-- rosa/deluxe/DeluxeAgent.hpp -----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeAgent.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \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 /// Local helper macros to deal with built-in types. /// ///@{ /// Creates function name for member functions in \c rosa::deluxe::DeluxeAgent. /// /// \param N name suffix to use #define DAHANDLERNAME(N) handleSlave_##N /// Defines member functions for handling messages from *slaves* in /// \c rosa::deluxe::DeluxeAgent. /// /// \see \c DeluxeAgentInputHandlers /// /// \note No pre- and post-conditions are validated directly by these functions, /// they rather rely on \c rosa::deluxe::DeluxeAgent::saveInput to do that. /// /// \param T the type of input to handle /// \param N name suffix for the function identifier #define DAHANDLERDEFN(T, N) \ - void DAHANDLERNAME(N)(atoms::Slave, id_t Id, T Value) noexcept { \ - saveInput(Id, Value); \ + void DAHANDLERNAME(N)(atoms::Slave, id_t SlaveId, T Value) noexcept { \ + saveInput(SlaveId, Value); \ } /// Convenience macro for \c DAHANDLERDEFN with identical arguments. /// /// \see \c DAHANDLERDEFN /// /// This macro can be used instead of \c DAHANDLERDEFN if the actual value of /// \p T can be used as a part of a valid identifier. /// /// \param T the type of input to handle #define DAHANDLERDEF(T) DAHANDLERDEFN(T, T) /// Results in a \c THISMEMBER reference to a member function defined by /// \c DAHANDLERDEFN. /// /// Used in the constructor of \c rosa::deluxe::DeluxeAgent to initialize super /// class \c rosa::Agent with member function defined by \c DAHANDLERDEFN. /// /// \see \c DAHANDLERDEFN, \c THISMEMBER /// /// \param N name suffix for the function identifier #define DAHANDLERREF(N) THISMEMBER(DAHANDLERNAME(N)) ///@} namespace rosa { namespace deluxe { /// Specialization of \c rosa::Agent for *agent* role of the *deluxe interface*. /// /// \see \c rosa::deluxe::DeluxeContext /// /// \invariant All input-related container objects have a size matching /// \c rosa::deluxe::DeluxeAgent::NumberOfInputs, thus having a corresponding /// entry for each input. Types of input values are consistent throughout all /// the input-related containers. No *slave* is registered at more than one /// input position. *Slave* registrations and corresponding reverse lookup /// information are consistent. /// /// \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 A type of mandatory first input value - /// \tparam As types of further optional input values - template - using D = std::function(std::pair, - std::pair...) noexcept>; + /// \tparam As types of input values + template + using D = std::function(std::pair...)>; /// The type of values produced by \p this object. /// /// That is the type of values \p this object sends to its *master*. /// /// \see \c rosa::deluxe::DeluxeAgent::master const TypeNumber OutputType; /// Number of inputs processed by \p this object. const size_t NumberOfInputs; private: /// Types of input values produced by *slaves* of \p this object. /// /// \note The \c rosa::TypeNumber values stored here match the corresponding /// values in \c rosa::deluxe::DeluxeAgent::InputValues. /// /// \note The position of a type in the \c std::vector indicates which /// argument of \p this object's processing function it belongs to. See also /// \c rosa::deluxe::DeluxeAgent::D. const std::vector InputTypes; /// Indicates whether any particular input value has been changed since the /// last trigger received from the system. /// /// All the flags are reset to \c false upon handling a trigger and then set /// to \c true by \c rosa::deluxe::DeluxeAgent::saveInput when storing a new /// input value in \c rosa::deluxe::DeluxeAgent::InputValues. /// /// \note The position of a flag in the \c std::vector indicates which /// argument of \p this object's processing function it belongs to. See also /// \c rosa::deluxe::DeluxeAgent::D. std::vector InputChanged; /// Stores the actual input values. /// /// \note The types of stored values match the corresponding /// \c rosa::TypeNumber values in \c rosa::deluxe::DeluxeAgent::InputTypes. /// /// \note The position of a value in the \c rosa::AbstractTokenizedStorage /// indicates which argument of \p this object's processing function it is. /// See also \c rosa::deluxe::DeluxeAgent::D. const std::unique_ptr InputValues; /// Alias for function objects used as trigger handler for /// \c rosa::deluxe::DeluxeAgent. /// + /// \note The function used for \c H is to be \c noexcept. + /// /// \see \c rosa::deluxe::DeluxeAgent::FP - using H = std::function; + using H = std::function; /// Handles trigger from the system. /// /// The actual function processing *slave* inputs and generating optional /// output to *master* is captured in a lambda expression that is in turn /// wrapped in a \c std::function object. The lambda expression calls the /// processing function with the actual input data and sends its result -- if /// any -- to *master* by calling \c rosa::deluxe::DeluxeAgent::sendToMaster. /// Also, all the flags stored in \c rose::deluxe::DeluxeAgent::InputChanged /// are reset when the current input values are processed. The function /// \c rosa::deluxe::DeluxeAgent::handleTrigger needs only to call the /// function object. /// /// \see \c rosa::deluxe::DeluxeAgent::triggerHandlerFromProcessingFunction const H FP; /// The *master* to send values to. /// /// \note *Masters* are set dynamically, hence it is possible that a /// \c rosa::deluxe::DeluxeAgent instance does not have any *master* at a /// given moment. Optional Master; /// The *slaves* sending input to \p this object. /// /// \note The position of a *slave* in the \c std::vector indicates which /// argument of \p this object's processing function it belongs to. See also /// \c rosa::deluxe::DeluxeAgent::D. /// /// \note *Slaves* are set dynamically, hence it is possible that a /// \c rosa::deluxe::DeluxeAgent instance does have input positions without /// any *slave* associated to them. /// /// \note Reverse lookup information is maintained in /// \c rosa::deluxe::DeluxeAgent::SlaveIds, which is to be kept in sync with /// the *slaves* stored here. std::vector> Slaves; /// Associates \c rosa::id_t values to corresponding indices of registered /// *slaves*. /// /// \see \c rosa::deluxe::DeluxeAgent::Slaves std::map SlaveIds; /// Tells whether types \p As... match the input types of \p this object. /// /// \tparam As types to match against values in /// \c rosa::deluxe::DeluxeAgent::InputTypes /// /// \return if types \p As... match \c rosa::TypeNumber values stored in /// \c rosa::deluxe::DeluxeAgent::InputTypes template bool inputTypesMatch(void) const noexcept; /// Gives an \c std::tuple containing the current input values and their /// change flags so that they can be used for the processing function. /// /// \tparam As types of the input values /// \tparam S0 indices for accessing input values and their change flags /// /// \note The only argument provides indices statically as template arguments /// \p S0..., so its actual value is ignored. /// /// \return current input values and their change flags prepared for invoking /// the processing function with them /// /// \pre The type arguments \p As... match the input types of \p this object /// and the provided indices \p S0... constitute a proper sequence for /// accessing input values and their change flags: \code /// inputTypesMatch() && sizeof...(As) == sizeof...(S0) /// \endcode template std::tuple...> prepareCurrentInputs(Seq) const noexcept; /// Invokes a processing function matching the output and input types of /// \p this object with actual arguments provided in a \c std::tuple. /// /// \note \p Args providing the actual arguments for \p F is to be created by /// \c rosa::deluxe::DeluxeAgent::prepareCurrentInputs. /// /// \tparam T output type of the processing function - /// \tparam A type of the first mandatory input for the processing function - /// \tparam As types of further optional inputs for the processing function - /// \tparam S1 indices starting with `1` for extracting actual arguments from + /// \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 S1..., so its actual value is ignored. + /// \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 S1... prefixed with the value - /// `0` constitutes a proper sequence for extracting all actual arguments for + /// \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...(S1) + /// sizeof...(As) == sizeof...(S0) /// \endcode - template - static Optional - invokeWithTuple(D F, - std::tuple, std::pair...> Args, - Seq<0, S1...>) noexcept; + template + static Optional invokeWithTuple(D F, + std::tuple...> Args, + Seq) noexcept; /// Wraps a processing function into a trigger handler. /// /// \see \c rosa::deluxe::DeluxeAgent::FP /// /// \note The function cannot be const qualified because the lambda /// expression defined in it needs to capture \p this object by a non-const /// reference /// /// \tparam T type of output - /// \tparam A type of the first mandatory input value - /// \tparam As types of further optional input values + /// \tparam As types of input values /// /// \param F function processing inputs and generating output /// - /// \pre Template arguments \p T, \p A and \p As... match the corresponding + /// \pre Template arguments \p T and \p As... match the corresponding /// types \p this object was created with: \code - /// OutputType == TypeNumberOf::Value && inputTypesMatch() + /// OutputType == TypeNumberOf::Value && inputTypesMatch() /// \endcode - template - H triggerHandlerFromProcessingFunction(D &&F) noexcept; + template + H triggerHandlerFromProcessingFunction(D &&F) noexcept; public: /// Creates a new instance. /// /// The constructor instantiates the base-class with functions to handle /// messages as defined for the *deluxe interface*. /// - /// \note Template argument deduction for this constructor breaks older Clang - /// versions, the minimal working version is 3.9.0. The issue and minimal - /// version requirement are recorded in the documentation. Using the named - /// constructor idiom was also investigated to no avail. Explicit - /// specification of actual template arguments does not stop Clang 3.8.0 from - /// breaking on a call to a corresponding named constructor. + /// \todo Enforce F does not potentially throw exception. /// /// \tparam T type of output of \p F - /// \tparam A type of mandatory first input value of \p F - /// \tparam As types of further optional input values of \p F + /// \tparam As types of input values of \p F /// - /// \note Instantiation fails if any of the type arguments \p T, \p A, and - /// \p As... is not a built-in type. + /// \note Instantiation fails if any of the type arguments \p T and \p As... + /// is not a built-in type. /// /// \param Kind kind of the new \c rosa::Unit instance /// \param Id unique identifier of the new \c rosa::Unit instance /// \param Name name of the new \c rosa::Unit instance /// \param S \c rosa::MessagingSystem owning the new instance /// \param F function to process input values and generate output with /// - /// \pre Statically, all of the type arguments \p T, \p A, and \p As... is a + /// \pre Statically, all of the type arguments \p T and \p As... is a /// built-in type: \code - /// TypeListSubsetOf, BuiltinTypes>::Value + /// TypeListSubsetOf, BuiltinTypes>::Value /// \endcode Dynamically, the instance is created as of kind /// \c rosa::deluxe::atoms::AgentKind: \code /// Kind == rosa::deluxe::atoms::AgentKind /// \endcode - template , BuiltinTypes>::Value>> + TypeListSubsetOf, BuiltinTypes>::Value>> DeluxeAgent(const AtomValue Kind, const id_t Id, const std::string &Name, - MessagingSystem &S, D &&F) noexcept; + MessagingSystem &S, D &&F) noexcept; /// Destroys \p this object. ~DeluxeAgent(void) noexcept; /// The *master* of \p this object, if any is registered. /// /// \see \c rosa::deluxe::DeluxeAgent::registerMaster /// /// \return the *master* registered for \p this object Optional master(void) const noexcept; /// Registers a *master* for \p this object. /// /// The new *master* is registered by overwriting the reference to any /// already registered *master*. One can clear the registered reference by /// passing an *empty* \c rosa::Optional object as actual argument. /// /// \note The role of the referred *master* is validated by checking its /// *kind*. /// - /// \param Master the *master* to register + /// \param _Master the *master* to register /// - /// \pre \p Master is empty or of kind \c rosa::deluxe::atoms::AgentKind: + /// \pre \p _Master is empty or of kind \c rosa::deluxe::atoms::AgentKind: /// \code - /// !Master || unwrapAgent(*Master).Kind == rosa::deluxe::atoms::AgentKind + /// !_Master || unwrapAgent(*_Master).Kind == rosa::deluxe::atoms::AgentKind /// \endcode - void registerMaster(const Optional Master) noexcept; + void registerMaster(const Optional _Master) noexcept; /// Tells the type of values consumed from the *slave* at a position. /// /// That is the type of values \p this object expect to be sent to it by its /// *slave* registered at position \p Pos. /// /// \see \c rosa::deluxe::DeluxeAgent::slave /// /// \param Pos position of *slave* /// /// \return \c rosa::TypeNumber representing the type of values consumed from /// the *slave* at position \p Pos /// /// \pre \p Pos is a valid index of input: \code /// Pos < NumberOfInputs /// \endcode TypeNumber inputType(const size_t Pos) const noexcept; /// The *slave* of \p this object registered at a position, if any. /// /// \see \c rosa::deluxe::DeluxeAgent::registerSlave /// /// \param Pos position of *slave* /// /// \return the *slave* registered for \p this object at position \p Pos /// /// \pre \p Pos is a valid index of input: \code /// Pos < NumberOfInputs /// \endcode Optional slave(const size_t Pos) const noexcept; /// Registers a *slave* for \p this object at a position. /// /// The new *slave* is registered by overwriting the reference to any already /// registered *slave* at position \p Pos. One can clear the registered /// reference by passing an *empty* \c rosa::Optional object as actual /// argument. If \p Slave is already registered for another position, the /// other position gets cleared. /// /// \note The role of the referred *slave* is validated by checking its /// *kind*. /// /// \note The type of values produced by the referred *slave* is validated by /// matching its `OutputType` against the corresponding value in /// \c rosa::deluxe::DeluxeAgent::InputTypes. /// /// \param Pos position to register \p Slave at /// \param Slave the *slave* to register /// /// \pre \p Pos is a valid index of input, \p Slave is empty or of kind /// \c rosa::deluxe::atoms::AgentKind or \c rosa::deluxe::atoms::SensorKind, /// and \p Slave -- if not empty -- produces values of types matching the /// expected input type at position \p Pos: /// \code /// Pos < NumberOfInputs && /// (!Slave || /// (unwrapAgent(*Slave.)Kind == rosa::deluxe::atoms::SensorKind && /// static_cast(unwrapAgent(*Slave)).OutputType == /// InputTypes[Pos]) || /// (unwrapAgent(*Slave).Kind == rosa::deluxe::atoms::AgentKind && /// static_cast(unwrapAgent(*Slave)).OutputType == /// InputTypes[Pos])) /// \endcode void registerSlave(const size_t Pos, const Optional Slave) noexcept; /// Tells the position of a registered *slave*. /// /// \param Slave \c rosa::AgentHandle for the *slave* to check /// /// \return position of \p Slave if it is registered and found, /// \c rosa::deluxe::DeluxeAgent::NumberOfInputs otherwise. size_t positionOfSlave(AgentHandle Slave) const noexcept; private: /// Sends a value to the *master* of \p this object. /// /// \p Value is getting sent to \c rosa::deluxe::DeluxeAgent::Master if it /// contains a valid handle for a \c rosa::deluxe::DeluxeAgent. The function /// does nothing otherwise. /// /// \tparam T type of the value to send /// /// \param Value value to send /// /// \pre \p T matches \c rosa::deluxe::DeluxeiAgent::OutputType: \code /// OutputType == TypeNumberOf::Value /// \endcode template void sendToMaster(const T &Value) noexcept; /// Generates the next output by processing current input values upon trigger /// from the system. /// /// Executes \c rosa::deluxe::DeluxeAgent::FP. /// /// \note The only argument is a \c rosa::AtomConstant, hence its actual /// value is ignored. void handleTrigger(atoms::Trigger) noexcept; /// Stores a new input value from a *slave*. /// /// The function stores \p Value in \c rosa::deluxe::DeluxeAgent::InputValues /// at the position associated to \p Id in /// \c rosa::deluxe::DeluxeAgent::SlaveIds and also sets the corresponding /// flag in \c rosa::deluxe::DeluxeAgent::InputChanged. /// /// \note Utilized by member functions of group \c DeluxeAgentInputHandlers. /// /// \tparam T type of input to store /// /// \param Id unique identifier of *slave* /// \param Value the input value to store /// /// \pre The *slave* with \p Id is registered and the input from it is /// expected to be of type \p T: \code /// SlaveIds.find(Id) != SlaveIds.end() && /// InputTypes[SlaveIds.find(Id)->second] == TypeNumberOf::Value /// \endcode template void saveInput(id_t Id, T Value) noexcept; /// \defgroup DeluxeAgentInputHandlers Input handlers of rosa::deluxe::DeluxeAgent /// /// Definition of member functions handling messages from *slaves* with /// different types of input /// /// A *master* generally needs to be prepared to deal with values of any /// built-in type. Each type requires a separate message handler, which are /// implemented by these functions. The functions instantiate /// \c rosa::deluxe::DeluxeAgent::saveInput with the proper template argument /// and pass the content of the message on for processing. /// /// \note The member functions in this group are defined by \c DAHANDLERDEF. /// /// \note Keep these definitions in sync with \c rosa::BuiltinTypes. /// ///@{ DAHANDLERDEF(AtomValue) DAHANDLERDEF(int16_t) DAHANDLERDEF(int32_t) DAHANDLERDEF(int64_t) DAHANDLERDEF(int8_t) DAHANDLERDEFN(long double, long_double) DAHANDLERDEFN(std::string, std__string) DAHANDLERDEF(uint16_t) DAHANDLERDEF(uint32_t) DAHANDLERDEF(uint64_t) DAHANDLERDEF(uint8_t) DAHANDLERDEF(unit_t) DAHANDLERDEF(bool) DAHANDLERDEF(double) DAHANDLERDEF(float) /// @} }; /// Anonymous namespace with implementation for /// \c rosa::deluxe::DeluxeAgent::inputTypesMatch, consider it private. namespace { /// Template \c struct whose specializations provide a recursive implementation /// for \c rosa::deluxe::DeluxeAgent::inputTypesMatch. /// /// \note Matching a list of types \p As... against a \c std::vector of /// \c rosa::TypeNumber values, \c InputTypes, like \code /// bool match = InputTypesMatchImpl::f(InputTypes, 0); /// \endcode /// /// \tparam As types to match template struct InputTypesMatchImpl; /// Template specialization for the general case, when at least one type is to /// be matched. /// /// \tparam A first type to match /// \tparam As further types to match template struct InputTypesMatchImpl { /// Tells whether types \p A, \p As... match \c rosa::TypeNumber values /// stored in \p InputTypes starting at position \p Pos. /// /// The function has got a recursive implementation: it matches the first /// type \p A against \c rosa::TypeNumber at position \p Pos of \p /// InputTypes, then further types \p As.. are matched recursively starting /// at position \c (Pos + 1). /// /// \param InputTypes container of \c rosa::TypeNumber values to match /// types against /// \param Pos position in \p InputTypes to start matching at /// /// \return if types \p A, \p As... match \c rosa::TypeNumber values stored /// in \p InputTypes starting at position \p Pos static bool f(const std::vector &InputTypes, size_t Pos) noexcept { return Pos < InputTypes.size() && TypeNumberOf::Value == InputTypes[Pos] && InputTypesMatchImpl::f(InputTypes, Pos + 1); } }; /// Template specialization for the terminal case, when no type remains to /// check. template <> struct InputTypesMatchImpl<> { /// Tells whether \p Pos is the number of values stored in \p InputTypes. /// /// In this terminal case, there is no more types to matchi because all the /// types are supposed to be already matched successfully. The whole list of /// types already matched is a complete match if it covers all values in /// \p InputTypes. That is true if \p Pos points exactly to the end of /// \p InputTypes. /// /// \param InputTypes container of \c rosa::TypeNumber values to match /// types against /// \param Pos position in \p InputTypes to start matching at /// /// \return if \p Pos is the number of values stored in \p InputTypes static bool f(const std::vector &InputTypes, size_t Pos) noexcept { return Pos == InputTypes.size(); } }; } // End namespace template bool DeluxeAgent::inputTypesMatch(void) const noexcept { return InputTypesMatchImpl::f(InputTypes, 0); } template std::tuple...> DeluxeAgent::prepareCurrentInputs(Seq) const noexcept { // Need to indirectly reference \c rosa::deluxe::DeluxeAgent::inputTypesMatch // inside \c ASSERT because of the comma in its template argument list. auto MFP = &DeluxeAgent::inputTypesMatch; ASSERT(inv() && (this->*MFP)() && sizeof...(As) == sizeof...(S0)); return std::make_tuple( std::make_pair(*static_cast(InputValues->pointerTo(S0)), InputChanged[S0])...); } -template +template Optional DeluxeAgent::invokeWithTuple( - D F, - std::tuple, std::pair...> Args, - Seq<0, S1...>) noexcept { - ASSERT(sizeof...(As) == sizeof...(S1)); - return F(std::get<0>(Args), std::get(Args)...); + D F, + std::tuple...> Args, + Seq) noexcept { + ASSERT(sizeof...(As) == sizeof...(S0)); + return F(std::get(Args)...); } -template +template DeluxeAgent::H -DeluxeAgent::triggerHandlerFromProcessingFunction(D &&F) noexcept { +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; + auto MFP = &DeluxeAgent::inputTypesMatch; ASSERT(OutputType == TypeNumberOf::Value && (this->*MFP)()); - return [this, F]() noexcept { - using Indices = typename GenSeq::Type; - auto Args = prepareCurrentInputs(Indices()); + 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 +template DeluxeAgent::DeluxeAgent(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, - D &&F) noexcept + D &&F) noexcept : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger), DAHANDLERREF(AtomValue), DAHANDLERREF(int16_t), DAHANDLERREF(int32_t), DAHANDLERREF(int64_t), DAHANDLERREF(int8_t), DAHANDLERREF(long_double), DAHANDLERREF(std__string), DAHANDLERREF(uint16_t), DAHANDLERREF(uint32_t), DAHANDLERREF(uint64_t), DAHANDLERREF(uint8_t), DAHANDLERREF(unit_t), DAHANDLERREF(bool), DAHANDLERREF(double), DAHANDLERREF(float)), OutputType(TypeNumberOf::Value), - NumberOfInputs(1 + sizeof...(As)), - InputTypes({TypeNumberOf::Value, TypeNumberOf::Value...}), + NumberOfInputs(sizeof...(As)), + InputTypes({TypeNumberOf::Value...}), InputChanged(NumberOfInputs, false), - InputValues(new TokenizedStorage()), + InputValues(new TokenizedStorage()), FP(triggerHandlerFromProcessingFunction(std::move(F))), Slaves(NumberOfInputs) { ASSERT(Kind == atoms::AgentKind); LOG_TRACE("DeluxeAgent is created."); ASSERT(inv()); } template void DeluxeAgent::sendToMaster(const T &Value) noexcept { ASSERT(inv() && OutputType == TypeNumberOf::Value); // There is a handle and the referred *master* is in a valid state. if (Master && *Master) { Master->sendMessage(Message::create(atoms::Slave::Value, Id, Value)); } } template void DeluxeAgent::saveInput(id_t Id, T Value) noexcept { ASSERT(inv() && SlaveIds.find(Id) != SlaveIds.end() && InputTypes[SlaveIds.find(Id)->second] == TypeNumberOf::Value); size_t Pos = SlaveIds.at(Id); *static_cast(InputValues->pointerTo(Pos)) = Value; InputChanged[Pos] = true; ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa #undef DAHANDLEREF #undef DAHANDLEDEF #undef DAHANDLEDEFN #undef DAHANDLENAME #endif // ROSA_DELUXE_DELUXEAGENT_HPP diff --git a/include/rosa/deluxe/DeluxeContext.hpp b/include/rosa/deluxe/DeluxeContext.hpp index 24f86fb..29794a1 100755 --- a/include/rosa/deluxe/DeluxeContext.hpp +++ b/include/rosa/deluxe/DeluxeContext.hpp @@ -1,296 +1,294 @@ //===-- rosa/deluxe/DeluxeContext.hpp ---------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeContext.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \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*. 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, WrongPosition, AlreadyHasSlave, AlreadyHasMaster, AlreadyHasValueStream }; /// 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; /// Creates a new *sensor* in the context of \p this object. /// /// \tparam T type of data the new *sensor* operates on /// /// \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. /// /// \return \c rosa::AgentHandle for the new *sensor* template AgentHandle createSensor(const std::string &Name, DeluxeSensor::D &&F = [](void) { return T(); }) noexcept; /// Creates a new *agent* in the context of \p this object. /// /// \tparam T type of data the new *agent* outputs - /// \tparam A type of mandatory first input the new *agent* takes - /// \tparam As types of futher optional inputs the new *agent* takes + /// \tparam As types of inputs the new *agent* takes /// /// \param Name name of the new *agent* /// \param F function for the new *agent* to process input values and /// generate output with /// /// \return \c rosa::AgentHandle for the new *agent* - template + template AgentHandle createAgent(const std::string &Name, - DeluxeAgent::D &&F) noexcept; + DeluxeAgent::D &&F) noexcept; /// Connectes a *sensor* to an *agent* in the context of \p this object. /// /// \param Agent the *agent* to connect to /// \param Pos the index of slot of \p Agent to connect \p Sensor to /// \param Sensor the *sensor* to connect /// \param Description optional textual description of the connection /// /// \return how successfull connecting \p Sensor to \p Agent at slot index /// \p Pos was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotAgent` | Referred \p Agent is not \c rosa::deluxe::DeluxeAgent /// `NotSensor` | Referred \p Sensor is not \c rosa::deluxe::DeluxeSensor /// `WrongPosition` | \p Pos is not a valid input position of \p Agent /// `TypeMismatch` | Expected input type at position \p Pos of \p Agent is other than the output type of \p Sensor /// `AlreadyHasSlave` | \p Agent at position \p Pos already has a *slave* registered /// `AlreadyHasMaster` | \p Sensor already has a *master* registered ErrorCode connectSensor(AgentHandle Agent, const size_t Pos, AgentHandle Sensor, const std::string &Description = "") noexcept; /// Connectes two *agents* in the context of \p this object. /// /// \param Master the *agent* to connect to /// \param Pos the index of slot of \p Master to connect \p Slave to /// \param Slave the *agent* to connect /// \param Description optional textual description of the connection /// /// \return how succesfull connecting \p Slave to \p Master at slot index /// \p Pos was /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `NotAgent` | Referred \p Master or \p Slave is not \c rosa::deluxe::DeluxeAgent /// `WrongPosition` | \p Pos is not a valid input position of \p Master /// `TypeMismatch` | Expected input type at position \p Pos of \p Master is other than the output type of \p Slave /// `AlreadyHasSlave` | \p Master at position \p Pos already has a *slave* registered /// `AlreadyHasMaster` | \p Slave already has a *master* registered ErrorCode connectAgents(AgentHandle Master, const size_t Pos, AgentHandle Slave, const std::string &Description = "") noexcept; /// Initializes \c this object and others managed by \p this object for /// setting up and performing simulation. /// /// \see \c rosa::deluxe::DeluxeContext::registerSensorValues, /// \c rosa::deluxe::DeluxeContext::simulate /// /// Need to clear simulation data sources from all the *sensors*. void initializeSimulation(void) noexcept; /// 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! /// /// \param Sensor the *sensor* to register values for /// \param Start provides values for \p Sensor /// \param End denotes the end of stream of values /// \param Default value to be used when input stream is depleted during /// simulation /// /// \return how successful registering \p Source for \p Sensor /// /// \note The function may return the following /// \c rosa::deluxe::DeluxeContext::ErrorCode values: /// `ErrorCode` | Comment /// ----------- | ------- /// `NoError` | Success /// `TypeMismatch` | \p Sensor generates values of a type other than \p T /// `NotSensor` | Referred \p Sensor is not \c rosa::deluxe::DeluxeSensor /// `AlreadyHasValueStream` | \p Sensor already has simulation data source set template ErrorCode registerSensorValues(AgentHandle Sensor, Iterator &&Start, const Iterator &End, T Default = {}) noexcept; /// Performs the system contained by \p this object. /// /// The function performs \p NumCycles cycle of simulation. In each cycle, /// all the *agents* and *sensors* registered in /// \c rosa::deluxe::DeluxeContext::DeluxeUnits are trigged for execution. /// /// \param NumCycles number of cycles to perform /// /// \pre All the *sensors* in the system contained by \p this object generate /// their output from simulation data sources. void simulate(const size_t NumCycles) const noexcept; }; 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; } -template -AgentHandle -DeluxeContext::createAgent(const std::string &Name, - DeluxeAgent::D &&F) noexcept { +template +AgentHandle DeluxeContext::createAgent(const std::string &Name, + DeluxeAgent::D &&F) noexcept { AgentHandle H = System->createAgent(Name, std::move(F)); DeluxeUnits.emplace(H); return H; } template DeluxeContext::ErrorCode DeluxeContext::registerSensorValues(AgentHandle Sensor, Iterator &&Start, const Iterator &End, T Default) noexcept { // Get the type of values provided by \p Iterator. STATIC_ASSERT((std::is_same::value), "type mismatch"); // Make sure preconditions are met. if (!System->isDeluxeSensor(Sensor)) { DCRETERROR(ErrorCode::NotSensor); } auto S = System->getDeluxeSensor(Sensor); ASSERT(S); // Sanity check. if (S->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 { 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; } })); return ErrorCode::NoError; } } // End namespace deluxe } // End namespace rosa // Undef local macro if not used in the corresponding implementation. #ifndef ROSA_LIB_DELUXE_DELUXECONTEXT_CPP #undef DCRETERROR #endif #endif // ROSA_DELUXE_DELUXECONTEXT_HPP diff --git a/include/rosa/deluxe/DeluxeSensor.hpp b/include/rosa/deluxe/DeluxeSensor.hpp index 8f8e526..46ca0c1 100755 --- a/include/rosa/deluxe/DeluxeSensor.hpp +++ b/include/rosa/deluxe/DeluxeSensor.hpp @@ -1,250 +1,257 @@ //===-- rosa/deluxe/DeluxeSensor.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeSensor.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \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" namespace rosa { namespace deluxe { /// Specialization of \c rosa::Agent for *sensor* role of the *deluxe /// interface*. /// /// \see \c rosa::deluxe::DeluxeContext class DeluxeSensor : public Agent { public: /// Template alias for function objects used as data source for /// \c rosa::deluxe::DeluxeSensor. /// + /// \note The function used for \c D is to be \c noexcept. + /// /// \tparam T type of data provided by the function - template using D = std::function; + template using D = std::function; /// The type of values produced by \p this object. /// /// That is the type of values \p this object sends to its *master*. /// /// \see \c rosa::deluxe::DeluxeSensor::master const TypeNumber OutputType; private: - /// Alias for function objects used as trigger handler for /// \c rosa::deluxe::DeluxeSensor. /// + /// \note The function used for \c H is to be \c noexcept. + /// /// \see \c DeluxeSensorTriggerHandlers - using H = std::function; + using H = std::function; /// \defgroup DeluxeSensorTriggerHandlers Trigger handlers of rosa::deluxe::DeluxeSensor /// /// \brief Trigger handler functions of \c rosa::deluxe::DeluxeSensor /// /// The actual data source functions are captured in a lambda expression that /// is in turn wrapped in a \c std::function object. The lambda expression /// calls the data source function to obtain the next sensory value and sends /// it to *master* by calling \c rosa::deluxe::DeluxeSensor::sendToMaster. The /// function \c rosa::deluxe::DeluxeSensor::handleTrigger needs only to call /// the proper function object. /// Handles trigger during normal execution. /// /// \ingroup DeluxeSensorTriggerHandlers /// /// The function is used during normal execution. During simulation, the /// simulation environment sets \c rosa::deluxe::DeluxeSensor::SFP, which is /// used instead of \c rosa::deluxe::DeluxeSensor::FP. const H FP; /// Handles trigger during simulation. /// /// \ingroup DeluxeSensorTriggerHandlers /// /// The function is empty by default. The simulation environment sets it to be /// used during simulation. H SFP; /// The *master* to send values to. /// /// \note *Masters* are set dynamically, hence it is possible that a /// \c rosa::deluxe::DeluxeSensor instance does not have any *master* at a /// given moment. Optional Master; /// Wraps a data source function into a trigger handler. /// /// \see \c DeluxeSensorTriggerHandlers /// /// \tparam T type of data provided by \p F /// /// \param F function to generate value with /// /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == TypeNumberOf::Value /// \endcode template H triggerHandlerFromDataSource(D &&F) noexcept; public: /// Creates a new instance. /// /// The constructor instantiates the base-class with functions to handle /// messages as defined for the *deluxe interface*. /// + /// \todo Enforce F does not potentially throw exception. + /// /// \tparam T type of data to operate on /// /// \param Kind kind of the new \c rosa::Unit instance /// \param Id unique identifier of the new \c rosa::Unit instance /// \param Name name of the new \c rosa::Unit instance /// \param S \c rosa::MessagingSystem owning the new instance /// \param F function to generate the next value with during normal operation /// /// \pre Statically, \p T is a built-in type:\code /// TypeListContains::Value /// \endcode /// Dynamically, the instance is created as of kind /// \c rosa::deluxe::atoms::SensorKind: /// \code /// Kind == rosa::deluxe::atoms::SensorKind /// \endcode template ::Value>> DeluxeSensor(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, D &&F) noexcept; /// Destroys \p this object. ~DeluxeSensor(void) noexcept; /// The *master* of \p this object, if any. /// /// \see \c rosa::deluxe::DeluxeSensor::registerMaster /// /// \return the *master* registered for \p this object Optional master(void) const noexcept; /// Registers a *master* for \p this object. /// /// The new *master* is registered by overwriting the reference to any /// already registered *master*. One can clear the registered reference by /// passing an *empty* \c rosa::Optional object as actual argument. /// /// \note The role of the referred *master* is validated by checking its /// *kind*. /// - /// \param Master the *master* to register + /// \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 + /// !_Master || unwrapAgent(*_Master).Kind == rosa::deluxe::atoms::AgentKind /// \endcode - void registerMaster(const Optional Master) noexcept; + void registerMaster(const Optional _Master) noexcept; /// Clears the simulation trigger handler of \p this object. /// /// The function assigns \c rosa::deluxe::DeluxeSensor::SFP with \c nullptr. void clearSimulationDataSource(void) noexcept; /// Tells whether a simulation trigger handler is set for \p this object. /// /// The function returns whether \c rosa::deluxe::DeluxeSensor::SFP is not /// \c nullptr. /// /// \return if a simulation trigger handler is set for \p this object. bool simulationDataSourceIsSet(void) const noexcept; /// Registers a simulation data source for \p this object. /// /// A new simulation trigger handler wrapping \p SF is stored in /// \c rosa::deluxe::DeluxeSensor::SFP by overwriting any already registered /// simulation data source. /// + /// \todo Enforce SF does not potentially throw exception. + /// /// \tparam T type of data provided by \p SF /// /// \param SF function to generate value with /// /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == TypeNumberOf::Value /// \endcode template void registerSimulationDataSource(D &&SF) noexcept; private: /// Sends a value to the *master* of \p this object. /// /// \p Value is getting sent to \c rosa::deluxe::DeluxeSensor::Master if it /// contains a valid handle for a \c rosa::deluxe::DeluxeAgent. The function /// does nothing otherwise. /// /// \tparam T type of the value to send /// /// \param Value value to send /// /// \pre \p T matches \c rosa::deluxe::DeluxeSensor::OutputType: \code /// OutputType == TypeNumberOf::Value /// \endcode template void sendToMaster(const T &Value) noexcept; /// Generates the next sensory value upon trigger from the system. /// /// Executes \c rosa::deluxe::DeluxeSensor::FP or /// \c rosa::deluxe::DeluxeSensor::SFP if set. /// /// \note The only argument is a \c rosa::AtomConstant, hence its actual /// value is ignored. void handleTrigger(atoms::Trigger) noexcept; }; template DeluxeSensor::H DeluxeSensor::triggerHandlerFromDataSource(D &&F) noexcept { ASSERT(OutputType == TypeNumberOf::Value); return [this, F](void) noexcept { sendToMaster(F()); }; } template DeluxeSensor::DeluxeSensor(const AtomValue Kind, const id_t Id, const std::string &Name, MessagingSystem &S, D &&F) noexcept : Agent(Kind, Id, Name, S, THISMEMBER(handleTrigger)), OutputType(TypeNumberOf::Value), FP(triggerHandlerFromDataSource(std::move(F))), SFP(nullptr) { ASSERT(Kind == atoms::SensorKind); LOG_TRACE("DeluxeSensor is created."); } template void DeluxeSensor::registerSimulationDataSource(D &&SF) noexcept { ASSERT(OutputType == TypeNumberOf::Value); SFP = triggerHandlerFromDataSource(std::move(SF)); } template void DeluxeSensor::sendToMaster(const T &Value) noexcept { ASSERT(OutputType == TypeNumberOf::Value); // There is a handle and the referred *master* is in a valid state. if (Master && *Master) { Master->sendMessage(Message::create(atoms::Slave::Value, Id, Value)); } } } // End namespace deluxe } // End namespace rosa #endif // ROSA_DELUXE_DELUXESENSOR_HPP diff --git a/include/rosa/deluxe/DeluxeSystem.hpp b/include/rosa/deluxe/DeluxeSystem.hpp index 56dcf3c..4388a4e 100755 --- a/include/rosa/deluxe/DeluxeSystem.hpp +++ b/include/rosa/deluxe/DeluxeSystem.hpp @@ -1,213 +1,211 @@ //===-- rosa/deluxe/DeluxeSystem.hpp ----------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/deluxe/DeluxeSystem.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \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; 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 T type of data the new \c rosa::deluxe::DeluxeSensor operates on /// /// \param Name name of the new \c rosa::deluxe::DeluxeSensor /// \param F function to generate the next value with during normal operation /// /// \return \c rosa::AgentHandle for new \c rosa::deluxe::DeluxeSensor template AgentHandle createSensor(const std::string &Name, DeluxeSensor::D &&F) noexcept; /// Creates a \c rosa::deluxe::DeluxeAgent instance owned by \p this object /// and returns a \c rosa::AgentHandle for it. /// /// \tparam T type of data the new \c rosa::deluxe::DeluxeAgent outputs - /// \tparam A type of mandatory first input the new - /// \c rosa::deluxe::DeluxeAgent takes - /// \tparam As types of futher optional inputs the new - /// \c rosa::deluxe::DeluxeAgent takes + /// \tparam As types of inputs the new \c rosa::deluxe::DeluxeAgent takes /// /// \param Name name of the new \c rosa::deluxe::DeluxeAgent /// \param F function for the new \c rosa::deluxe::DeluxeAgent to process /// input values and generate output with /// /// \return \c rosa::AgentHandle for new \c rosa::deluxe::DeluxeAgent - template + template AgentHandle createAgent(const std::string &Name, - DeluxeAgent::D &&F) noexcept; + DeluxeAgent::D &&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 { - Agent &DS = createUnit([&]( - const id_t Id, MessagingSystem &S) noexcept { - return new DeluxeSensor(atoms::SensorKind, Id, Name, S, std::move(F)); - }); + Agent &DS = createUnit( + [&](const id_t Id, MessagingSystem &S) { + return new DeluxeSensor(atoms::SensorKind, Id, Name, S, std::move(F)); + }); return {DS}; } -template +template AgentHandle DeluxeSystem::createAgent(const std::string &Name, - DeluxeAgent::D &&F) noexcept { - Agent &DA = createUnit([&]( - const id_t Id, DeluxeSystem &S) noexcept { - return new DeluxeAgent(atoms::AgentKind, Id, Name, S, std::move(F)); - }); + DeluxeAgent::D &&F) noexcept { + + Agent &DA = createUnit( + [&](const id_t Id, DeluxeSystem &S) { + return new DeluxeAgent(atoms::AgentKind, Id, Name, S, std::move(F)); + }); return {DA}; } } // End namespace deluxe } // End namespace rosa #endif // ROSA_LIB_DELUXE_DELUXESYSTEM_HPP diff --git a/include/rosa/support/csv/CSVReader.hpp b/include/rosa/support/csv/CSVReader.hpp index 15c80d3..0491c17 100755 --- a/include/rosa/support/csv/CSVReader.hpp +++ b/include/rosa/support/csv/CSVReader.hpp @@ -1,375 +1,382 @@ //===-- rosa/support/csv/CSVReader.hpp --------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file rosa/support/csv/CSVReader.hpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Facitilities to read CSV files. /// /// \note The implementation is based on the solution at /// https://stackoverflow.com/a/1120224 /// //===----------------------------------------------------------------------===// #ifndef ROSA_SUPPORT_CSV_CSVREADER_HPP #define ROSA_SUPPORT_CSV_CSVREADER_HPP #include "rosa/support/debug.hpp" #include #include #include namespace rosa { namespace csv { /// Anonymous namespace providing implementation details for /// \c rosa::csv::CSVIterator, consider it private. namespace { /// Provides facility for parsing values from one row CSV data. /// /// \tparam T type of values to parse from the line /// \tparam IsSignedInt if \p T is a signed integral type, always use default /// \tparam IsUnsignedInt if \p T is an unsigned integral type, always use /// default /// \tparam IsFloat if \p T is a floating-point type, always use default /// \tparam IsString if \p T is \c std::string, always use default /// /// \note Specializations of this `struct` are provided for arithmentic types /// and \c std::string. template ::value && std::is_signed::value), bool IsUnsignedInt = (std::is_integral::value && std::is_unsigned::value), bool IsFloat = std::is_floating_point::value, bool IsString = std::is_same::value> struct CSVRowParser; /// Specialization for signed integral types. /// /// \tparam T type of values to parse from the line /// /// \pre \p T is a signed integral type:\code /// std::is_integral::value && std::is_signed::value /// \endcode template struct CSVRowParser { STATIC_ASSERT((std::is_integral::value && std::is_signed::value), "wrong type"); // Sanity check. /// Parses a given row of CSV data into a given container. /// /// \p Data is cleared and then filled with values parsed from \p LineStream. /// Entries in the line are to be separated by commas, the character `,`. A /// trailing comma results in an empty entry at the end of the line. No empty /// entry should be present otherwise. /// + /// \note Parsed values are silently converted to type \p T. + /// /// \param [in,out] LineStream the line to parse /// \param [in,out] Data the container to store the parsed values static void parse(std::stringstream &LineStream, std::vector &Data) { std::string Cell; Data.clear(); while (std::getline(LineStream, Cell, ',')) { - Data.push_back(std::stoll(Cell)); + Data.push_back(static_cast(std::stoll(Cell))); } // This checks for a trailing comma with no data after it. if (!LineStream && Cell.empty()) { // If there was a trailing comma then add an empty element. Data.push_back(0); } } }; /// Specialization for unsigned integral types. /// /// \tparam T type of values to parse from the line /// /// \pre \p T is an unsigned integral type:\code /// std::is_integral::value && std::is_unsigned::value /// \endcode template struct CSVRowParser { STATIC_ASSERT((std::is_integral::value && std::is_unsigned::value), "wrong type"); // Sanity check. /// Parses a given row of CSV data into a given container. /// /// \p Data is cleared and then filled with values parsed from \p LineStream. /// Entries in the line are to be separated by commas, the character `,`. A /// trailing comma results in an empty entry at the end of the line. No empty /// entry should be present otherwise. /// + /// \note Parsed values are silently converted to type \p T. + /// /// \param [in,out] LineStream the line to parse /// \param [in,out] Data the container to store the parsed values static void parse(std::stringstream &LineStream, std::vector &Data) { std::string Cell; Data.clear(); while (std::getline(LineStream, Cell, ',')) { - Data.push_back(std::stoull(Cell)); + Data.push_back(static_cast(std::stoull(Cell))); } // This checks for a trailing comma with no data after it. if (!LineStream && Cell.empty()) { // If there was a trailing comma then add an empty element. Data.push_back(0); } } }; /// Specialization for floating-point types. /// /// \tparam T type of values to parse from the line /// /// \pre \p T is a floating-point type:\code /// std::is_floating_point::value /// \endcode template struct CSVRowParser { STATIC_ASSERT((std::is_floating_point::value), "wrong type"); // Sanity check. /// Parses a given row of CSV data into a given container. /// /// \p Data is cleared and then filled with values parsed from \p LineStream. /// Entries in the line are to be separated by commas, the character `,`. A /// trailing comma results in an empty entry at the end of the line. No empty /// entry should be present otherwise. /// + /// \note Parsed values are silently converted to type \p T. + /// /// \param [in,out] LineStream the line to parse /// \param [in,out] Data the container to store the parsed values static void parse(std::stringstream &LineStream, std::vector &Data) { std::string Cell; Data.clear(); while (std::getline(LineStream, Cell, ',')) { - Data.push_back(std::stold(Cell)); + Data.push_back(static_cast(std::stold(Cell))); } // This checks for a trailing comma with no data after it. if (!LineStream && Cell.empty()) { // If there was a trailing comma then add an empty element. Data.push_back(0); } } }; /// Specialization for \c std::string. /// /// \tparam T type of values to parse from the line /// /// \pre \p T is \c std::string:\code /// std::is_same::value /// \endcode template struct CSVRowParser { STATIC_ASSERT((std::is_same::value), "wrong type"); // Sanity check. /// Parses a given row of CSV data into a given container. /// /// \p Data is cleared and then filled with values parsed from \p LineStream. /// Entries in the line are to be separated by commas, the character `,`. A /// trailing comma results in an empty entry at the end of the line. No empty /// entry should be present otherwise. /// /// \param [in,out] LineStream the line to parse /// \param [in,out] Data the container to store the parsed values static void parse(std::stringstream &LineStream, std::vector &Data) { std::string Cell; Data.clear(); while (std::getline(LineStream, Cell, ',')) { Data.push_back(Cell); } // This checks for a trailing comma with no data after it. if (!LineStream && Cell.empty()) { // If there was a trailing comma then add an empty element. Data.push_back(""); } } }; /// Parses and stores entries from a row of CSV data. /// /// \tparam T type of values to parse and store, i.e. entries in the row /// /// \note The implementation relies on \c rosa::csv::CSVRowParser, which is /// implemented only for `arithmetic` types -- signed and unsigned integral and /// floating-point types -- and for \c std::string. Those are the valid values /// for \p T. template class CSVRow { public: /// Gives a constant reference for an entry at a given position of the row. /// /// \note No bounds checking is performed. /// /// \param Index the position of the entry /// /// \return constant reference for the stored entry at position \p Index const T &operator[](const size_t Index) const noexcept { return Data[Index]; } /// Tells the number of entries stored in the row. /// /// \return number of stored entries. size_t size(void) const noexcept { return Data.size(); } /// Parses and stores one row of CSV data. /// /// The function reads one line from \p Str and parses it into /// \c rosa::csv::CSVRow::Data using \c rosa::csv::CSVRowParser. /// /// \param [in,out] Str input stream of a CSV file void readNextRow(std::istream &Str) { std::string Line; std::getline(Str, Line); std::stringstream LineStream(Line); CSVRowParser::parse(LineStream, Data); } private: std::vector Data; ///< Stores parsed entries }; /// Reads a row of CSV data into \c rosa::csv::CSVRow. /// /// The next line is read from \p Str by calling /// \c rosa::csv::CSVRow::readNextRow on \p Data. /// /// \note A CSV file should contain no empty lines. /// /// \param [in,out] Str input stream of a CSV file /// \param [in,out] Data object to read the next line into /// /// \return \p Str after reading one line from it template std::istream &operator>>(std::istream &Str, CSVRow &Data) { Data.readNextRow(Str); return Str; } } // End namespace /// Provides `InputIterator` features for iterating over a CSV file in a /// flat way. /// /// The iterator hides rows of the CSV file, and iterates over the entries /// row-by-row. /// /// \note A CSV file should contain no empty lines. /// /// \tparam T type of values to iterate over, i.e. entries in the CSV file. /// /// \note The implementation relies on \c rosa::csv::CSVRow, which in turn /// relies on \c rosa::csv::CSVRowParser, which is implemented only for /// `arithmetic` types -- signed and unsigned integral types and floating-point /// types -- and for \c std::string. Those are the valid values for \p T. template class CSVFlatIterator { public: /// \defgroup CSVFlatIteratorTypedefs Typedefs of rosa::csv::CSVFlatIterator /// /// Standard `typedef`s for iterators. /// ///@{ typedef std::input_iterator_tag iterator_category; ///< Category of the iterator. typedef T value_type; ///< Type of values iterated over. typedef std::size_t difference_type; ///< Type to identify distance. typedef T *pointer; ///< Pointer to the type iterated over. typedef T &reference; ///< Reference to the type iterated over. ///@} /// Creates a new instance. /// /// \param [in,out] S input stream to iterate over - CSVFlatIterator(std::istream &S) : Str(S.good() ? &S : nullptr), Pos(-1) { + CSVFlatIterator(std::istream &S) + : Str(S.good() ? &S : nullptr), Pos((size_t)(-1)) { // \c rosa::csv::CSVFlatIterator::Pos is initialized to `-1` so the first // incrementation here will set it properly. ++(*this); } /// Creates an empty new instance. CSVFlatIterator(void) noexcept : Str(nullptr) {} /// Pre-increment operator. /// /// The implementation moves over the entries in the current row and advances /// to the next row when the end of the current row is reached. If the end of /// the input stream is reached, the operator becomes empty and has no /// further effect. /// /// \return \p this object after incrementing it. CSVFlatIterator &operator++() { if (Str) { ++Pos; if (Pos == Row.size()) { if (!((*Str) >> Row)) { Str = nullptr; --Pos; // Stay on the last entry forever. } else { Pos = 0; } } } return *this; } /// Post-increment operator. /// /// The implementation uses the pre-increment operator and returns a copy of /// the original state of \p this object. /// /// \return \p this object before incrementing it. CSVFlatIterator operator++(int) { CSVFlatIterator Tmp(*this); ++(*this); return Tmp; } /// Returns a constant reference to the current entry. /// /// \note Should not dereference the iterator when it is empty. /// /// \return constant reference to the current entry. const T &operator*(void)const noexcept { return Row[Pos]; } /// Returns a constant pointer to the current entry. /// /// \note Should not dereference the iterator when it is empty. /// /// \return constant pointer to the current entry. const T *operator->(void)const noexcept { return &Row[Pos]; } /// Tells if \p this object is equal to another one. /// /// Two \c rosa::csv::CSVReader instances are equal if and only if they are /// the same or both are empty. /// /// \param RHS other object to compare to /// /// \return whether \p this object is equal with \p RHS bool operator==(const CSVFlatIterator &RHS) const noexcept { return ((this == &RHS) || ((this->Str == nullptr) && (RHS.Str == nullptr))); } /// Tells if \p this object is not equal to another one. /// /// \see rosa::csv::CSVReader::operator== /// /// \param RHS other object to compare to /// /// \return whether \p this object is not equal with \p RHS. bool operator!=(const CSVFlatIterator &RHS) const noexcept { return !((*this) == RHS); } private: std::istream *Str; ///< Input stream of a CSV file to iterate over. CSVRow Row; ///< Content of the current row iterating over. size_t Pos; ///< Current position within the current row. }; } // End namespace csv } // End namespace rosa #endif // ROSA_SUPPORT_CSV_CSVREADER_HPP diff --git a/lib/deluxe/DeluxeAgent.cpp b/lib/deluxe/DeluxeAgent.cpp index 6d8af42..78d1cd4 100755 --- a/lib/deluxe/DeluxeAgent.cpp +++ b/lib/deluxe/DeluxeAgent.cpp @@ -1,205 +1,205 @@ //===-- deluxe/DeluxeAgent.cpp ----------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file deluxe/DeluxeAgent.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Implementation of rosa/deluxe/DeluxeAgent.hpp. /// //===----------------------------------------------------------------------===// #include "rosa/deluxe/DeluxeAgent.hpp" #include "rosa/deluxe/DeluxeSensor.hpp" #include namespace rosa { namespace deluxe { bool DeluxeAgent::inv(void) const noexcept { // Check container sizes. if (!(InputTypes.size() == NumberOfInputs && InputChanged.size() == NumberOfInputs && InputValues->size() == NumberOfInputs && Slaves.size() == NumberOfInputs)) { return false; } // Check *slave* types and validate *slave* registrations and reverse lookup // information. std::map RefIds; // Build up a reference of SlaveIds in this. for (size_t I = 0; I < NumberOfInputs; ++I) { // First, validate input types at position \c I. const TypeNumber T = InputTypes[I]; if (InputValues->typeAt(I) != T) { return false; } // Check the registered *slave* at position \c I. - const auto &S = Slaves[I]; - // If \c S is empty, nothing to check. - if (!S) + const auto &Slave = Slaves[I]; + // If \c Slave is empty, nothing to check. + if (!Slave) continue; - // \c S is not empty here. + // \c Slave is not empty here. // Check the `OutputType` of the registered *slave*. - const auto &A = unwrapAgent(*S); + const auto &A = unwrapAgent(*Slave); if (!((A.Kind == atoms::SensorKind && static_cast(A).OutputType == T) || (A.Kind == atoms::AgentKind && static_cast(A).OutputType == T))) { return false; } // Validate that the *slave* is not registered more than once. if (std::any_of( Slaves.begin() + I + 1, Slaves.end(), - [&S](const Optional &O) { return O && *S == *O; })) { + [&Slave](const Optional &O) { return O && *Slave == *O; })) { return false; } // Build the content of \c RefIds. RefIds.emplace(A.Id, I); } // Validate *slave* reverse lookup information against our reference. if (RefIds != SlaveIds) { return false; } // All checks were successful, the invariant is held. return true; } DeluxeAgent::~DeluxeAgent(void) noexcept { ASSERT(inv()); LOG_TRACE("Destroying DeluxeAgent..."); // Make sure \p this object is not a registered *slave*. if (Master) { ASSERT(unwrapAgent(*Master).Kind == atoms::AgentKind); // Sanity check. DeluxeAgent &M = static_cast(unwrapAgent(*Master)); ASSERT(M.positionOfSlave(self()) != M.NumberOfInputs); // Sanity check. M.registerSlave(M.positionOfSlave(self()), {}); Master = {}; } // Also, make sure \p this object is no acting *master*. for (size_t Pos = 0; Pos < NumberOfInputs; ++Pos) { registerSlave(Pos, {}); } // Now there is no connection with other entities, safe to destroy. } Optional DeluxeAgent::master(void) const noexcept { ASSERT(inv()); return Master; } -void DeluxeAgent::registerMaster(const Optional Master) noexcept { - ASSERT(inv() && (!Master || unwrapAgent(*Master).Kind == atoms::AgentKind)); +void DeluxeAgent::registerMaster(const Optional _Master) noexcept { + ASSERT(inv() && (!_Master || unwrapAgent(*_Master).Kind == atoms::AgentKind)); - this->Master = Master; + Master = _Master; ASSERT(inv()); } TypeNumber DeluxeAgent::inputType(const size_t Pos) const noexcept { ASSERT(inv() && Pos < NumberOfInputs); return InputTypes[Pos]; } Optional DeluxeAgent::slave(const size_t Pos) const noexcept { ASSERT(inv() && Pos < NumberOfInputs); return Slaves[Pos]; } void DeluxeAgent::registerSlave(const size_t Pos, const Optional Slave) noexcept { ASSERT(inv() && Pos < NumberOfInputs && (!Slave || (unwrapAgent(*Slave).Kind == atoms::SensorKind && static_cast(unwrapAgent(*Slave)).OutputType == InputTypes[Pos]) || (unwrapAgent(*Slave).Kind == atoms::AgentKind && static_cast(unwrapAgent(*Slave)).OutputType == InputTypes[Pos]))); // If registering an actual *slave*, not just clearing the slot, make sure // the same *slave* is not registered to another slot. if (Slave) { auto It = SlaveIds.find(unwrapAgent(*Slave).Id); if (It != SlaveIds.end()) { Slaves[It->second] = {};//Optional(); SlaveIds.erase(It); } } // Obtain the place whose content is to be replaced with \p Slave - auto &S = Slaves[Pos]; + auto &OldSlave = Slaves[Pos]; // If there is already a *slave* registered at \p Pos, clear reverse lookup // information for it, and make sure it no longer has \p this object as // *master*. - if (S) { - auto &A = unwrapAgent(*S); + if (OldSlave) { + auto &A = unwrapAgent(*OldSlave); ASSERT(SlaveIds.find(A.Id) != SlaveIds.end()); // Sanity check. SlaveIds.erase(A.Id); if (A.Kind == atoms::AgentKind) { static_cast(A).registerMaster({}); } else { ASSERT(A.Kind == atoms::SensorKind); // Sanity check. static_cast(A).registerMaster({}); } } // Register \p Slave at \p Pos. - S = Slave; + OldSlave = Slave; // If registering an actual *slave*, not just clearing the slot, register // reverse lookup information for the new *slave*. if (Slave) { SlaveIds.emplace(unwrapAgent(*Slave).Id, Pos); } ASSERT(inv()); } size_t DeluxeAgent::positionOfSlave(const AgentHandle Slave) const noexcept { ASSERT(inv()); bool Found = false; size_t Pos = 0; while (!Found && Pos < NumberOfInputs) { - auto &S = Slaves[Pos]; - if (S && *S == Slave) { + auto &ExistingSlave = Slaves[Pos]; + if (ExistingSlave && *ExistingSlave == Slave) { Found = true; } else { ++Pos; } } ASSERT(Found || Pos == NumberOfInputs); // Sanity check. return Pos; } void DeluxeAgent::handleTrigger(atoms::Trigger) noexcept { ASSERT(inv()); FP(); ASSERT(inv()); } } // End namespace deluxe } // End namespace rosa diff --git a/lib/deluxe/DeluxeSensor.cpp b/lib/deluxe/DeluxeSensor.cpp index 0074532..c55ef7f 100755 --- a/lib/deluxe/DeluxeSensor.cpp +++ b/lib/deluxe/DeluxeSensor.cpp @@ -1,63 +1,63 @@ //===-- deluxe/DeluxeSensor.cpp ---------------------------------*- C++ -*-===// // // The RoSA Framework // //===----------------------------------------------------------------------===// /// /// \file deluxe/DeluxeSensor.cpp /// /// \author David Juhasz (david.juhasz@tuwien.ac.at) /// -/// \date 2017 +/// \date 2017-2019 /// /// \brief Implementation of rosa/deluxe/DeluxeSensor.hpp. /// //===----------------------------------------------------------------------===// #include "rosa/deluxe/DeluxeSensor.hpp" #include "rosa/deluxe/DeluxeAgent.hpp" namespace rosa { namespace deluxe { DeluxeSensor::~DeluxeSensor(void) noexcept { LOG_TRACE("Destroying DeluxeSensor..."); // Make sure \p this object is not a registered *slave*. if (Master) { ASSERT(unwrapAgent(*Master).Kind == atoms::AgentKind); // Sanity check. DeluxeAgent &M = static_cast(unwrapAgent(*Master)); ASSERT(M.positionOfSlave(self()) != M.NumberOfInputs); // Sanity check. M.registerSlave(M.positionOfSlave(self()), {}); Master = {}; } } Optional DeluxeSensor::master(void) const noexcept { return Master; } -void DeluxeSensor::registerMaster(const Optional Master) noexcept { - ASSERT(!Master || unwrapAgent(*Master).Kind == atoms::AgentKind); +void DeluxeSensor::registerMaster(const Optional _Master) noexcept { + ASSERT(!_Master || unwrapAgent(*_Master).Kind == atoms::AgentKind); - this->Master = Master; + Master = _Master; } void DeluxeSensor::clearSimulationDataSource(void) noexcept { SFP = nullptr; } bool DeluxeSensor::simulationDataSourceIsSet(void) const noexcept { return SFP != nullptr; } void DeluxeSensor::handleTrigger(atoms::Trigger) noexcept { // Use \c rosa::deluxe::DeluxeSensor::SFP if set, otherwise // \c rosa::deluxe::DeluxeSensor::FP. const H &F = SFP ? SFP : FP; F(); } } // End namespace deluxe } // End namespace rosa