diff --git a/CMakeLists.txt b/CMakeLists.txt index 92dfd42a..380eddbc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,8 @@ configure_file(include/reactor-cpp/config.hh.in include/reactor-cpp/config.hh @O include(GNUInstallDirs) add_subdirectory(lib) +add_subdirectory(reactor-sdk) + if(NOT DEFINED LF_REACTOR_CPP_SUFFIX) add_subdirectory(examples) endif() @@ -71,3 +73,13 @@ if (DEFINED LF_REACTOR_CPP_SUFFIX) else() install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") endif() + +if(NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +endif() \ No newline at end of file diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 00000000..c2d34d47 --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach() diff --git a/examples/sdk-SrcSink-Fanout/.gitignore b/examples/sdk-SrcSink-Fanout/.gitignore new file mode 100644 index 00000000..c809c12d --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/.gitignore @@ -0,0 +1 @@ +*build \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/CMakeLists.txt b/examples/sdk-SrcSink-Fanout/CMakeLists.txt new file mode 100644 index 00000000..39213d0c --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.9) +project(src_sink_fanout VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET src_sink_fanout) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) + +include(Sink/SinkReactor.cmake) +include(Source/SourceReactor.cmake) +include(Main/MainReactor.cmake) +include(Config-a/Config-a.cmake) \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Config-a/Config-a.cc b/examples/sdk-SrcSink-Fanout/Config-a/Config-a.cc new file mode 100644 index 00000000..48e621d0 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Config-a/Config-a.cc @@ -0,0 +1,17 @@ +#include "Config-a.hh" + +UserParameters cfg_parameters; + +ConfigParameter<int, uint32_t>::ParametersMap UserParameters::homogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata<int> { 5 } } + }; +} + +ConfigParameter<int, uint32_t>::ParametersMap UserParameters::heterogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata<int> { 20 } }, + {"Main.Sink.n_ports", ConfigParameterMetadata<int> { 2 } } + + }; +} \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Config-a/Config-a.cmake b/examples/sdk-SrcSink-Fanout/Config-a/Config-a.cmake new file mode 100644 index 00000000..bd7049e4 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Config-a/Config-a.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Config-a.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Config-a.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Config-a/Config-a.hh b/examples/sdk-SrcSink-Fanout/Config-a/Config-a.hh new file mode 100644 index 00000000..7d57d334 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Config-a/Config-a.hh @@ -0,0 +1,18 @@ +#ifndef USER_PARAMETERS_H +#define USER_PARAMETERS_H + +#include <reactor-sdk/reactor-sdk.hh> +#include <map> +#include <variant> +#include <string> + +using namespace sdk; + +struct UserParameters : public ConfigParameter<int, uint32_t> { + ConfigParameter<int, uint32_t>::ParametersMap homogeneous_config(); + ConfigParameter<int, uint32_t>::ParametersMap heterogeneous_config(); +}; + +extern UserParameters cfg_parameters; + +#endif // USER_PARAMETERS_H diff --git a/examples/sdk-SrcSink-Fanout/Main/MainReactor.cc b/examples/sdk-SrcSink-Fanout/Main/MainReactor.cc new file mode 100644 index 00000000..fc841b8c --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Main/MainReactor.cc @@ -0,0 +1,29 @@ +#include "MainReactor.hh" + +void MainReactor::construction() { + + cout << "Construction Main\n"; + + src = std::make_unique<SourceReactor>("Source", this); + snk = std::make_unique<SinkReactor>("Sink", this); +} + +void MainReactor::wiring() { + cout << "Wiring Main\n"; + + src->req -->> snk->req; + snk->rsp --> src->rsp; +} + +void REACTION_SCOPE(MainReactor)::add_reactions(MainReactor *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(). + function( + [this](Startup& startup) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Starting up reaction\n" << "Bank:" << bank_index << " name:" << parameters.alias.value << " fqn:" << fqn() << endl; + } + ); +} \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Main/MainReactor.cmake b/examples/sdk-SrcSink-Fanout/Main/MainReactor.cmake new file mode 100644 index 00000000..4c6cc870 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Main/MainReactor.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/MainReactor.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/MainReactor.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Main/MainReactor.hh b/examples/sdk-SrcSink-Fanout/Main/MainReactor.hh new file mode 100644 index 00000000..a945643b --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Main/MainReactor.hh @@ -0,0 +1,47 @@ +#pragma once + +#include <reactor-sdk/reactor-sdk.hh> + +#include "Source/SourceReactor.hh" +#include "Sink/SinkReactor.hh" + +using namespace sdk; + +class MainReactor: public Reactor { +public: + struct Parameters { + string alias = "Src-Sink-Fanout-Example"; + }; +private: + struct PublishParameters : public SystemParameters<Parameters, string> { + REACTOR_PARAMETER(string, alias, "Alternate name", "another", "another", defaults.alias); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters<Parameters, string>(container, std::forward<Parameters>(param)) { + register_parameters (alias); + } + }; + PublishParameters parameters; + + REACTION_SCOPE_START(MainReactor, PublishParameters) + void add_reactions(MainReactor *reactor); + REACTION_SCOPE_END(this, parameters) + + std::unique_ptr<SourceReactor> src; + std::unique_ptr<SinkReactor> snk; + +public: + MainReactor(const std::string &name, Environment *env) + : Reactor(name, env), parameters{this, Parameters{}} {} + MainReactor(const std::string &name, Reactor *container) + : Reactor(name, container), parameters{this, Parameters{}} {} + + MainReactor(const std::string &name, Environment *env, Parameters && param) + : Reactor(name, env), parameters{this, std::forward<Parameters>(param)} {} + MainReactor(const std::string &name, Reactor *container, Parameters && param) + : Reactor(name, container), parameters{this, std::forward<Parameters>(param)} {} + + void construction() override; + void wiring() override; +}; + diff --git a/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cc b/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cc new file mode 100644 index 00000000..b4e8e125 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cc @@ -0,0 +1,45 @@ +#include "SinkReactor.hh" +using namespace std; + +void SinkReactor::construction() { + cout << "Construction Sink n_ports:" << parameters.n_ports.value << "\n"; + req.set_width (parameters.n_ports.value); + rsp.set_width (parameters.n_ports.value); +} + +void SinkReactor::wiring() { + cout << "Wiring Sink\n"; +} + +void REACTION_SCOPE(SinkReactor)::add_reactions (SinkReactor *reactor) { + reaction("startup_reaction"). + triggers(&reactor->startup). + dependencies(). + effects(). + function(pass_function(startup_reaction) + ); + + reaction("process_request"). + triggers(&reactor->req). + dependencies(). + effects(&reactor->rsp). + function(pass_function(process_request) + ); +} + + + +void REACTION_SCOPE(SinkReactor)::startup_reaction (Startup& startup) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Starting up reaction\n" << "Bank:" << bank_index << " name:" << parameters.name.value << " fqn:" << fqn() << endl; +} + +void REACTION_SCOPE(SinkReactor)::process_request (MultiportInput<int>& req, MultiportOutput<int>& rsp) { + for (int i = 0; i < parameters.n_ports.value; ++i) { + if (req[i].is_present()) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Received input:" << *req[i].get() << " port:" << i << endl; + rsp[i].set (*req[i].get()); + } + } +} \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cmake b/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cmake new file mode 100644 index 00000000..8402349d --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cmake @@ -0,0 +1,14 @@ +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SinkReactor.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SinkReactor.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.hh b/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.hh new file mode 100644 index 00000000..63cb7ddc --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.hh @@ -0,0 +1,49 @@ +#pragma once + +#include <reactor-sdk/reactor-sdk.hh> +using namespace std; +using namespace sdk; + +class SinkReactor : public Reactor { +public: + + struct Parameters { + string name = "Sink"; + int n_ports = 1; + }; +private: + struct PublishParameters : public SystemParameters<Parameters, string, int> { + REACTOR_PARAMETER(string, name, "Alternate name", "Sink", "Sink", defaults.name); + REACTOR_PARAMETER (int, n_ports, "Size of multiports", 1, 10, defaults.n_ports); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters<Parameters, string, int>(container, std::forward<Parameters>(param)) { + register_parameters (name, n_ports); + } + }; + PublishParameters parameters; + + REACTION_SCOPE_START(SinkReactor, PublishParameters) + void add_reactions(SinkReactor *reactor); + + void startup_reaction (Startup &startup); + void process_request (MultiportInput<int>& req, MultiportOutput<int>& rsp); + REACTION_SCOPE_END(this, parameters) + +public: + SinkReactor(const std::string &name, Environment *env) + : Reactor(name, env), parameters{this, Parameters{}} {} + SinkReactor(const std::string &name, Reactor *container) + : Reactor(name, container), parameters{this, Parameters{}} {} + + SinkReactor(const std::string &name, Environment *env, Parameters && param) + : Reactor(name, env), parameters{this, std::forward<Parameters>(param)} {} + SinkReactor(const std::string &name, Reactor *container, Parameters && param) + : Reactor(name, container), parameters{this, std::forward<Parameters>(param)} {} + + MultiportInput<int> req{"req", this}; + MultiportOutput<int> rsp{"rsp", this}; + + void construction() override; + void wiring() override; +}; diff --git a/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cc b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cc new file mode 100644 index 00000000..b4ca3dba --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cc @@ -0,0 +1,61 @@ + +#include "SourceReactor.hh" +using namespace std; + +void SourceReactor::construction() { + + cout << "Construction Source iterations:" << parameters.iterations.value << "\n"; +} + +void SourceReactor::wiring() { + cout << "Wiring Source iterations:" << parameters.iterations.value << "\n"; +} + +void REACTION_SCOPE(SourceReactor)::add_reactions(SourceReactor *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(&reactor->sch). + function( + [this](Startup& startup, LogicalAction<int>& sched) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Starting up reaction\n" << "Bank:" << bank_index << " name:" << name << " fqn:" << fqn() << " iterations:" << parameters.iterations.value << endl; + if (itr < parameters.iterations.value) { + sched.schedule (itr, 0ms); + ++itr; + } + } + ); + + reaction("reaction_2"). + triggers(&reactor->sch). + dependencies(). + effects(&reactor->req). + function( + [this](LogicalAction<int>& sch, Output<int>& req) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Scheduling iteration:" << *sch.get() << endl; + req.set (*sch.get()); + } + ); + + reaction("reaction_3"). + triggers(&reactor->rsp). + dependencies(). + effects(&reactor->sch). + function( + [this](Input<int>& rsp, LogicalAction<int>& sch) { + if (rsp.is_present()) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Recevied response:" << *rsp.get() << endl; + } + + if (itr < parameters.iterations.value) { + sch.schedule (itr, 0ms); + ++itr; + } else { + request_stop(); + } + } + ); +} \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cmake b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cmake new file mode 100644 index 00000000..a6071900 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cmake @@ -0,0 +1,13 @@ +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SourceReactor.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SourceReactor.cc" +) + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/Source/SourceReactor.hh b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.hh new file mode 100644 index 00000000..248f77ec --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.hh @@ -0,0 +1,40 @@ +#pragma once + +#include <reactor-sdk/reactor-sdk.hh> +using namespace std; +using namespace sdk; + +class SourceReactor : public Reactor { +public: + struct Parameters : public SystemParametersStandalone<int> { + REACTOR_PARAMETER(int, iterations, "Number of iterations", 1, 100, 10); + + Parameters(Reactor *container) + : SystemParametersStandalone<int>(container) { + register_parameters (iterations); + } + }; +private: + LogicalAction<int> sch{"sch", this}; + + Parameters parameters{this}; + + REACTION_SCOPE_START(SourceReactor, Parameters) + std::string name = "Source"; + int itr = 0; + + void add_reactions(SourceReactor *reactor); + REACTION_SCOPE_END(this, parameters) + +public: + SourceReactor(const std::string &name, Environment *env) + : Reactor(name, env) {} + SourceReactor(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Input<int> rsp{"rsp", this}; + Output<int> req{"req", this}; + + void construction() override; + void wiring() override; +}; \ No newline at end of file diff --git a/examples/sdk-SrcSink-Fanout/example.png b/examples/sdk-SrcSink-Fanout/example.png new file mode 100644 index 00000000..ba059554 Binary files /dev/null and b/examples/sdk-SrcSink-Fanout/example.png differ diff --git a/examples/sdk-SrcSink-Fanout/graph.dot b/examples/sdk-SrcSink-Fanout/graph.dot new file mode 100644 index 00000000..03cc57c6 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/graph.dot @@ -0,0 +1,44 @@ +digraph { +rankdir=LR; +subgraph { +rank=same; +Main_reaction_1 [label="Main.reaction_1"]; +Main_Source_reaction_1 [label="Main.Source.reaction_1"]; +Main_Sink_0_startup_reaction [label="Main.Sink_0.startup_reaction"]; +Main_Sink_1_startup_reaction [label="Main.Sink_1.startup_reaction"]; +Main_Sink_2_startup_reaction [label="Main.Sink_2.startup_reaction"]; +Main_Sink_3_startup_reaction [label="Main.Sink_3.startup_reaction"]; +} +subgraph { +rank=same; +Main_Source_reaction_2 [label="Main.Source.reaction_2"]; +} +subgraph { +rank=same; +Main_Sink_0_process_request [label="Main.Sink_0.process_request"]; +Main_Sink_1_process_request [label="Main.Sink_1.process_request"]; +Main_Sink_2_process_request [label="Main.Sink_2.process_request"]; +Main_Sink_3_process_request [label="Main.Sink_3.process_request"]; +} +subgraph { +rank=same; +Main_Source_reaction_3 [label="Main.Source.reaction_3"]; +} +Main_reaction_1 -> Main_Source_reaction_2 [style=invis]; +Main_Source_reaction_2 -> Main_Sink_0_process_request [style=invis]; +Main_Sink_0_process_request -> Main_Source_reaction_3 [style=invis]; +Main_Source_reaction_3 -> Main_Sink_0_process_request +Main_Source_reaction_3 -> Main_Sink_1_process_request +Main_Source_reaction_3 -> Main_Sink_2_process_request +Main_Source_reaction_3 -> Main_Sink_3_process_request +Main_Source_reaction_2 -> Main_Source_reaction_1 +Main_Source_reaction_3 -> Main_Source_reaction_2 +Main_Sink_0_process_request -> Main_Source_reaction_2 +Main_Sink_0_process_request -> Main_Sink_0_startup_reaction +Main_Sink_1_process_request -> Main_Source_reaction_2 +Main_Sink_1_process_request -> Main_Sink_1_startup_reaction +Main_Sink_2_process_request -> Main_Source_reaction_2 +Main_Sink_2_process_request -> Main_Sink_2_startup_reaction +Main_Sink_3_process_request -> Main_Source_reaction_2 +Main_Sink_3_process_request -> Main_Sink_3_startup_reaction +} diff --git a/examples/sdk-SrcSink-Fanout/main.cc b/examples/sdk-SrcSink-Fanout/main.cc new file mode 100644 index 00000000..2ed281e9 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/main.cc @@ -0,0 +1,51 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +#include "Config-a/Config-a.hh" +#include "Main/MainReactor.hh" + +using namespace std; +using namespace sdk; + +int main(int argc, char **argv) { + cxxopts::Options options("sdk-SrcSink-Fanout", "Multiport source connecting to banked sink reactors"); + + unsigned workers = std::thread::hardware_concurrency(); + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + bool cfg_gen{false}; + + // the timeout variable needs to be tested beyond fitting the Duration-type + options + .set_width(120) + .add_options() + ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value<unsigned>(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value<reactor::Duration>(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value<bool>(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value<bool>(cfg_gen)->default_value("false")) + ("help", "Print help"); + + cxxopts::ParseResult result{}; + bool parse_error{false}; + try { + result = options.parse(argc, argv); + } catch (const cxxopts::OptionException& e) { + reactor::log::Error() << e.what(); + parse_error = true; + } + + // if parameter --help was used or there was a parse error, print help + if (parse_error || result.count("help")) + { + std::cout << options.help({""}); + return parse_error ? -1 : 0; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " cfg_gen:" << (cfg_gen ? "True" : "False") << std::endl; + + Environment sim {&cfg_parameters, workers, fast, timeout, cfg_gen}; + auto main = new MainReactor("Main", &sim); + + sim.run(); + return 0; +} diff --git a/examples/sdk-SrcSink/.gitignore b/examples/sdk-SrcSink/.gitignore new file mode 100644 index 00000000..c809c12d --- /dev/null +++ b/examples/sdk-SrcSink/.gitignore @@ -0,0 +1 @@ +*build \ No newline at end of file diff --git a/examples/sdk-SrcSink/CMakeLists.txt b/examples/sdk-SrcSink/CMakeLists.txt new file mode 100644 index 00000000..71c3b16d --- /dev/null +++ b/examples/sdk-SrcSink/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.9) +project(src_sink VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET src_sink) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) + +include(Sink/SinkReactor.cmake) +include(Source/SourceReactor.cmake) +include(Main/MainReactor.cmake) +include(Config-a/Config-a.cmake) \ No newline at end of file diff --git a/examples/sdk-SrcSink/Config-a/Config-a.cc b/examples/sdk-SrcSink/Config-a/Config-a.cc new file mode 100644 index 00000000..77b906d0 --- /dev/null +++ b/examples/sdk-SrcSink/Config-a/Config-a.cc @@ -0,0 +1,23 @@ +#include "Config-a.hh" + +UserParameters cfg_parameters; + +ConfigParameter<int, uint32_t, string>::ParametersMap UserParameters::homogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata<int> { 5 } }, + // {"Main.Sink.name", ConfigParameterMetadata<string> { "Homog Name" } }, + }; +} + +ConfigParameter<int, uint32_t, string>::ParametersMap UserParameters::heterogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata<int> { 20 } }, + {"Main.Source.n_ports", ConfigParameterMetadata<int> { 4 } }, + {"Main.n_sinks", ConfigParameterMetadata<int> { 4 } }, + // {"Main.Sink_0.name", ConfigParameterMetadata<string> { "Hetero Name 0" } }, + // {"Main.Sink_1.name", ConfigParameterMetadata<string> { "Hetero Name 1" } }, + // {"Main.Sink_2.name", ConfigParameterMetadata<string> { "Hetero Name 2" } }, + // {"Main.Sink_3.name", ConfigParameterMetadata<string> { "Hetero Name 3" } }, + + }; +} diff --git a/examples/sdk-SrcSink/Config-a/Config-a.cmake b/examples/sdk-SrcSink/Config-a/Config-a.cmake new file mode 100644 index 00000000..bd7049e4 --- /dev/null +++ b/examples/sdk-SrcSink/Config-a/Config-a.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Config-a.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Config-a.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-SrcSink/Config-a/Config-a.hh b/examples/sdk-SrcSink/Config-a/Config-a.hh new file mode 100644 index 00000000..129beb99 --- /dev/null +++ b/examples/sdk-SrcSink/Config-a/Config-a.hh @@ -0,0 +1,18 @@ +#ifndef USER_PARAMETERS_H +#define USER_PARAMETERS_H + +#include <reactor-sdk/reactor-sdk.hh> +#include <map> +#include <variant> +#include <string> + +using namespace sdk; + +struct UserParameters : public ConfigParameter<int, uint32_t, string> { + ConfigParameter<int, uint32_t, string>::ParametersMap homogeneous_config(); + ConfigParameter<int, uint32_t, string>::ParametersMap heterogeneous_config(); +}; + +extern UserParameters cfg_parameters; + +#endif // USER_PARAMETERS_H diff --git a/examples/sdk-SrcSink/Main/MainReactor.cc b/examples/sdk-SrcSink/Main/MainReactor.cc new file mode 100644 index 00000000..ffd48410 --- /dev/null +++ b/examples/sdk-SrcSink/Main/MainReactor.cc @@ -0,0 +1,41 @@ +#include "MainReactor.hh" + +void MainReactor::construction() { + + cout << "Construction Main n_sinks:" << parameters.n_sinks.value << " default n_sinks:" << parameters.defaults.n_sinks << "\n"; + + src = std::make_unique<SourceReactor>("Source", this); + + for (int i = 0; i < parameters.n_sinks.value; i++) { + snk.create_reactor(SinkReactor::Parameters{.name = "Default Sink Name"}); + } +} + +void MainReactor::wiring() { + cout << "Wiring Main n_sinks:" << parameters.n_sinks.value << " default n_sinks:" << parameters.defaults.n_sinks << "\n"; + + src->req --> snk.for_each(select_default(snk).req); + // src->req --> snk.for_each(&SinkReactor::req); // alternative + // src->req --> snk.for_each(&snk[0].req); // alternative + // src->req --> snk->*(select_default(snk).req); // alternative + // src->req --> snk->*(&SinkReactor::req); // alternative + + snk.for_each(select_default(snk).rsp) --> src->rsp; + // snk.for_each(&SinkReactor::rsp) --> src->rsp; // alternative + // snk.for_each(&snk[0].rsp) --> src->rsp; // alternative + // (snk->*(select_default(snk).rsp)) --> src->rsp; // alternative + // (snk->*(&SinkReactor::rsp)) --> src->rsp; // alternative +} + +void REACTION_SCOPE(MainReactor)::add_reactions(MainReactor *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(). + function( + [this](Startup& startup) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Starting up reaction\n" << "Bank:" << bank_index << " name:" << parameters.alias.value << " fqn:" << fqn() << " n_sinks:" << parameters.n_sinks.value << endl; + } + ); +} \ No newline at end of file diff --git a/examples/sdk-SrcSink/Main/MainReactor.cmake b/examples/sdk-SrcSink/Main/MainReactor.cmake new file mode 100644 index 00000000..4c6cc870 --- /dev/null +++ b/examples/sdk-SrcSink/Main/MainReactor.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/MainReactor.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/MainReactor.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-SrcSink/Main/MainReactor.hh b/examples/sdk-SrcSink/Main/MainReactor.hh new file mode 100644 index 00000000..ef5d875f --- /dev/null +++ b/examples/sdk-SrcSink/Main/MainReactor.hh @@ -0,0 +1,48 @@ +#pragma once + +#include <reactor-sdk/reactor-sdk.hh> + +#include "Source/SourceReactor.hh" +#include "Sink/SinkReactor.hh" + +using namespace sdk; + +class MainReactor: public Reactor { +public: + struct Parameters { + string alias = "Src-Sink-Example"; + int n_sinks = 2; + }; +private: + struct PublishParameters : public SystemParameters<Parameters, string, int> { + REACTOR_PARAMETER(string, alias, "Alternate name", "another", "another", defaults.alias); + REACTOR_PARAMETER(int, n_sinks, "Sink reactors bank width", 1, 10, defaults.n_sinks); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters<Parameters, string, int>(container, std::forward<Parameters>(param)) { + register_parameters (alias, n_sinks); + } + }; + PublishParameters parameters; + + REACTION_SCOPE_START(MainReactor, PublishParameters) + void add_reactions(MainReactor *reactor); + REACTION_SCOPE_END(this, parameters) + + std::unique_ptr<SourceReactor> src; + ReactorBank<SinkReactor> snk{"Sink", this}; + +public: + MainReactor(const std::string &name, Environment *env) + : Reactor(name, env), parameters{this, Parameters{}} {} + MainReactor(const std::string &name, Reactor *container) + : Reactor(name, container), parameters{this, Parameters{}} {} + + MainReactor(const std::string &name, Environment *env, Parameters && param) + : Reactor(name, env), parameters{this, std::forward<Parameters>(param)} {} + MainReactor(const std::string &name, Reactor *container, Parameters && param) + : Reactor(name, container), parameters{this, std::forward<Parameters>(param)} {} + + void construction() override; + void wiring() override; +}; \ No newline at end of file diff --git a/examples/sdk-SrcSink/Sink/SinkReactor.cc b/examples/sdk-SrcSink/Sink/SinkReactor.cc new file mode 100644 index 00000000..74a8c69b --- /dev/null +++ b/examples/sdk-SrcSink/Sink/SinkReactor.cc @@ -0,0 +1,40 @@ +#include "SinkReactor.hh" +using namespace std; + +void SinkReactor::construction() { + cout << "Construction Sink\n"; +} + +void SinkReactor::wiring() { + + cout << "Wiring Sink\n"; +} + +void REACTION_SCOPE(SinkReactor)::add_reactions(SinkReactor *reactor) { + reaction("startup_reaction"). + triggers(&reactor->startup). + dependencies(). + effects(). + function(pass_function(startup_reaction) + ); + + reaction("process_request"). + triggers(&reactor->req). + dependencies(). + effects(&reactor->rsp). + function(pass_function(process_request) + ); +} + + + +void REACTION_SCOPE(SinkReactor)::startup_reaction (Startup& startup) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Starting up reaction\n" << "Bank:" << bank_index << " name:" << parameters.name.value << " fqn:" << fqn() << endl; +} + +void REACTION_SCOPE(SinkReactor)::process_request (Input<int>& req, Output<int>& rsp) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Received input:" << *req.get() << " bank:" << bank_index << endl; + rsp.set (*req.get()); +} \ No newline at end of file diff --git a/examples/sdk-SrcSink/Sink/SinkReactor.cmake b/examples/sdk-SrcSink/Sink/SinkReactor.cmake new file mode 100644 index 00000000..8402349d --- /dev/null +++ b/examples/sdk-SrcSink/Sink/SinkReactor.cmake @@ -0,0 +1,14 @@ +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SinkReactor.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SinkReactor.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-SrcSink/Sink/SinkReactor.hh b/examples/sdk-SrcSink/Sink/SinkReactor.hh new file mode 100644 index 00000000..db709382 --- /dev/null +++ b/examples/sdk-SrcSink/Sink/SinkReactor.hh @@ -0,0 +1,47 @@ +#pragma once + +#include <reactor-sdk/reactor-sdk.hh> +using namespace std; +using namespace sdk; + +class SinkReactor : public Reactor { +public: + + struct Parameters { + string name = "Sink"; + }; +private: + struct PublishParameters : public SystemParameters<Parameters, string> { + REACTOR_PARAMETER(string, name, "Alternate name", "Sink", "Sink", defaults.name); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters<Parameters, string>(container, std::forward<Parameters>(param)) { + register_parameters (name); + } + }; + PublishParameters parameters; + + REACTION_SCOPE_START(SinkReactor, PublishParameters) + void add_reactions(SinkReactor *reactor); + + void startup_reaction (Startup &startup); + void process_request (Input<int>& req, Output<int>& rsp); + REACTION_SCOPE_END(this, parameters) + +public: + SinkReactor(const std::string &name, Environment *env) + : Reactor(name, env), parameters{this, Parameters{}} {} + SinkReactor(const std::string &name, Reactor *container) + : Reactor(name, container), parameters{this, Parameters{}} {} + + SinkReactor(const std::string &name, Environment *env, Parameters && param) + : Reactor(name, env), parameters{this, std::forward<Parameters>(param)} {} + SinkReactor(const std::string &name, Reactor *container, Parameters && param) + : Reactor(name, container), parameters{this, std::forward<Parameters>(param)} {} + + Input<int> req{"req", this}; + Output<int> rsp{"rsp", this}; + + void construction() override; + void wiring() override; +}; diff --git a/examples/sdk-SrcSink/Source/SourceReactor.cc b/examples/sdk-SrcSink/Source/SourceReactor.cc new file mode 100644 index 00000000..25042cc3 --- /dev/null +++ b/examples/sdk-SrcSink/Source/SourceReactor.cc @@ -0,0 +1,75 @@ + +#include "SourceReactor.hh" +using namespace std; + +void SourceReactor::construction() { + + cout << "Construction Source n_ports:" << parameters.n_ports.value << "\n"; + + req.set_width (parameters.n_ports.value); + rsp.set_width (parameters.n_ports.value); +} + +void SourceReactor::wiring() { + cout << "Wiring Source n_ports:" << parameters.n_ports.value << "\n"; +} + +void REACTION_SCOPE(SourceReactor)::add_reactions(SourceReactor *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(&reactor->sch). + function( + [this](Startup& startup, LogicalAction<int>& sched) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Starting up reaction\n" << "Bank:" << bank_index << " name:" << name << " fqn:" << fqn() << " iterations:" << parameters.iterations.value << endl; + if (itr < parameters.iterations.value) { + sched.schedule (itr, 0ms); + ++itr; + } + } + ); + + reaction("reaction_2"). + triggers(&reactor->sch). + dependencies(). + effects(&reactor->req). + function( + [this](LogicalAction<int>& sch, MultiportOutput<int>& req) { + for (int i = 0; i < parameters.n_ports.value; ++i) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Scheduling iteration:" << *sch.get() << " out_port:" << i << endl; + req[i].set (*sch.get()); + } + } + ); + + reaction("reaction_3"). + triggers(&reactor->rsp). + dependencies(). + effects(&reactor->sch). + function( + [this](MultiportInput<int>& rsp, LogicalAction<int>& sch) { + for (int i = 0; i < parameters.n_ports.value; ++i) { + if (rsp[i].is_present()) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Recevied response:" << *rsp[i].get() << " in_port:" << i << endl; + ++rsp_itr; + } + } + + if (rsp_itr < parameters.n_ports.value) { + return; + } + + rsp_itr = 0; + + if (itr < parameters.iterations.value) { + sch.schedule (itr, 0ms); + ++itr; + } else { + request_stop(); + } + } + ); +} \ No newline at end of file diff --git a/examples/sdk-SrcSink/Source/SourceReactor.cmake b/examples/sdk-SrcSink/Source/SourceReactor.cmake new file mode 100644 index 00000000..a6071900 --- /dev/null +++ b/examples/sdk-SrcSink/Source/SourceReactor.cmake @@ -0,0 +1,13 @@ +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SourceReactor.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/SourceReactor.cc" +) + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-SrcSink/Source/SourceReactor.hh b/examples/sdk-SrcSink/Source/SourceReactor.hh new file mode 100644 index 00000000..9a7a3b53 --- /dev/null +++ b/examples/sdk-SrcSink/Source/SourceReactor.hh @@ -0,0 +1,42 @@ +#pragma once + +#include <reactor-sdk/reactor-sdk.hh> +using namespace std; +using namespace sdk; + +class SourceReactor : public Reactor { +public: + struct Parameters : public SystemParametersStandalone<int> { + REACTOR_PARAMETER(int, iterations, "Number of iterations", 1, 100, 10); + REACTOR_PARAMETER(int, n_ports, "Size of multiports", 1, 10, 1); + + Parameters(Reactor *container) + : SystemParametersStandalone<int>(container) { + register_parameters (iterations, n_ports); + } + }; +private: + LogicalAction<int> sch{"sch", this}; + + Parameters parameters{this}; + + REACTION_SCOPE_START(SourceReactor, Parameters) + std::string name = "Source"; + int itr = 0; + int rsp_itr = 0; + + void add_reactions(SourceReactor *reactor); + REACTION_SCOPE_END(this, parameters) + +public: + SourceReactor(const std::string &name, Environment *env) + : Reactor(name, env) {} + SourceReactor(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + MultiportInput<int> rsp{"rsp", this}; + MultiportOutput<int> req{"req", this}; + + void construction() override; + void wiring() override; +}; \ No newline at end of file diff --git a/examples/sdk-SrcSink/example.png b/examples/sdk-SrcSink/example.png new file mode 100644 index 00000000..ba059554 Binary files /dev/null and b/examples/sdk-SrcSink/example.png differ diff --git a/examples/sdk-SrcSink/graph.dot b/examples/sdk-SrcSink/graph.dot new file mode 100644 index 00000000..03cc57c6 --- /dev/null +++ b/examples/sdk-SrcSink/graph.dot @@ -0,0 +1,44 @@ +digraph { +rankdir=LR; +subgraph { +rank=same; +Main_reaction_1 [label="Main.reaction_1"]; +Main_Source_reaction_1 [label="Main.Source.reaction_1"]; +Main_Sink_0_startup_reaction [label="Main.Sink_0.startup_reaction"]; +Main_Sink_1_startup_reaction [label="Main.Sink_1.startup_reaction"]; +Main_Sink_2_startup_reaction [label="Main.Sink_2.startup_reaction"]; +Main_Sink_3_startup_reaction [label="Main.Sink_3.startup_reaction"]; +} +subgraph { +rank=same; +Main_Source_reaction_2 [label="Main.Source.reaction_2"]; +} +subgraph { +rank=same; +Main_Sink_0_process_request [label="Main.Sink_0.process_request"]; +Main_Sink_1_process_request [label="Main.Sink_1.process_request"]; +Main_Sink_2_process_request [label="Main.Sink_2.process_request"]; +Main_Sink_3_process_request [label="Main.Sink_3.process_request"]; +} +subgraph { +rank=same; +Main_Source_reaction_3 [label="Main.Source.reaction_3"]; +} +Main_reaction_1 -> Main_Source_reaction_2 [style=invis]; +Main_Source_reaction_2 -> Main_Sink_0_process_request [style=invis]; +Main_Sink_0_process_request -> Main_Source_reaction_3 [style=invis]; +Main_Source_reaction_3 -> Main_Sink_0_process_request +Main_Source_reaction_3 -> Main_Sink_1_process_request +Main_Source_reaction_3 -> Main_Sink_2_process_request +Main_Source_reaction_3 -> Main_Sink_3_process_request +Main_Source_reaction_2 -> Main_Source_reaction_1 +Main_Source_reaction_3 -> Main_Source_reaction_2 +Main_Sink_0_process_request -> Main_Source_reaction_2 +Main_Sink_0_process_request -> Main_Sink_0_startup_reaction +Main_Sink_1_process_request -> Main_Source_reaction_2 +Main_Sink_1_process_request -> Main_Sink_1_startup_reaction +Main_Sink_2_process_request -> Main_Source_reaction_2 +Main_Sink_2_process_request -> Main_Sink_2_startup_reaction +Main_Sink_3_process_request -> Main_Source_reaction_2 +Main_Sink_3_process_request -> Main_Sink_3_startup_reaction +} diff --git a/examples/sdk-SrcSink/main.cc b/examples/sdk-SrcSink/main.cc new file mode 100644 index 00000000..ecf28acf --- /dev/null +++ b/examples/sdk-SrcSink/main.cc @@ -0,0 +1,51 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +#include "Config-a/Config-a.hh" +#include "Main/MainReactor.hh" + +using namespace std; +using namespace sdk; + +int main(int argc, char **argv) { + cxxopts::Options options("sdk-SrcSink", "Multiport source connecting to banked sink reactors"); + + unsigned workers = std::thread::hardware_concurrency(); + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + bool cfg_gen{false}; + + // the timeout variable needs to be tested beyond fitting the Duration-type + options + .set_width(120) + .add_options() + ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value<unsigned>(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value<reactor::Duration>(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value<bool>(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value<bool>(cfg_gen)->default_value("false")) + ("help", "Print help"); + + cxxopts::ParseResult result{}; + bool parse_error{false}; + try { + result = options.parse(argc, argv); + } catch (const cxxopts::OptionException& e) { + reactor::log::Error() << e.what(); + parse_error = true; + } + + // if parameter --help was used or there was a parse error, print help + if (parse_error || result.count("help")) + { + std::cout << options.help({""}); + return parse_error ? -1 : 0; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " cfg_gen:" << (cfg_gen ? "True" : "False") << std::endl; + + Environment sim {&cfg_parameters, workers, fast, timeout, cfg_gen}; + auto main = new MainReactor("Main", &sim, MainReactor::Parameters{.alias = "Test Param", .n_sinks = 3}); + + sim.run(); + return 0; +} diff --git a/examples/sdk-Workers/CMakeLists.txt b/examples/sdk-Workers/CMakeLists.txt new file mode 100644 index 00000000..65b13793 --- /dev/null +++ b/examples/sdk-Workers/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(workers VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET workers) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/examples/sdk-Workers/main.cc b/examples/sdk-Workers/main.cc new file mode 100644 index 00000000..07b080ff --- /dev/null +++ b/examples/sdk-Workers/main.cc @@ -0,0 +1,463 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +using namespace sdk; + +class Relay : public Reactor { +public: + struct Parameters { + int n_outputs; + }; + +private: + Parameters parameters; + const int &n_outputs = parameters.n_outputs; + + REACTION_SCOPE_START(Relay, Parameters) + // parameters + const int &n_outputs = parameters.n_outputs; + + // state variables + int index = 0; + int *busy = 0; + + void add_reactions(Relay *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(). + function( + [this](Startup& startup) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + busy = (int*) calloc (n_outputs, sizeof(int)); + } + ); + + reaction("reaction_2"). + triggers(&reactor->in_req). + dependencies(). + effects(&reactor->all_workers_busy, &reactor->out_req). + function( + [this](Input<int> &in_req, Output<bool> &all_workers_busy, MultiportOutput<int> &out_req) { + for (int i = 0; i < n_outputs; ++i, index = (index + 1) % n_outputs) { + if (busy[index] == 0) { + out_req[index].set(*in_req.get()); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << *in_req.get() << " to worker:" << index << std::endl; + busy[index] = 1; + index = (index + 1) % n_outputs; + break; + } + } + int busy_count = 0; + for (int i = 0; i < n_outputs; ++i) { + busy_count = busy[i] ? (busy_count + 1) : busy_count; + } + + if (busy_count == n_outputs) { + all_workers_busy.set(true); + } + } + ); + + reaction("reaction_3"). + triggers(&reactor->in_rsp). + dependencies(). + effects(&reactor->out_rsp). + function( + [this](MultiportInput<int> &in_rsp, Output<int> &out_rsp) { + for (int i = 0; i < n_outputs; ++i) { + if (in_rsp[i].is_present()) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Receiving task_id:" << *in_rsp[i].get() << " from worker:" << i << std::endl; + busy[i] = 0; + out_rsp.set(*in_rsp[i].get()); + } + } + } + ); + + reaction("reaction_4"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function( + [this](Shutdown &shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + } + ); + } + REACTION_SCOPE_END(this, parameters) + + +public: + Input<int> in_req{"in_req", this}; + Output<int> out_rsp{"out_rsp", this}; + MultiportOutput<int> out_req{"out_req", this}; + MultiportInput<int> in_rsp{"in_rsp", this}; + + Output<bool> all_workers_busy{"all_workers_busy", this}; + + Relay(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward<Parameters>(param)} {} + Relay(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward<Parameters>(param)} {} + + void construction() override { + out_req.set_width(n_outputs); + in_rsp.set_width(n_outputs); + } + + void wiring() override {} +}; + +class Worker : public Reactor { +public: + struct Parameters { + Duration processing_delay = 2s; + }; + +private: + LogicalAction<int> sch_rsp{"sch_rsp", this}; + + Parameters parameters; + const Duration &processing_delay = parameters.processing_delay; + + REACTION_SCOPE_START(Worker, Parameters) + const Duration &processing_delay = parameters.processing_delay; + + void add_reactions(Worker *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(). + function( + [this](Startup& startup) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + } + ); + + reaction("reaction_2"). + triggers(&reactor->req). + dependencies(). + effects(&reactor->sch_rsp). + function( + [this](Input<int> &req, LogicalAction<int> &sch_rsp) { + auto req_ref = *req.get(); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Receiving task_id:" << req_ref << std::endl; + sch_rsp.schedule (req_ref, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(processing_delay))); + } + ); + + reaction("reaction_3"). + triggers(&reactor->sch_rsp). + dependencies(). + effects(&reactor->rsp). + function( + [this](LogicalAction<int> &sch_rsp, Output<int> &rsp) { + auto req_ref = *sch_rsp.get(); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_ref << std::endl; + rsp.set(req_ref); + } + ); + + reaction("reaction_4"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function( + [this](Shutdown &shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + } + ); + } + REACTION_SCOPE_END(this, parameters) + +public: + Worker(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward<Parameters>(param)} {} + Worker(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward<Parameters>(param)} {} + + Input<int> req{"req", this}; + Output<int> rsp{"rsp", this}; + + void construction() override {} + void wiring() override {} +}; + + +class Pool : public Reactor { +public: + struct Parameters { + int n_workers = 1; + }; + +private: + Parameters parameters; + const int &n_workers = parameters.n_workers; + + LogicalAction<int> sch_rsp{"sch_rsp", this}; + + ReactorBank<Worker> workers{"workers", this}; + std::unique_ptr<Relay> relay; + + REACTION_SCOPE_START(Pool, Parameters) + const int &n_workers = parameters.n_workers; + + void add_reactions(Pool *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(). + function( + [this](Startup& startup) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + } + ); + + reaction("reaction_2"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function( + [this](Shutdown &shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + } + ); + } + REACTION_SCOPE_END(this, parameters) + +public: + Pool(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward<Parameters>(param)} {} + Pool(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward<Parameters>(param)} {} + + Input<int> req{"req", this}; + Output<int> rsp{"rsp", this}; + + Output<bool> all_workers_busy{"all_workers_busy", this}; + + void construction() override { + for (int i = 0; i < n_workers; ++i) { + workers.create_reactor(Worker::Parameters{.processing_delay = 1s}); + } + relay = std::make_unique<Relay>("relay", this, Relay::Parameters{.n_outputs = n_workers}); + } + + void wiring() override { + req --> relay->in_req; + relay->out_req --> workers.for_each(select_default(workers).req); + workers.for_each(select_default(workers).rsp) --> relay->in_rsp; + relay->out_rsp --> rsp; + + relay->all_workers_busy --> all_workers_busy; + } +}; + +class Tasks : public Reactor { +public: + struct Parameters { + int n_tasks = 10; + int n_pools = 1; + }; + +private: + Parameters parameters; + const int &n_tasks = parameters.n_tasks; + const int &n_pools = parameters.n_pools; + + LogicalAction<int> sch{"sch", this}; + + REACTION_SCOPE_START(Tasks, Parameters) + const int &n_tasks = parameters.n_tasks; + const int &n_pools = parameters.n_pools; + + int req_itr = 0; + int rsp_itr = 0; + bool *busy = 0; + + void add_reactions(Tasks *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(&reactor->sch). + function( + [this](Startup& startup, LogicalAction<int> &sch) { + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup n_pools:" << n_pools << "\n"; + busy = (bool*) calloc (n_pools, sizeof(bool)); + } + ); + + reaction("reaction_2"). + triggers(&reactor->sch). + dependencies(). + effects(&reactor->req). + function( + [this](LogicalAction<int> &sch, MultiportOutput<int> &req) { + if (req_itr == n_tasks) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Tasks queue empty" << std::endl; + return; + } + auto index = *sch.get(); + if (index < 0) { + for (int i = 0; i < n_pools; ++i) { + if (busy[i]) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Busy Pool:" << i << std::endl; + continue; + } + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_itr << " to pool:" << i << std::endl; + req[i].set (req_itr++); + } + + int busy_count = 0; + for (int i = 0; i < n_pools; ++i) { + busy_count = busy[i] ? (busy_count + 1) : busy_count; + } + + if (busy_count == n_pools) { + return; + } + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + } else { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_itr << " to pool:" << index << std::endl; + req[index].set (req_itr++); + } + } + ); + + reaction("reaction_3"). + triggers(&reactor->rsp). + dependencies(). + effects(&reactor->sch). + function( + [this](MultiportInput<int> &rsp, LogicalAction<int> &sch) { + for (int i = 0; i < n_pools; ++i) { + if (rsp[i].is_present()) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << "Received response of task:" << *rsp[i].get() << "\n"; + ++rsp_itr; + busy[i] = 0; + } + } + if (rsp_itr == n_tasks) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << "Terminating Run\n"; + request_stop(); + } else { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Scheduling tasks\n"; + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + } + } + ); + + reaction("reaction_4"). + triggers(&reactor->hybernate). + dependencies(). + effects(). + function( + [this](MultiportInput<bool> &hybernate) { + for (int i = 0; i < n_pools; ++i) { + if (hybernate[i].is_present()) { + busy[i] = *hybernate[i].get(); + } + } + } + ); + + reaction("reaction_5"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function( + [this](Shutdown &shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + } + ); + } + REACTION_SCOPE_END(this, parameters) + +public: + Tasks(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward<Parameters>(param)} {} + Tasks(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward<Parameters>(param)} {} + + MultiportOutput<int> req{"req", this}; + MultiportInput<int> rsp{"rsp", this}; + MultiportInput<bool> hybernate{"hybernate", this}; + + void construction() override { + req.set_width (n_pools); + rsp.set_width (n_pools); + hybernate.set_width (n_pools); + } + + void wiring() override {} +}; + +class Main : public Reactor { +public: + struct Parameters { + int n_tasks = 10; + int n_pools = 2; + int n_workers = 4; + }; + + Main(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward<Parameters>(param)} {} + Main(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward<Parameters>(param)} {} +private: + Parameters parameters; + const int &n_tasks = parameters.n_tasks; + const int &n_pools = parameters.n_pools; + const int &n_workers = parameters.n_workers; + + std::unique_ptr<Tasks> tasks; + ReactorBank<Pool> pool{"pool", this}; + +public: + void construction() override { + tasks = std::make_unique<Tasks>("tasks", this, Tasks::Parameters{.n_tasks = n_tasks, .n_pools = n_pools}); + for (int i = 0; i < n_pools; ++i) { + pool.create_reactor(Pool::Parameters{.n_workers = n_workers}); + } + } + + void wiring() override { + tasks->req --> pool.for_each(select_default(pool).req); + pool.for_each(select_default(pool).rsp) --> tasks->rsp; + pool.for_each(select_default(pool).all_workers_busy) --> tasks->hybernate; + } +}; + +int main(int argc, char **argv) { + Environment env {nullptr, 4, false, reactor::Duration::max(), false}; + + int n_tasks = 10; + int n_pools = 2; + int n_workers = 4; + + auto main = new Main("Main", &env, Main::Parameters{.n_tasks = n_tasks, .n_pools = n_pools, .n_workers = n_workers}); + + env.run(); + return 0; +} diff --git a/examples/sdk-Workers/src/Workers.lf b/examples/sdk-Workers/src/Workers.lf new file mode 100644 index 00000000..d0db26e4 --- /dev/null +++ b/examples/sdk-Workers/src/Workers.lf @@ -0,0 +1,219 @@ +target Cpp { + fast: false, + // logging: debug +} + +// lf_set_destructor(alloc_rsp, cache_entry_destructor); + +public preamble {= +#include <stdint.h> +=} + +reactor Relay (bank_index:size_t = 0, n_outputs:int = 1) { + input in_req:int; + output out_rsp:int; + output [n_outputs] out_req:int; + input [n_outputs] in_rsp:int; + + output all_workers_busy:bool; + + state index:int = 0; + state busy:int* = 0; + + reaction (startup) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + busy = (int*) calloc (n_outputs, sizeof(int)); + =} + + reaction (in_req) -> all_workers_busy, out_req {= + for (int i = 0; i < n_outputs; ++i, index = (index + 1) % n_outputs) { + if (busy[index] == 0) { + out_req[index].set(*in_req.get()); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << *in_req.get() << " to worker:" << index << std::endl; + busy[index] = 1; + index = (index + 1) % n_outputs; + break; + } + } + int busy_count = 0; + for (int i = 0; i < n_outputs; ++i) { + busy_count = busy[i] ? (busy_count + 1) : busy_count; + } + + if (busy_count == n_outputs) { + all_workers_busy.set(true); + } + =} + + reaction (in_rsp) -> out_rsp {= + for (int i = 0; i < n_outputs; ++i) { + if (in_rsp[i].is_present()) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Receiving task_id:" << *in_rsp[i].get() << " from worker:" << i << std::endl; + busy[i] = 0; + out_rsp.set(*in_rsp[i].get()); + } + } + =} + + reaction (shutdown) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + =} +} + +reactor Worker (bank_index:size_t = 0, processing_delay:time = 2s) { + input req:int; + output rsp:int; + + logical action sch_rsp(0):int; + + reaction (startup) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + =} + + reaction (req) -> sch_rsp {= + auto req_ref = *req.get(); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Receiving task_id:" << req_ref << std::endl; + sch_rsp.schedule (req_ref, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(processing_delay))); + =} + + reaction (sch_rsp) -> rsp {= + auto req_ref = *sch_rsp.get(); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_ref << std::endl; + rsp.set(req_ref); + =} + + reaction (shutdown) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + =} +} + + +reactor Pool (bank_index:size_t = 0, n_workers:int = 1) { + + input req:int; + output rsp:int; + + output all_workers_busy:bool; + + logical action sch_rsp(0):int; + + workers = new [n_workers] Worker(processing_delay = 1s); + relay = new Relay(n_outputs = n_workers); + + reaction (startup) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + =} + + reaction (shutdown) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + =} + + req -> relay.in_req; + relay.out_req -> workers.req; + workers.rsp -> relay.in_rsp; + relay.out_rsp -> rsp; + + relay.all_workers_busy -> all_workers_busy; + +} + +reactor Tasks (bank_index:size_t = 0, n_tasks:int = 10, n_pools:int = 1) { + + output[n_pools] req:int; + input[n_pools] rsp:int; + + input[n_pools] hybernate:bool; + + state req_itr:int = 0; + state rsp_itr:int = 0; + state busy:bool* = 0; + + logical action sch(0):int; + + reaction (startup) -> sch {= + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + busy = (bool*) calloc (n_pools, sizeof(bool)); + =} + + reaction (sch) -> sch, req {= + auto index = *sch.get(); + if (index < 0) { + for (int i = 0; i < n_pools; ++i) { + if (busy[i]) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Busy Pool:" << i << std::endl; + continue; + } + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_itr << " to pool:" << i << std::endl; + req[i].set (req_itr++); + } + + int busy_count = 0; + for (int i = 0; i < n_pools; ++i) { + busy_count = busy[i] ? (busy_count + 1) : busy_count; + } + + if (busy_count == n_pools) { + return; + } + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + } else { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_itr << " to pool:" << index << std::endl; + req[index].set (req_itr++); + } + =} + + reaction (rsp) -> sch {= + for (int i = 0; i < n_pools; ++i) { + if (rsp[i].is_present()) { + ++rsp_itr; + if (req_itr < n_tasks) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Scheduling task_id:" << req_itr << " to pool:" << i << std::endl; + sch.schedule (i, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + } + } + } + if (rsp_itr == n_tasks) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << "Terminating Run\n"; + request_stop(); + } + =} + + reaction (hybernate) {= + for (int i = 0; i < n_pools; ++i) { + if (hybernate[i].is_present()) { + busy[i] = *hybernate[i].get(); + } + } + =} + + reaction (shutdown) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + =} +} + +main reactor (n_tasks:int = 10, n_pools:int = 2, n_workers:int = 4) { + tasks = new Tasks(n_tasks = n_tasks, n_pools = n_pools); + pool = new [n_pools] Pool(n_workers = n_workers); + + tasks.req -> pool.req; + pool.rsp -> tasks.rsp; + pool.all_workers_busy -> tasks.hybernate; +} \ No newline at end of file diff --git a/examples/sdk-WorkersParams/CMakeLists.txt b/examples/sdk-WorkersParams/CMakeLists.txt new file mode 100644 index 00000000..159ed788 --- /dev/null +++ b/examples/sdk-WorkersParams/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(workers_with_params VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET workers_with_params) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/examples/sdk-WorkersParams/main.cc b/examples/sdk-WorkersParams/main.cc new file mode 100644 index 00000000..88056e72 --- /dev/null +++ b/examples/sdk-WorkersParams/main.cc @@ -0,0 +1,567 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +using namespace sdk; + +struct UserParameters : public ConfigParameter<Duration, int> { + ConfigParameter<Duration, int>::ParametersMap homogeneous_config(); + ConfigParameter<Duration, int>::ParametersMap heterogeneous_config(); +}; + +UserParameters cfg_parameters; + +ConfigParameter<Duration, int>::ParametersMap UserParameters::homogeneous_config() { + return { + { "Main.n_pools", ConfigParameterMetadata<int> {2} }, + { "Main.n_tasks", ConfigParameterMetadata<int> {10} }, + { "Main.pool.n_workers", ConfigParameterMetadata<int> {4} }, + { "Main.pool.workers.processing_delay", ConfigParameterMetadata<Duration> {1000000000ns} } + }; +} + +ConfigParameter<Duration, int>::ParametersMap UserParameters::heterogeneous_config() { + return { + { "Main.n_pools", ConfigParameterMetadata<int> {2} }, + { "Main.n_tasks", ConfigParameterMetadata<int> {10} }, + { "Main.pool_0.n_workers", ConfigParameterMetadata<int> {2} }, + { "Main.pool_0.workers_0.processing_delay", ConfigParameterMetadata<Duration> {100000000ns} }, + { "Main.pool_0.workers_1.processing_delay", ConfigParameterMetadata<Duration> {1000000000ns} }, + { "Main.pool_1.n_workers", ConfigParameterMetadata<int> {3} }, + { "Main.pool_1.workers_0.processing_delay", ConfigParameterMetadata<Duration> {1000000000ns} }, + { "Main.pool_1.workers_1.processing_delay", ConfigParameterMetadata<Duration> {100000000ns} }, + { "Main.pool_1.workers_2.processing_delay", ConfigParameterMetadata<Duration> {1000000000ns} }, + }; +} + +class Relay : public Reactor { +public: + struct Parameters { + int n_outputs; + }; + +private: + Parameters parameters; + const int &n_outputs = parameters.n_outputs; + + REACTION_SCOPE_START(Relay, Parameters) + const int &n_outputs = parameters.n_outputs; + + int index = 0; + int *busy = 0; + + void add_reactions(Relay *reactor) override { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(). + function( + [this](Startup& startup) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + busy = (int*) calloc (n_outputs, sizeof(int)); + } + ); + + reaction("reaction_2"). + triggers(&reactor->in_req). + dependencies(). + effects(&reactor->all_workers_busy, &reactor->out_req). + function( + [this](Input<int> &in_req, Output<bool> &all_workers_busy, MultiportOutput<int> &out_req) { + for (int i = 0; i < n_outputs; ++i, index = (index + 1) % n_outputs) { + if (busy[index] == 0) { + out_req[index].set(*in_req.get()); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << *in_req.get() << " to worker:" << index << std::endl; + busy[index] = 1; + index = (index + 1) % n_outputs; + break; + } + } + int busy_count = 0; + for (int i = 0; i < n_outputs; ++i) { + busy_count = busy[i] ? (busy_count + 1) : busy_count; + } + + if (busy_count == n_outputs) { + all_workers_busy.set(true); + } + } + ); + + reaction("reaction_3"). + triggers(&reactor->in_rsp). + dependencies(). + effects(&reactor->out_rsp). + function( + [this](MultiportInput<int> &in_rsp, Output<int> &out_rsp) { + for (int i = 0; i < n_outputs; ++i) { + if (in_rsp[i].is_present()) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Receiving task_id:" << *in_rsp[i].get() << " from worker:" << i << std::endl; + busy[i] = 0; + out_rsp.set(*in_rsp[i].get()); + } + } + } + ); + + reaction("reaction_4"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function( + [this](Shutdown &shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + } + ); + } + REACTION_SCOPE_END(this, parameters) + +public: + Input<int> in_req{"in_req", this}; + Output<int> out_rsp{"out_rsp", this}; + MultiportOutput<int> out_req{"out_req", this}; + MultiportInput<int> in_rsp{"in_rsp", this}; + + Output<bool> all_workers_busy{"all_workers_busy", this}; + + Relay(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward<Parameters>(param)} {} + Relay(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward<Parameters>(param)} {} + + void construction() override { + out_req.set_width(n_outputs); + in_rsp.set_width(n_outputs); + } + + void wiring() override { + } +}; + +class Worker : public Reactor { +public: + struct Parameters { + Duration processing_delay = 2s; + }; + +private: + LogicalAction<int> sch_rsp{"sch_rsp", this}; + + struct PublishParameters : public SystemParameters<Parameters, Duration> { + REACTOR_PARAMETER(Duration, processing_delay, "Worker's processing delay", 1ms, 2s, defaults.processing_delay); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters<Parameters, Duration>(container, std::forward<Parameters>(param)) { + register_parameters (processing_delay); + } + }; + + PublishParameters parameters; + class Chamber : public ReactionChamber<Worker, PublishParameters> { + const Duration &processing_delay = parameters.processing_delay.value; + public: + Chamber(Reactor *reactor, PublishParameters ¶ms) + : ReactionChamber<Worker, PublishParameters>(reactor, params) {} + + void add_reactions(Worker *reactor) override { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(). + function( + [this](Startup& startup) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + } + ); + + reaction("reaction_2"). + triggers(&reactor->req). + dependencies(). + effects(&reactor->sch_rsp). + function( + [this](Input<int> &req, LogicalAction<int> &sch_rsp) { + auto req_ref = *req.get(); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Receiving task_id:" << req_ref << std::endl; + sch_rsp.schedule (req_ref, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(processing_delay))); + } + ); + + reaction("reaction_3"). + triggers(&reactor->sch_rsp). + dependencies(). + effects(&reactor->rsp). + function( + [this](LogicalAction<int> &sch_rsp, Output<int> &rsp) { + auto req_ref = *sch_rsp.get(); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_ref << std::endl; + rsp.set(req_ref); + } + ); + + reaction("reaction_4"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function( + [this](Shutdown &shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + } + ); + } + + + }; + Chamber reaction_chamber{this, parameters}; + +public: + Worker(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{this, std::forward<Parameters>(param)} {} + Worker(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{this, std::forward<Parameters>(param)} {} + + Input<int> req{"req", this}; + Output<int> rsp{"rsp", this}; + + void construction() override {} + void wiring() override { + } +}; + + +class Pool : public Reactor { +public: + struct Parameters { + int n_workers = 1; + }; + +private: + struct PublishParameters : public SystemParameters<Parameters, int> { + REACTOR_PARAMETER(int, n_workers, "Number of workers in the pool", 1, 10, defaults.n_workers); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters<Parameters, int>(container, std::forward<Parameters>(param)) { + register_parameters (n_workers); + } + }; + PublishParameters parameters; + const int &n_workers = parameters.n_workers.value; + + LogicalAction<int> sch_rsp{"sch_rsp", this}; + + ReactorBank<Worker> workers{"workers", this}; + std::unique_ptr<Relay> relay; + + class Chamber : public ReactionChamber<Pool, PublishParameters> { + public: + Chamber(Reactor *reactor, PublishParameters ¶ms) + : ReactionChamber<Pool, PublishParameters>(reactor, params) {} + + void add_reactions(Pool *reactor) override { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(). + function( + [this](Startup& startup) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + } + ); + + reaction("reaction_2"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function( + [this](Shutdown &shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + } + ); + } + + + }; + + Chamber reaction_chamber{this, parameters}; + +public: + Pool(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{this, std::forward<Parameters>(param)} {} + Pool(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{this, std::forward<Parameters>(param)} {} + + Input<int> req{"req", this}; + Output<int> rsp{"rsp", this}; + + Output<bool> all_workers_busy{"all_workers_busy", this}; + + void construction() override { + for (int i = 0; i < n_workers; ++i) { + workers.create_reactor(Worker::Parameters{.processing_delay = 1s}); + } + relay = std::make_unique<Relay>("relay", this, Relay::Parameters{.n_outputs = n_workers}); + + } + + void wiring() override { + req --> relay->in_req; + relay->out_req --> workers.for_each(select_default(workers).req); + workers.for_each(select_default(workers).rsp) --> relay->in_rsp; + relay->out_rsp --> rsp; + + relay->all_workers_busy --> all_workers_busy; + } +}; + +class Tasks : public Reactor { +public: + struct Parameters { + int n_tasks = 10; + int n_pools = 1; + }; + +private: + Parameters parameters; + const int &n_pools = parameters.n_pools; + + LogicalAction<int> sch{"sch", this}; + + class Chamber : public ReactionChamber<Tasks, Parameters> { + const int &n_tasks = parameters.n_tasks; + const int &n_pools = parameters.n_pools; + + int req_itr = 0; + int rsp_itr = 0; + bool *busy = 0; + public: + Chamber(Reactor *reactor, Parameters ¶ms) + : ReactionChamber<Tasks, Parameters>(reactor, params) {} + + void add_reactions(Tasks *reactor) override { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(&reactor->sch). + function( + [this](Startup& startup, LogicalAction<int> &sch) { + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup n_pools:" << n_pools << "\n"; + busy = (bool*) calloc (n_pools, sizeof(bool)); + } + ); + + reaction("reaction_2"). + triggers(&reactor->sch). + dependencies(). + effects(&reactor->req). + function( + [this](LogicalAction<int> &sch, MultiportOutput<int> &req) { + if (req_itr == n_tasks) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Tasks queue empty" << std::endl; + return; + } + auto index = *sch.get(); + if (index < 0) { + for (int i = 0; i < n_pools; ++i) { + if (busy[i]) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Busy Pool:" << i << std::endl; + continue; + } + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_itr << " to pool:" << i << std::endl; + req[i].set (req_itr++); + } + + int busy_count = 0; + for (int i = 0; i < n_pools; ++i) { + busy_count = busy[i] ? (busy_count + 1) : busy_count; + } + + if (busy_count == n_pools) { + return; + } + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + } else { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_itr << " to pool:" << index << std::endl; + req[index].set (req_itr++); + } + } + ); + + reaction("reaction_3"). + triggers(&reactor->rsp). + dependencies(). + effects(&reactor->sch). + function( + [this](MultiportInput<int> &rsp, LogicalAction<int> &sch) { + for (int i = 0; i < n_pools; ++i) { + if (rsp[i].is_present()) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << "Received response of task:" << *rsp[i].get() << "\n"; + ++rsp_itr; + busy[i] = 0; + } + } + if (rsp_itr == n_tasks) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << "Terminating Run\n"; + request_stop(); + } else { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Scheduling tasks\n"; + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + } + } + ); + + reaction("reaction_4"). + triggers(&reactor->hybernate). + dependencies(). + effects(). + function( + [this](MultiportInput<bool> &hybernate) { + for (int i = 0; i < n_pools; ++i) { + if (hybernate[i].is_present()) { + busy[i] = *hybernate[i].get(); + } + } + } + ); + + reaction("reaction_5"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function( + [this](Shutdown &shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + } + ); + } + }; + + Chamber reaction_chamber{this, parameters}; + +public: + Tasks(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward<Parameters>(param)} {} + Tasks(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward<Parameters>(param)} {} + + MultiportOutput<int> req{"req", this}; + MultiportInput<int> rsp{"rsp", this}; + MultiportInput<bool> hybernate{"hybernate", this}; + + void construction() override { + req.set_width (n_pools); + rsp.set_width (n_pools); + hybernate.set_width (n_pools); + } + + void wiring() override { + } +}; + +class Main : public Reactor { +public: + struct Parameters { + int n_tasks = 10; + int n_pools = 2; + int n_workers = 4; + }; + + Main(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{this, std::forward<Parameters>(param)} {} + Main(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{this, std::forward<Parameters>(param)} {} +private: + struct PublishParameters : public SystemParameters<Parameters, int> { + REACTOR_PARAMETER(int, n_tasks, "Number of tasks", 1, 100, defaults.n_tasks); + REACTOR_PARAMETER(int, n_pools, "Number of pools", 1, 10, defaults.n_pools); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters<Parameters, int>(container, std::forward<Parameters>(param)) { + register_parameters (n_tasks, n_pools); + } + }; + PublishParameters parameters; + const int &n_tasks = parameters.n_tasks.value; + const int &n_pools = parameters.n_pools.value; + const int &n_workers = parameters.defaults.n_workers; + + std::unique_ptr<Tasks> tasks; + ReactorBank<Pool> pool{"pool", this}; + +public: + + void construction() override { + tasks = std::make_unique<Tasks>("tasks", this, Tasks::Parameters{.n_tasks = n_tasks, .n_pools = n_pools}); + for (int i = 0; i < n_pools; ++i) { + pool.create_reactor(Pool::Parameters{.n_workers = n_workers}); + } + } + + void wiring() override { + tasks->req --> pool.for_each(select_default(pool).req); + pool.for_each(select_default(pool).rsp) --> tasks->rsp; + pool.for_each(select_default(pool).all_workers_busy) --> tasks->hybernate; + } +}; + +int main(int argc, char **argv) { + cxxopts::Options options("Workers-Example", "Multiport source connecting to banked sink reactors"); + + unsigned workers = std::thread::hardware_concurrency(); + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + bool cfg_gen{false}; + + // the timeout variable needs to be tested beyond fitting the Duration-type + options + .set_width(120) + .add_options() + ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value<unsigned>(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value<reactor::Duration>(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value<bool>(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value<bool>(cfg_gen)->default_value("false")) + ("help", "Print help"); + + cxxopts::ParseResult result{}; + bool parse_error{false}; + try { + result = options.parse(argc, argv); + } catch (const cxxopts::OptionException& e) { + reactor::log::Error() << e.what(); + parse_error = true; + } + + // if parameter --help was used or there was a parse error, print help + if (parse_error || result.count("help")) + { + std::cout << options.help({""}); + return parse_error ? -1 : 0; + } + Environment env {nullptr, workers, fast, timeout, cfg_gen}; + + int n_tasks = 10; + int n_pools = 2; + int n_workers = 4; + + auto main = new Main("Main", &env, Main::Parameters{.n_tasks = n_tasks, .n_pools = n_pools, .n_workers = n_workers}); + + env.run(); + return 0; +} diff --git a/examples/sdk-WorkersParams/src/Workers.lf b/examples/sdk-WorkersParams/src/Workers.lf new file mode 100644 index 00000000..6afebc9e --- /dev/null +++ b/examples/sdk-WorkersParams/src/Workers.lf @@ -0,0 +1,226 @@ +target Cpp { + fast: false, + // logging: debug +} + +// lf_set_destructor(alloc_rsp, cache_entry_destructor); + +public preamble {= +#include <stdint.h> +=} + +reactor Relay (bank_index:size_t = 0, n_outputs:int = 1) { + input in_req:int; + output out_rsp:int; + output [n_outputs] out_req:int; + input [n_outputs] in_rsp:int; + + output all_workers_busy:bool; + + state index:int = 0; + state busy:int* = 0; + + reaction (startup) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + busy = (int*) calloc (n_outputs, sizeof(int)); + =} + + reaction (in_req) -> all_workers_busy, out_req {= + for (int i = 0; i < n_outputs; ++i, index = (index + 1) % n_outputs) { + if (busy[index] == 0) { + out_req[index].set(*in_req.get()); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << *in_req.get() << " to worker:" << index << std::endl; + busy[index] = 1; + index = (index + 1) % n_outputs; + break; + } + } + int busy_count = 0; + for (int i = 0; i < n_outputs; ++i) { + busy_count = busy[i] ? (busy_count + 1) : busy_count; + } + + if (busy_count == n_outputs) { + all_workers_busy.set(true); + } + =} + + reaction (in_rsp) -> out_rsp {= + for (int i = 0; i < n_outputs; ++i) { + if (in_rsp[i].is_present()) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Receiving task_id:" << *in_rsp[i].get() << " from worker:" << i << std::endl; + busy[i] = 0; + out_rsp.set(*in_rsp[i].get()); + } + } + =} + + reaction (shutdown) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + =} +} + +reactor Worker (bank_index:size_t = 0, processing_delay:time = 2s) { + input req:int; + output rsp:int; + + logical action sch_rsp(0):int; + + reaction (startup) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + =} + + reaction (req) -> sch_rsp {= + auto req_ref = *req.get(); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Receiving task_id:" << req_ref << std::endl; + sch_rsp.schedule (req_ref, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(processing_delay))); + =} + + reaction (sch_rsp) -> rsp {= + auto req_ref = *sch_rsp.get(); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_ref << std::endl; + rsp.set(req_ref); + =} + + reaction (shutdown) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + =} +} + + +reactor Pool (bank_index:size_t = 0, n_workers:int = 1) { + + input req:int; + output rsp:int; + + output all_workers_busy:bool; + + logical action sch_rsp(0):int; + + workers = new [n_workers] Worker(processing_delay = 1s); + relay = new Relay(n_outputs = n_workers); + + reaction (startup) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + =} + + reaction (shutdown) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + =} + + req -> relay.in_req; + relay.out_req -> workers.req; + workers.rsp -> relay.in_rsp; + relay.out_rsp -> rsp; + + relay.all_workers_busy -> all_workers_busy; + +} + +reactor Tasks (bank_index:size_t = 0, n_tasks:int = 10, n_pools:int = 1) { + + output[n_pools] req:int; + input[n_pools] rsp:int; + + input[n_pools] hybernate:bool; + + state req_itr:int = 0; + state rsp_itr:int = 0; + state busy:bool* = 0; + + logical action sch(0):int; + + reaction (startup) -> sch {= + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Startup\n"; + busy = (bool*) calloc (n_pools, sizeof(bool)); + =} + + reaction (sch) -> sch, req {= + if (req_itr == n_tasks) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Tasks queue empty" << std::endl; + return; + } + auto index = *sch.get(); + if (index < 0) { + for (int i = 0; i < n_pools; ++i) { + if (busy[i]) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Busy Pool:" << i << std::endl; + continue; + } + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_itr << " to pool:" << i << std::endl; + req[i].set (req_itr++); + } + + int busy_count = 0; + for (int i = 0; i < n_pools; ++i) { + busy_count = busy[i] ? (busy_count + 1) : busy_count; + } + + if (busy_count == n_pools) { + return; + } + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + } else { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Sending task_id:" << req_itr << " to pool:" << index << std::endl; + req[index].set (req_itr++); + } + =} + + reaction (rsp) -> sch {= + for (int i = 0; i < n_pools; ++i) { + if (rsp[i].is_present()) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << "Received response of task:" << *rsp[i].get() << "\n"; + ++rsp_itr; + busy[i] = 0; + } + } + if (rsp_itr == n_tasks) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << "Terminating Run\n"; + request_stop(); + } else { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Scheduling tasks\n"; + sch.schedule (-1, std::chrono::duration_cast<reactor::Duration>(std::chrono::nanoseconds(0))); + } + =} + + reaction (hybernate) {= + for (int i = 0; i < n_pools; ++i) { + if (hybernate[i].is_present()) { + busy[i] = *hybernate[i].get(); + } + } + =} + + reaction (shutdown) {= + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Shutdown\n"; + =} +} + +main reactor (n_tasks:int = 10, n_pools:int = 2, n_workers:int = 4) { + tasks = new Tasks(n_tasks = n_tasks, n_pools = n_pools); + pool = new [n_pools] Pool(n_workers = n_workers); + + tasks.req -> pool.req; + pool.rsp -> tasks.rsp; + pool.all_workers_busy -> tasks.hybernate; +} \ No newline at end of file diff --git a/examples/sdk-deadlines/CMakeLists.txt b/examples/sdk-deadlines/CMakeLists.txt new file mode 100644 index 00000000..b6035a02 --- /dev/null +++ b/examples/sdk-deadlines/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.9) +project(node VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET node) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) + +include(Node/NodeReactor.cmake) +include(Main/MainReactor.cmake) +include(Config-a/Config-a.cmake) \ No newline at end of file diff --git a/examples/sdk-deadlines/Config-a/Config-a.cc b/examples/sdk-deadlines/Config-a/Config-a.cc new file mode 100644 index 00000000..0aa1a421 --- /dev/null +++ b/examples/sdk-deadlines/Config-a/Config-a.cc @@ -0,0 +1,18 @@ +#include "Config-a.hh" + +UserParameters cfg_parameters; + +ConfigParameter<int, Duration>::ParametersMap UserParameters::homogeneous_config() { + return { + }; +} + +ConfigParameter<int, Duration>::ParametersMap UserParameters::heterogeneous_config() { + return { + {"Main.slow.period", ConfigParameterMetadata<Duration> { 1s } }, + {"Main.slow.duration", ConfigParameterMetadata<Duration> { 5s } }, + {"Main.n_fast", ConfigParameterMetadata<int> { 3 } }, + {"Main.fast_0.period", ConfigParameterMetadata<Duration> { 500ms } }, + {"Main.fast_0.duration", ConfigParameterMetadata<Duration> { 10ms } } + }; +} \ No newline at end of file diff --git a/examples/sdk-deadlines/Config-a/Config-a.cmake b/examples/sdk-deadlines/Config-a/Config-a.cmake new file mode 100644 index 00000000..bd7049e4 --- /dev/null +++ b/examples/sdk-deadlines/Config-a/Config-a.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Config-a.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/Config-a.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-deadlines/Config-a/Config-a.hh b/examples/sdk-deadlines/Config-a/Config-a.hh new file mode 100644 index 00000000..e23e5e2d --- /dev/null +++ b/examples/sdk-deadlines/Config-a/Config-a.hh @@ -0,0 +1,18 @@ +#ifndef USER_PARAMETERS_H +#define USER_PARAMETERS_H + +#include <reactor-sdk/reactor-sdk.hh> +#include <map> +#include <variant> +#include <string> + +using namespace sdk; + +struct UserParameters : public ConfigParameter<int, Duration> { + ConfigParameter<int, Duration>::ParametersMap homogeneous_config(); + ConfigParameter<int, Duration>::ParametersMap heterogeneous_config(); +}; + +extern UserParameters cfg_parameters; + +#endif // USER_PARAMETERS_H diff --git a/examples/sdk-deadlines/Main/MainReactor.cc b/examples/sdk-deadlines/Main/MainReactor.cc new file mode 100644 index 00000000..0f6ef5ab --- /dev/null +++ b/examples/sdk-deadlines/Main/MainReactor.cc @@ -0,0 +1,16 @@ +#include "MainReactor.hh" + +void MainReactor::construction() { + + std::cout << "Construction Main n_fast:" << parameters.n_fast.value << "\n"; + + slow = std::make_unique<NodeReactor>("slow", this); + + for (int i = 0; i < parameters.n_fast.value; i++) { + fast.create_reactor(); + } +} + +void MainReactor::wiring() { + std::cout << "Wiring Main n_sinks:" << parameters.n_fast.value << "\n"; +} \ No newline at end of file diff --git a/examples/sdk-deadlines/Main/MainReactor.cmake b/examples/sdk-deadlines/Main/MainReactor.cmake new file mode 100644 index 00000000..4c6cc870 --- /dev/null +++ b/examples/sdk-deadlines/Main/MainReactor.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/MainReactor.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/MainReactor.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-deadlines/Main/MainReactor.hh b/examples/sdk-deadlines/Main/MainReactor.hh new file mode 100644 index 00000000..62031b9c --- /dev/null +++ b/examples/sdk-deadlines/Main/MainReactor.hh @@ -0,0 +1,34 @@ +#pragma once + +#include <reactor-sdk/reactor-sdk.hh> + +#include "Node/NodeReactor.hh" + +using namespace sdk; + +class MainReactor: public Reactor { +public: + struct Parameters : public SystemParametersStandalone<int> { + REACTOR_PARAMETER(int, n_fast, "Number of fast nodes", 1, 10, 2); + + Parameters(Reactor *container) + : SystemParametersStandalone<int>(container) { + register_parameters (n_fast); + } + }; +private: + Parameters parameters{this}; + std::unique_ptr<NodeReactor> slow; + ReactorBank<NodeReactor> fast{"fast", this}; + +public: + MainReactor(const std::string &name, Environment *env) + : Reactor(name, env) {} + MainReactor(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() override; + void wiring() override; +}; + + diff --git a/examples/sdk-deadlines/Node/NodeReactor.cc b/examples/sdk-deadlines/Node/NodeReactor.cc new file mode 100644 index 00000000..d23d8bab --- /dev/null +++ b/examples/sdk-deadlines/Node/NodeReactor.cc @@ -0,0 +1,29 @@ +#include "NodeReactor.hh" + +void NodeReactor::construction() { + std::cout << "Construction:" << fqn() << " period:" << parameters.period.value << " duration:" << parameters.duration.value << "\n"; +} + +void NodeReactor::wiring() { + std::cout << "Assembling Node\n"; +} + +void NodeReactor::Internals::add_reactions(NodeReactor *reactor) { + reaction("reaction_1"). + triggers(&reactor->startup, &reactor->a). + dependencies(). + effects(). + function( + [this](Startup& startup, LogicalAction<void> &a) { + reactor::log::Info() << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << fqn() << " reaction executes."; + std::this_thread::sleep_for(parameters.duration.value); + reactor::log::Info() << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << fqn() << " reaction done."; + a.schedule(parameters.period.value); + } + ).deadline (parameters.period.value, + [this](Startup& startup, LogicalAction<void> &a) { + reactor::log::Error() << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << fqn() << " deadline was violated!"; + exit(1); + } + ); +} \ No newline at end of file diff --git a/examples/sdk-deadlines/Node/NodeReactor.cmake b/examples/sdk-deadlines/Node/NodeReactor.cmake new file mode 100644 index 00000000..7468f97c --- /dev/null +++ b/examples/sdk-deadlines/Node/NodeReactor.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +set(INCLUDE_FILES + "${CMAKE_CURRENT_LIST_DIR}/NodeReactor.hh" +) + +set(SOURCE_FILES + "${CMAKE_CURRENT_LIST_DIR}/NodeReactor.cc" +) + + +foreach(file IN LISTS INCLUDE_FILES) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include ${file}") +endforeach() + +target_sources(${LF_MAIN_TARGET} PRIVATE ${SOURCE_FILES}) \ No newline at end of file diff --git a/examples/sdk-deadlines/Node/NodeReactor.hh b/examples/sdk-deadlines/Node/NodeReactor.hh new file mode 100644 index 00000000..8b5b017f --- /dev/null +++ b/examples/sdk-deadlines/Node/NodeReactor.hh @@ -0,0 +1,38 @@ +#pragma once + +#include <reactor-sdk/reactor-sdk.hh> + +using namespace sdk; + +class NodeReactor: public Reactor { +public: + struct Parameters : public SystemParametersStandalone<Duration> { + REACTOR_PARAMETER(Duration, period, "Schedule and deadline period", 10ms, 10s, 500ms); + REACTOR_PARAMETER(Duration, duration, "Sleep duration", 5ms, 5s, 10ms); + + Parameters(Reactor *container) + : SystemParametersStandalone<Duration>(container) { + register_parameters (period, duration); + } + }; + +private: + Parameters parameters{this}; + LogicalAction<void> a{"a", this}; + + REACTION_SCOPE_START(NodeReactor, Parameters) + void add_reactions(NodeReactor *reactor); + REACTION_SCOPE_END(this, parameters) + + +public: + NodeReactor(const std::string &name, Environment *env) + : Reactor(name, env) {} + NodeReactor(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() override; + void wiring() override; +}; + + diff --git a/examples/sdk-deadlines/main.cc b/examples/sdk-deadlines/main.cc new file mode 100644 index 00000000..cb3d106b --- /dev/null +++ b/examples/sdk-deadlines/main.cc @@ -0,0 +1,51 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +#include "Config-a/Config-a.hh" +#include "Main/MainReactor.hh" + +using namespace std; +using namespace sdk; + +int main(int argc, char **argv) { + cxxopts::Options options("sdk-deadlines", "Multiport source connecting to banked sink reactors"); + + unsigned workers = std::thread::hardware_concurrency(); + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + bool cfg_gen{false}; + + // the timeout variable needs to be tested beyond fitting the Duration-type + options + .set_width(120) + .add_options() + ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value<unsigned>(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value<reactor::Duration>(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value<bool>(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value<bool>(cfg_gen)->default_value("false")) + ("help", "Print help"); + + cxxopts::ParseResult result{}; + bool parse_error{false}; + try { + result = options.parse(argc, argv); + } catch (const cxxopts::OptionException& e) { + reactor::log::Error() << e.what(); + parse_error = true; + } + + // if parameter --help was used or there was a parse error, print help + if (parse_error || result.count("help")) + { + std::cout << options.help({""}); + return parse_error ? -1 : 0; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " cfg_gen:" << (cfg_gen ? "True" : "False") << std::endl; + + Environment sim {&cfg_parameters, workers, fast, timeout, cfg_gen}; + auto main = new MainReactor("Main", &sim); + + sim.run(); + return 0; +} diff --git a/examples/sdk-hello/CMakeLists.txt b/examples/sdk-hello/CMakeLists.txt new file mode 100644 index 00000000..77875f90 --- /dev/null +++ b/examples/sdk-hello/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(hello VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET hello) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/examples/sdk-hello/main.cc b/examples/sdk-hello/main.cc new file mode 100644 index 00000000..6c2c904b --- /dev/null +++ b/examples/sdk-hello/main.cc @@ -0,0 +1,65 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +using namespace sdk; + +class Hello : public Reactor { +private: + struct Parameters { + }; + Parameters parameters; + + Timer timer{"timer", this}; + + class Chamber : public ReactionChamber<Hello, Parameters> { + public: + Chamber(Reactor *reactor, Parameters ¶ms) + : ReactionChamber<Hello, Parameters>(reactor, params) {} + private: + + void terminate(Shutdown& shutdown) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Good Bye!\n"; + } + + void add_reactions (Hello *reactor) { + reaction("reaction_1"). + triggers(&reactor->timer). + dependencies(). + effects(). + function( + [this](Timer& timer) { + std::cout << "(" << get_elapsed_logical_time().count() << ", " << get_microstep() << ") physical_time:" << get_elapsed_physical_time().count() + << fqn() << " Bank:" << bank_index << " Hello World!\n"; + } + ); + + reaction("reaction_2"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function(pass_function(terminate)); + } + }; + Chamber reaction_chamber{this, parameters}; + +public: + Hello(const std::string &name, Environment *env) + : Reactor(name, env) {} + + void construction() { + timer.set_timer (1s, 2s); + } + + void wiring() override { + + } +}; + +int main(int argc, char **argv) { + Environment env {nullptr, 4, false, 4s, false}; + auto main = new Hello("Hello", &env); + + env.run(); + return 0; +} diff --git a/examples/sdk-hello/src/HelloWorld.lf b/examples/sdk-hello/src/HelloWorld.lf new file mode 100644 index 00000000..f55ccc56 --- /dev/null +++ b/examples/sdk-hello/src/HelloWorld.lf @@ -0,0 +1,19 @@ +target Cpp { + timeout: 4s +} + +reactor Hello (bank_index: size_t = 0) { + timer Timer(1s, 2s) + + reaction(Timer) {= + std::cout << "Bank:" << bank_index << " Hello World!\n"; + =} + + reaction(shutdown) {= + std::cout << "Good Bye!\n"; + =} +} + +main reactor { + hello = new Hello(); +} \ No newline at end of file diff --git a/include/reactor-cpp/action.hh b/include/reactor-cpp/action.hh index bb43c8c8..7f7be9d1 100644 --- a/include/reactor-cpp/action.hh +++ b/include/reactor-cpp/action.hh @@ -152,7 +152,7 @@ public: }; class Timer : public BaseAction { -private: +protected: Duration offset_{0}; Duration period_{0}; diff --git a/include/reactor-cpp/environment.hh b/include/reactor-cpp/environment.hh index 825b3bba..59234d3d 100644 --- a/include/reactor-cpp/environment.hh +++ b/include/reactor-cpp/environment.hh @@ -30,10 +30,11 @@ constexpr bool default_fast_fwd_execution = false; enum class Phase : std::uint8_t { Construction = 0, Assembly = 1, - Startup = 2, - Execution = 3, - Shutdown = 4, - Deconstruction = 5 + Indexing = 2, + Startup = 3, + Execution = 4, + Shutdown = 5, + Deconstruction = 6 }; class Environment { @@ -107,7 +108,9 @@ public: void register_reactor(Reactor* reactor); void register_port(BasePort* port) noexcept; void register_input_action(BaseAction* action); + void construct(); void assemble(); + void dependency_graph_and_indexes(); auto startup() -> std::thread; void sync_shutdown(); void async_shutdown(); diff --git a/include/reactor-cpp/reactor.hh b/include/reactor-cpp/reactor.hh index 02b698dd..a93cedb0 100644 --- a/include/reactor-cpp/reactor.hh +++ b/include/reactor-cpp/reactor.hh @@ -52,6 +52,7 @@ public: void shutdown() final; virtual void assemble() = 0; + virtual void construct() {} [[nodiscard]] static auto get_physical_time() noexcept -> TimePoint; [[nodiscard]] auto get_logical_time() const noexcept -> TimePoint; diff --git a/include/reactor-sdk/ConfigParameters.hh b/include/reactor-sdk/ConfigParameters.hh new file mode 100644 index 00000000..84eaccaf --- /dev/null +++ b/include/reactor-sdk/ConfigParameters.hh @@ -0,0 +1,135 @@ +#pragma once + +#include <map> +#include <string> +#include <variant> + +namespace sdk +{ + +template <typename T, typename = void> +struct is_comparable : std::false_type {}; + +template <typename T> +struct is_comparable< + T, + std::void_t<decltype(std::declval<T>() < std::declval<T>())> +> : std::true_type {}; + +extern std::map<std::string, std::string> type_convert; +template <typename T> +struct ParameterMetadata; + +class ConfigParameterBase { +protected: + virtual void pull_config() = 0; + virtual void display() = 0; + virtual int validate() = 0; + +public: + virtual ~ConfigParameterBase() = default; + virtual int pull_config_parameter(const bool &is_heterogeneous, const std::string &key, void *user_param, const std::type_info& ti) = 0; + + template<typename T> + int PullConfigParameter(const bool &is_heterogeneous, const std::string &key, ParameterMetadata<T>* user_param) { + return pull_config_parameter(is_heterogeneous, key, static_cast<void*>(user_param), typeid(T)); + } + friend class Environment; +}; + +template <typename T> +struct ConfigParameterMetadata { + std::vector<T> values; + + ConfigParameterMetadata(std::initializer_list<T> val) : values(val) {} +}; + +template <typename... ParameterValueType> +class ConfigParameter : public ConfigParameterBase { +public: + using ParameterValue = std::variant<ConfigParameterMetadata<ParameterValueType>...>; + using ParametersMap = std::map<std::string, ParameterValue>; + + virtual ParametersMap homogeneous_config() = 0; + virtual ParametersMap heterogeneous_config() = 0; + int pull_config_parameter(const bool &is_heterogeneous, const std::string &key, void *user_param, const std::type_info& ti) override { + std::map<std::string, ParameterValue> *param_map = is_heterogeneous ? &hetero_param_map : &homoge_param_map; + std::set<std::string> *invalid_keys = is_heterogeneous ? &hetero_invalid_keys : &homoge_invalid_keys; + auto itr_system = param_map->find(key); + if (itr_system != param_map->end()) { + auto v_it = invalid_keys->find(key); + if (v_it != invalid_keys->end()) { + invalid_keys->erase(v_it); + } + std::visit([is_heterogeneous, user_param, &ti, key](auto&& system_param) { + using ContainerType = std::decay_t<decltype(system_param.values)>; + using U = typename ContainerType::value_type; + + if (ti == typeid(U)) { + ParameterMetadata<U>* param = static_cast<ParameterMetadata<U>*>(user_param); + if constexpr (is_comparable<U>::value && !std::is_same<U, std::string>::value) { + if ((system_param.values[0] < param->min_value) || + (system_param.values[0] > param->max_value)) { + reactor::log::Error() << "Error: " << ((is_heterogeneous) ? "Heterogeneous Map" : "Homogeneous Map") << " -- Range mismatch for parameter name: " << key << " value:" << system_param.values[0] << + " min_value:" << param->min_value << " max_value:" << param->max_value; + std::exit(EXIT_FAILURE); + } + } + param->value = system_param.values[0]; + + } else { + reactor::log::Error() << "Error: Type mismatch for parameter name: " << key << "\n" + << "Expected type: " << type_convert[ti.name()] + << ", Provided type: " << type_convert[typeid(U).name()]; + std::exit(EXIT_FAILURE); + } + }, itr_system->second); + return 0; + } + return -1; + } + +protected: + std::map<std::string, ParameterValue> homoge_param_map; + std::set<std::string> homoge_invalid_keys; + std::map<std::string, ParameterValue> hetero_param_map; + std::set<std::string> hetero_invalid_keys; + void pull_config() override { + homoge_param_map = homogeneous_config(); + for (const auto& entry : homoge_param_map) { + bool result = homoge_invalid_keys.insert(entry.first).second; + assert(result); + } + + hetero_param_map = heterogeneous_config(); + for (const auto& entry : hetero_param_map) { + bool result = hetero_invalid_keys.insert(entry.first).second; + assert(result); + } + } + + int validate() override { + for (const auto &key : hetero_invalid_keys) { + reactor::log::Error() << "Heterogeneous Invalid key:" << key << "\n"; + } + + for (const auto &key : homoge_invalid_keys) { + reactor::log::Error() << "Homogeneous Invalid key:" << key << "\n"; + } + return (hetero_invalid_keys.size() + homoge_invalid_keys.size()); + } + + void display() override { + for (const auto& entry : hetero_param_map) { + reactor::log::Debug() << "Parameter: " << entry.first; + + std::visit([](auto&& param) { + for (auto val : param.values) { + reactor::log::Debug() << "Value: " << val; + } + }, entry.second); + } + } +}; + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/Environment.hh b/include/reactor-sdk/Environment.hh new file mode 100644 index 00000000..2653766b --- /dev/null +++ b/include/reactor-sdk/Environment.hh @@ -0,0 +1,34 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" +#include "ConfigParameters.hh" + +namespace sdk +{ + +class Reactor; +class Environment: public reactor::Environment { +private: + std::set<Reactor*> top_tier_reactors; + ConfigParameterBase *config_parameters; + bool cfg_gen = false; + +public: + Environment(ConfigParameterBase *sys_param = nullptr, unsigned int num_workers = 1, bool fast_fwd_execution = true, + const reactor::Duration& timeout = reactor::Duration::max(), bool cfg_gen = false); + + Environment(const Environment&) = delete; + Environment& operator=(const Environment&) = delete; + void run(); + + void add_reactor (Reactor* reactor) { + bool result = top_tier_reactors.insert(reactor).second; + reactor_assert(result); + } + + ConfigParameterBase *get_config_params() { return config_parameters; } + + friend class SystemParameterBase; +}; + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/InputPort.hh b/include/reactor-sdk/InputPort.hh new file mode 100644 index 00000000..e7891799 --- /dev/null +++ b/include/reactor-sdk/InputPort.hh @@ -0,0 +1,65 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" + +namespace sdk +{ + +template <typename T> +class Input : public reactor::Input<T> { + class WiringProxy { + public: + WiringProxy(Input& origin) : origin(origin) {} + + void operator>(Input<T>& input) { + origin.connect (input); + } + + void operator>(MultiportInput<T>& input) { + origin.connect (input); + } + + void operator>>(MultiportInput<T>& input) { + origin.connect_fanout (input); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>>(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect_fanout(std::move(other_bank_ports)); + } + + private: + Input& origin; + }; + + void connect(Input<T>& input); + void connect(MultiportInput<T>& input); + void connect_fanout(MultiportInput<T>& input); + + template <typename ReactorType> + void connect(ReactorBankInputPortOffset<ReactorType, T> &&other_bank_ports); + + template <typename ReactorType> + void connect_fanout(ReactorBankInputPortOffset<ReactorType, T> &&other_bank_ports); + +public: + using value_type = T; + Input(const std::string& name, reactor::Reactor* container) + : reactor::Input<T>(name, container) {} + + Input(Input&&) noexcept = default; + ~Input() {} + + WiringProxy operator--(int) { + return WiringProxy(*this); + } +}; + +} // namespace sdk + +#include "impl/InputPort_wiring_impl.hh" \ No newline at end of file diff --git a/include/reactor-sdk/Misc.hh b/include/reactor-sdk/Misc.hh new file mode 100644 index 00000000..55289d1a --- /dev/null +++ b/include/reactor-sdk/Misc.hh @@ -0,0 +1,108 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" +#include <iomanip> + +namespace sdk +{ + +class Reactor; + +template<typename T> +using LogicalAction = reactor::LogicalAction<T>; + +using Startup = reactor::StartupTrigger; +using Shutdown = reactor::ShutdownTrigger; + +using Duration = reactor::Duration; +using TimePoint = reactor::TimePoint; + +#define select_default(obj) &obj[0] + +template <typename T> +struct inspect_function_args; + +template <typename Ret, typename Class, typename... Args> +struct inspect_function_args<Ret(Class::*)(Args...)> { + static constexpr size_t nargs = sizeof...(Args); +}; + +template <typename Func, typename Object> +auto bind_function(Object* obj, Func&& func) { + constexpr size_t nargs = inspect_function_args<Func>::nargs; + + if constexpr (nargs == 0) { + static_assert(nargs > 0, "Reactors must have one or more parameters"); + return nullptr; + } else if constexpr (nargs == 1) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1); + } else if constexpr (nargs == 2) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2); + } else if constexpr (nargs == 3) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + } else if constexpr (nargs == 4) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); + } else if constexpr (nargs == 5) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5); + } else if constexpr (nargs == 6) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6); + } else if constexpr (nargs == 7) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7); + } else if constexpr (nargs == 8) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8); + } else if constexpr (nargs == 9) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8, std::placeholders::_9); + } else if constexpr (nargs == 10) { + return std::bind(std::forward<Func>(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8, std::placeholders::_9, std::placeholders::_10); + } else { + static_assert(nargs <= 10, "This needs to be extended as per requirement of more parameters"); + return nullptr; + } + +} + +#define pass_function(func) \ + bind_function(this, &std::decay_t<decltype(*this)>::func) + +class Timer : public reactor::Timer { + std::string name; + Reactor *reactor; +public: + Timer(const std::string& name, Reactor* container) + : reactor::Timer(name, (reactor::Reactor *) container), name (name), reactor (container){} + + void set_timer (Duration period = Duration::zero(), Duration offset = Duration::zero()) { + period_ = period; + offset_ = offset; + } + + Timer(Timer&&) noexcept = default; +}; + +inline auto operator<<(std::ostream& os, Duration dur) -> std::ostream& { + os << dur.count() << " nsecs"; + return os; +} + +constexpr std::size_t TIME_TO_STR_BUFFER_SIZE_{20}; +constexpr std::size_t NANOSECONDS_IN_ONE_SECOND_{1'000'000'000UL}; +constexpr std::size_t NANOSECOND_DIGITS_{9}; + +inline auto operator<<(std::ostream& os, TimePoint tp) -> std::ostream& { + std::array<char, TIME_TO_STR_BUFFER_SIZE_> buf{}; + time_t time = + std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(tp)); + auto res = std::strftime(buf.data(), sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&time)); + auto epoch = std::chrono::duration_cast<Duration>(tp.time_since_epoch()); + + if (res != 0) { + os << buf.data() << '.' << std::setw(NANOSECOND_DIGITS_) << std::setfill('0') + << epoch.count() % NANOSECONDS_IN_ONE_SECOND_; + } else { + os << "[INVALID TIME]"; + } + + return os; +} + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/MultiportInput.hh b/include/reactor-sdk/MultiportInput.hh new file mode 100644 index 00000000..3af18c49 --- /dev/null +++ b/include/reactor-sdk/MultiportInput.hh @@ -0,0 +1,65 @@ +#pragma once + +#include <string> +#include "reactor-cpp/reactor-cpp.hh" + +namespace sdk +{ + +template<typename T> +class Input; + +class Reactor; + +template<typename T> +class MultiportInput : public reactor::ModifableMultiport<Input<T>> { + size_t n_inputs; + std::string name; + Reactor *reactor; + + class WiringProxy { + public: + WiringProxy(MultiportInput& origin) : origin(origin) {} + + void operator>(Input<T>& input) { + origin.connect (input); + } + + void operator>(MultiportInput<T>& input) { + origin.connect (input); + } + + private: + MultiportInput& origin; + }; + + void connect(Input<T>& input); + void connect(MultiportInput<T>& input); + +public: + using value_type = T; + MultiportInput(const std::string& name, Reactor* container) + : name (name), reactor (container) {} + + void set_width (int width) + { + this->reserve(width); + n_inputs = width; + for (int idx = 0; idx < width; idx++) { + std::string input_name = name + "_" + std::to_string(idx); + this->emplace_back(input_name, reactor); + } + } + + MultiportInput(MultiportInput&&) noexcept = default; + auto get_nports() -> int { return n_inputs; } + + WiringProxy operator--(int) { + return WiringProxy(*this); + } +}; + + +} // namespace sdk + +#include "impl/InputMultiport_wiring_impl.hh" \ No newline at end of file diff --git a/include/reactor-sdk/MultiportOutput.hh b/include/reactor-sdk/MultiportOutput.hh new file mode 100644 index 00000000..6d076fcf --- /dev/null +++ b/include/reactor-sdk/MultiportOutput.hh @@ -0,0 +1,108 @@ +#pragma once + +#include <string> +#include "reactor-cpp/reactor-cpp.hh" + +namespace sdk +{ + +template<typename T> +class Input; + +template<typename T> +class Output; + +template<typename T> +class MultiportOutput; + +template<typename T> +class MultiportInput; + +class Reactor; + +template<typename T> +class MultiportOutput : public reactor::ModifableMultiport<Output<T>> { + size_t n_inputs; + std::string name; + Reactor *reactor; + class WiringProxy { + public: + WiringProxy(MultiportOutput& origin) : origin(origin) {} + + void operator>(Input<T>& input) { + origin.connect (input); + } + + void operator>(Output<T>& input) { + origin.connect (input); + } + + void operator>(MultiportInput<T>& input) { + origin.connect (input); + } + + void operator>(MultiportOutput<T>& input) { + origin.connect (input); + } + + template <typename ReactorType> + void operator>(std::pair<std::vector<std::unique_ptr<ReactorType>>*, Input<T> ReactorType::*> connections) + { + origin.connect (connections.first, connections.second); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + private: + MultiportOutput& origin; + }; + + void connect(Input<T>& input); + void connect(Output<T>& input); + void connect(MultiportInput<T>& input); + void connect(MultiportOutput<T>& input); + + template <typename ReactorType> + void connect(std::vector<std::unique_ptr<ReactorType>>* reactors, Input<T> ReactorType::*member); + + template <typename OtherReactorType> + void connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports); + +public: + using value_type = T; + MultiportOutput(const std::string& name, Reactor* container) + : name (name), reactor (container) {} + + void set_width (int width) + { + this->reserve(width); + n_inputs = width; + for (int idx = 0; idx < width; idx++) { + std::string input_name = name + "_" + std::to_string(idx); + this->emplace_back(input_name, reactor); + } + } + + MultiportOutput(MultiportOutput&&) noexcept = default; + auto get_nports() -> int { return n_inputs; } + + WiringProxy operator--(int) { + return WiringProxy(*this); + } +}; + + +} // namespace sdk + +#include "impl/OutputMultiport_wiring_impl.hh" \ No newline at end of file diff --git a/include/reactor-sdk/OutputPort.hh b/include/reactor-sdk/OutputPort.hh new file mode 100644 index 00000000..40e01b55 --- /dev/null +++ b/include/reactor-sdk/OutputPort.hh @@ -0,0 +1,113 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" + +namespace sdk +{ + +template<typename T> +class Input; + +template<typename T> +class Output; + +template<typename T> +class MultiportOutput; + +template<typename T> +class MultiportInput; + +class Reactor; + +template <typename T> +class Output : public reactor::Output<T> { + std::set<Output<T>*> accumulated; + bool is_accumulated = false; + + class WiringProxy { + public: + WiringProxy(Output& origin) : origin(origin) {} + + void operator>(Input<T>& input) { + origin.connect (input); + } + + void operator>(Output<T>& input) { + origin.connect (input); + } + + void operator>(MultiportInput<T>& input) { + origin.connect (input); + } + + void operator>>(MultiportInput<T>& input) { + origin.connect_fanout (input); + } + + template <typename ReactorType> + void operator>(std::pair<std::vector<std::unique_ptr<ReactorType>>*, Input<T> ReactorType::*> connections) + { + origin.connect (connections.first, connections.second); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>>(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect_fanout(std::move(other_bank_ports)); + } + + private: + Output& origin; + }; + + void connect(Input<T>& input); + void connect(Output<T>& input); + void connect(MultiportInput<T>& input); + void connect_fanout(MultiportInput<T>& input); + + template <typename ReactorType> + void connect(std::vector<std::unique_ptr<ReactorType>>* reactors, Input<T> ReactorType::*member); + + template <typename ReactorType> + void connect(ReactorBankInputPort<ReactorType, T> &&other_bank_ports); + + template <typename ReactorType> + void connect(ReactorBankInputPortOffset<ReactorType, T> &&other_bank_ports); + + template <typename ReactorType> + void connect_fanout(ReactorBankInputPortOffset<ReactorType, T> &&other_bank_ports); + +public: + using value_type = T; + Output(const std::string& name, reactor::Reactor* container) + : reactor::Output<T>(name, container) {} + + ~Output() {} + + Output(Output&&) noexcept = default; + + WiringProxy operator--(int) { + return WiringProxy(*this); + } + + Output<T>& operator+(Output<T> &output) { + [[maybe_unused]] bool result = accumulated.insert(&output).second; + reactor_assert(result); + is_accumulated = true; + return *this; + } +}; + + +} // namespace sdk + +#include "impl/OutputPort_wiring_impl.hh" \ No newline at end of file diff --git a/include/reactor-sdk/Reaction.hh b/include/reactor-sdk/Reaction.hh new file mode 100644 index 00000000..54da081e --- /dev/null +++ b/include/reactor-sdk/Reaction.hh @@ -0,0 +1,365 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" +#include "ReactionBase.hh" +#include "Reactor.hh" + +namespace sdk +{ + +template <typename Fn, typename InputTuple, typename DependencyTuple, typename OutputTuple> +class Reaction; + +template <typename T, template <typename...> class Template> +struct is_specialization : std::false_type +{ +}; + +template <typename... Args, template <typename...> class Template> +struct is_specialization<Template<Args...>, Template> : std::true_type +{ +}; + +template <typename T, template <typename...> class Template> +inline constexpr bool is_specialization_v = is_specialization<T, Template>::value; + +// fix for gcc < 13 +template <typename T> +constexpr bool templated_false = false; + +template <typename InputTuple, typename DependencyTuple, typename OutputTuple> +class ReactionOutput: public ReactionBase +{ +private: + InputTuple input_triggers; + DependencyTuple dependencies; + OutputTuple output_triggers; + +public: + explicit ReactionOutput(std::string name, Reactor *parent, InputTuple inputs, DependencyTuple deps, OutputTuple outputs) + : ReactionBase (name, parent), input_triggers(std::move(inputs)), dependencies(std::move(deps)), output_triggers(std::move(outputs)) {} + ~ReactionOutput() {} + + template <typename Fn> + Reaction<Fn, InputTuple, DependencyTuple, OutputTuple> &function(Fn func) + { + if constexpr (std::is_bind_expression<Fn>::value) { + } + else if (sizeof(func) != sizeof(void*)) { + reactor::log::Error() << "Reactor: " << reactor->fqn() << " Reaction: " << name << " Accesses variables outside of its scope"; + exit(EXIT_FAILURE); + } + + auto ReactionRef = std::make_shared<Reaction<Fn, InputTuple, DependencyTuple, OutputTuple>> (name, reactor, std::move(input_triggers), std::move(dependencies), std::move(output_triggers), std::forward<Fn>(func)); + ReactionRef->execute(); + return *ReactionRef; + } +}; + +template <typename InputTuple, typename DependencyTuple> +class ReactionDependency: public ReactionBase +{ +private: + InputTuple input_triggers; + DependencyTuple dependencies; + +public: + explicit ReactionDependency(std::string name, Reactor *parent, InputTuple inputs, DependencyTuple deps) + : ReactionBase (name, parent), input_triggers(inputs), dependencies(std::move(deps)) {} + ~ReactionDependency() {} + + template <typename... Outputs> + ReactionOutput<InputTuple, DependencyTuple, std::tuple<Outputs...>> &effects(Outputs&&... outputs) + { + auto output_tuple = std::make_tuple(outputs...); + auto ReactionOutputRef = std::make_shared<ReactionOutput<InputTuple, DependencyTuple, std::tuple<Outputs...>>> (name, reactor, std::move(input_triggers), std::move(dependencies), std::move(output_tuple)); + next = ReactionOutputRef; + return *ReactionOutputRef; + } +}; + +template <typename InputTuple> +class ReactionInput: public ReactionBase +{ +private: + InputTuple input_triggers; + +public: + explicit ReactionInput(std::string name, Reactor *parent, InputTuple inputs) + : ReactionBase (name, parent), input_triggers(std::move(inputs)) {} + ~ReactionInput() {} + + template <typename... Dependencies> + ReactionDependency<InputTuple, std::tuple<Dependencies...>> &dependencies(Dependencies&&... deps) + { + auto deps_tuple = std::make_tuple(deps...); + auto ReactionDependenciesRef = std::make_shared<ReactionDependency<InputTuple, std::tuple<Dependencies...>>> (name, reactor, std::move(input_triggers), std::move(deps_tuple)); + next = ReactionDependenciesRef; + return *ReactionDependenciesRef; + } +}; + +class ReactionName: public ReactionBase { +public: + explicit ReactionName(std::string name, Reactor *parent) + : ReactionBase (name, parent) {} + ~ReactionName() = default; + + template <typename... Inputs> + ReactionInput<std::tuple<Inputs...>> &triggers(Inputs&&... inputs) + { + auto input_tuple = std::make_tuple(inputs...); + auto ReactionInputRef = std::make_shared<ReactionInput<std::tuple<Inputs...>>> (name, reactor, std::move(input_tuple)); + next = ReactionInputRef; + return *ReactionInputRef; + } +}; + +template <typename Fn, typename InputTuple, typename DependencyTuple, typename OutputTuple> +class Reaction: public ReactionBase +{ +private: + InputTuple input_triggers; + DependencyTuple dependencies; + OutputTuple output_triggers; + Fn user_function; + std::unique_ptr<reactor::Reaction> reaction; + + template <typename Reaction, typename Trigger> + void set_input_trigger(Reaction &reaction, Trigger &&trigger) + { + if constexpr (is_specialization_v<std::remove_pointer_t<std::decay_t<Trigger>>, MultiportInput>) + { + for (auto& port : *trigger) { + reaction.declare_trigger(&port); + } + } + else if constexpr (is_specialization_v<std::remove_pointer_t<std::decay_t<Trigger>>, MultiportOutput>) + { + for (auto& port : *trigger) { + reaction.declare_trigger(&port); + } + } + else { + reaction.declare_trigger(trigger); + } + } + + template <typename Reaction, typename... Triggers> + void set_input_triggers(std::unique_ptr<Reaction> &reaction, const std::tuple<Triggers...> &inputs) + { + std::apply([this, &reaction](auto &&...input) + { + (void)this; + (..., set_input_trigger(*reaction, std::forward<decltype(input)>(input))); + }, + inputs); + } + + template <typename Reaction, typename Trigger> + void set_dependency(Reaction &reaction, Trigger &&trigger) + { + if constexpr (is_specialization_v<std::remove_pointer_t<std::decay_t<Trigger>>, MultiportInput>) + { + for (auto& port : *trigger) { + reaction.declare_dependency(&port); + } + } + else { + reaction.declare_dependency(trigger); + } + } + + template <typename Reaction, typename... Dependencies> + void set_dependencies(std::unique_ptr<Reaction> &reaction, const std::tuple<Dependencies...> &deps) + { + std::apply([this, &reaction](auto &&...dep) + { + (void)this; + (..., set_dependency(*reaction, std::forward<decltype(dep)>(dep))); + }, + deps); + } + + template <typename Reaction, typename Trigger> + void set_output_trigger(Reaction &reaction, Trigger &&trigger) + { + if constexpr (is_specialization_v<std::remove_pointer_t<std::decay_t<Trigger>>, Output>) + { + reaction.declare_antidependency(trigger); + } else if constexpr (is_specialization_v<std::remove_pointer_t<std::decay_t<Trigger>>, Input>) + { + reaction.declare_antidependency(trigger); + } + else if constexpr (is_specialization_v<std::remove_pointer_t<std::decay_t<Trigger>>, reactor::LogicalAction>) + { + reaction.declare_schedulable_action(trigger); + } + else if constexpr (is_specialization_v<std::remove_pointer_t<std::decay_t<Trigger>>, MultiportOutput>) + { + for (auto& port : *trigger) { + reaction.declare_antidependency(&port); + } + } + else + { + static_assert(templated_false<Trigger>, "Unsupported trigger type"); + } + } + + template <typename Reaction, typename... Triggers> + void set_output_triggers(std::unique_ptr<Reaction> &reaction, const std::tuple<Triggers...> &outputs) + { + std::apply([this, &reaction](auto &&...output) + { + (void)this; + (..., set_output_trigger(*reaction, std::forward<decltype(output)>(output))); + }, + outputs); + } + +public: + Reaction(std::string name, Reactor *parent, InputTuple inputs, DependencyTuple deps, OutputTuple outputs, Fn func) + : ReactionBase(name, parent), input_triggers(std::move(inputs)), dependencies(std::move(deps)), output_triggers(std::move(outputs)), user_function(std::forward<Fn>(func)) { /* std::cout << "Creating Reaction\n"; */ } + ~Reaction() {} + + void execute () { + int priority = reactor->get_priority(); + reactor->add_to_reaction_map(name, shared_from_this()); + reactor->validate_reaction (user_function, input_triggers, dependencies, output_triggers); + + auto reactor_func = [func = std::move(user_function), this]() + { + (void)this; + auto apply_to_dereferenced = [](auto&& func, auto&& tuple) { + return std::apply( + [&](auto*... ptrs) { + return std::invoke(std::forward<decltype(func)>(func), (*ptrs)...); + }, + std::forward<decltype(tuple)>(tuple)); + }; + + apply_to_dereferenced(func, std::tuple_cat(this->input_triggers, this->dependencies, this->output_triggers)); + }; + + reaction = std::make_unique<reactor::Reaction>(name, priority, reactor, reactor_func); + + set_input_triggers(reaction, input_triggers); + set_dependencies(reaction, dependencies); + set_output_triggers(reaction, output_triggers); + } + + template <typename Dfn> + void deadline(reactor::Duration deadline_period, Dfn fn) + { + reactor->validate_reaction (fn, input_triggers, dependencies, output_triggers); + + auto deadline_func = [func = std::move(fn), this]() + { + (void)this; + auto apply_to_dereferenced = [](auto&& func, auto&& tuple) { + return std::apply( + [&](auto*... ptrs) { + return std::invoke(std::forward<decltype(func)>(func), (*ptrs)...); + }, + std::forward<decltype(tuple)>(tuple)); + }; + + apply_to_dereferenced(func, std::tuple_cat(this->input_triggers, this->dependencies, this->output_triggers)); + }; + + reaction->set_deadline(deadline_period, deadline_func); + } +}; + +template <typename ReactorType> +class ReactionChamberParameterless : public ReactionBase { + ReactorType *reactor_; +protected: + const size_t &bank_index = reactor_->bank_index; +public: + ReactionChamberParameterless(Reactor *owner) + : ReactionBase("reaction-internals-parameterless", owner), reactor_((ReactorType*) owner) { + reactor_->add_reaction_internals(this); + } + + ReactionName &reaction (const std::string name) { + auto ReactionNameRef = std::make_shared<ReactionName>(name, reactor); + next = ReactionNameRef; + return *ReactionNameRef; + } + + virtual void add_reactions(ReactorType *reactor) = 0; + virtual void assemble() override { + add_reactions(reactor_); + } + + auto fqn() const noexcept -> const std::string& { return reactor_->fqn(); } + auto get_elapsed_logical_time() const noexcept -> Duration { return reactor_->get_elapsed_logical_time(); } + auto get_microstep() const noexcept -> reactor::mstep_t { return reactor_->get_microstep(); } + auto get_elapsed_physical_time() const noexcept -> Duration { return reactor_->get_elapsed_physical_time(); } + auto get_physical_time() noexcept -> reactor::TimePoint { return reactor_->get_physical_time(); } + auto get_logical_time() const noexcept -> reactor::TimePoint { return reactor_->get_logical_time(); } + auto get_tag() const noexcept -> reactor::Tag { return reactor_->get_tag(); } + void request_stop() { reactor_->environment()->sync_shutdown(); } +}; + +template <typename ReactorType, typename ParameterType> +class ReactionChamber : public ReactionBase { + ReactorType *reactor_; + +protected: + const ParameterType ¶meters; + const size_t &bank_index = reactor_->bank_index; +public: + ReactionChamber(Reactor *owner, ParameterType ¶m) + : ReactionBase("reaction-internals", owner), reactor_((ReactorType*) owner), parameters(param) { + reactor_->add_reaction_internals(this); + } + + ReactionName &reaction (const std::string name) { + auto ReactionNameRef = std::make_shared<ReactionName>(name, reactor); + next = ReactionNameRef; + return *ReactionNameRef; + } + + virtual void add_reactions(ReactorType *reactor) = 0; + virtual void assemble() override { + add_reactions(reactor_); + } + + auto fqn() const noexcept -> const std::string& { return reactor_->fqn(); } + auto get_elapsed_logical_time() const noexcept -> Duration { return reactor_->get_elapsed_logical_time(); } + auto get_microstep() const noexcept -> reactor::mstep_t { return reactor_->get_microstep(); } + auto get_elapsed_physical_time() const noexcept -> Duration { return reactor_->get_elapsed_physical_time(); } + auto get_physical_time() noexcept -> reactor::TimePoint { return reactor_->get_physical_time(); } + auto get_logical_time() const noexcept -> reactor::TimePoint { return reactor_->get_logical_time(); } + auto get_tag() const noexcept -> reactor::Tag { return reactor_->get_tag(); } + void request_stop() { reactor_->environment()->sync_shutdown(); } +}; + +#define REACTION_SCOPE_START(ReactorType, ParamType) \ +class Internals : public ReactionChamber<ReactorType, ParamType> { \ +public: \ + Internals(Reactor *reactor, ParamType ¶ms) \ + : ReactionChamber<ReactorType, ParamType>(reactor, params) {} \ +private: + +#define REACTION_SCOPE_END(reactor, param) \ +}; \ +Internals reaction_internals{reactor, param}; + +#define REACTION_SCOPE_START_NO_PARAMS(ReactorType) \ +class Internals : public ReactionChamberParameterless<ReactorType> { \ +public: \ + Internals(Reactor *reactor) \ + : ReactionChamberParameterless<ReactorType>(reactor) {} \ +private: + +#define REACTION_SCOPE_END_NO_PARAMS(reactor) \ +}; \ +Internals reaction_internals{reactor}; + +#define REACTION_SCOPE(ReactorType) ReactorType::Internals + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/ReactionBase.hh b/include/reactor-sdk/ReactionBase.hh new file mode 100644 index 00000000..2701c67a --- /dev/null +++ b/include/reactor-sdk/ReactionBase.hh @@ -0,0 +1,22 @@ +#pragma once + +namespace sdk +{ +class Reactor; + +class ReactionBase : public std::enable_shared_from_this<ReactionBase> +{ +public: + Reactor *reactor; + std::shared_ptr<ReactionBase> next; + std::string name; + +public: + ReactionBase(std::string name, Reactor *parent) + : reactor(parent), next(nullptr), name(name) {} + virtual ~ReactionBase() = default; + + virtual void assemble() {} +}; + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/Reactor.hh b/include/reactor-sdk/Reactor.hh new file mode 100644 index 00000000..f4354b59 --- /dev/null +++ b/include/reactor-sdk/Reactor.hh @@ -0,0 +1,155 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" +#include <string.h> +#include "ReactionBase.hh" +#include "SystemParameterBase.hh" +#include "Environment.hh" + +namespace sdk +{ + +template <typename InputTuple> +class ReactionInput; + +template <typename Fn, typename InputTuple, typename DependencyTuple, typename OutputTuple> +class Reaction; + +template<typename T> +class Input; + +template<typename T> +class Output; + +template<typename T> +class MultiportOutput; + +template<typename T> +class MultiportInput; + +class Timer; + +template <typename T> +struct trigger_value_type; + +template <typename T> +struct trigger_value_type<reactor::LogicalAction<T> *> +{ + using type = reactor::LogicalAction<T>&; +}; + +template <typename T> +struct trigger_value_type<Input<T> *> +{ + using type = Input<T>&; +}; + +template <typename T> +struct trigger_value_type<MultiportInput<T> *> +{ + using type = MultiportInput<T>&; +}; + +template <typename T> +struct trigger_value_type<MultiportOutput<T> *> +{ + using type = MultiportOutput<T>&; +}; + +template <typename T> +struct trigger_value_type<Output<T> *> +{ + using type = Output<T>&; +}; + +template <> +struct trigger_value_type<reactor::StartupTrigger *> +{ + using type = reactor::StartupTrigger&; +}; + +template <> +struct trigger_value_type<reactor::ShutdownTrigger *> +{ + using type = reactor::ShutdownTrigger&; +}; + +template <> +struct trigger_value_type<Timer *> +{ + using type = Timer&; +}; + +class Reactor : public reactor::Reactor +{ +protected: + reactor::StartupTrigger startup{"startup", this}; + reactor::ShutdownTrigger shutdown{"shutdown", this}; + +private: + size_t bank_index_ = 0; + SystemParameterBase *p_param = nullptr; + Environment *env{nullptr}; + Reactor *parent{nullptr}; + std::unordered_map<std::string, std::shared_ptr<ReactionBase>> reaction_map; + ReactionBase *reaction_internals_; + int priority = 1; + std::set<Reactor*> child_reactors; + std::string homog_name = ""; + + void add_child(Reactor* reactor); + void add_to_reaction_map (std::string &name, std::shared_ptr<ReactionBase> reaction); + int get_priority() { return priority++;} + + template <typename Fn, typename... InputTriggers, typename... Dependencies, typename... OutputTriggers> + void validate_reaction(Fn func, std::tuple<InputTriggers...> inputs, std::tuple<Dependencies...> deps, std::tuple<OutputTriggers...> outputs) { + (void)func; + (void)inputs; + (void)deps; + (void)outputs; + static_assert( + std::is_invocable_v< + Fn, + typename trigger_value_type<InputTriggers>::type..., + typename trigger_value_type<Dependencies>::type..., + typename trigger_value_type<OutputTriggers>::type... + >, + "Reaction function parameters must match the declared input and output types."); + } + + void populate_params(std::set<std::string> &types, std::map<std::string, std::string> &homog_map_entries, std::map<std::string, std::string> &hetero_map_entries); + +public: + const size_t &bank_index = bank_index_; + + Reactor(const std::string &name, Environment *env); + Reactor(const std::string &name, Reactor *container); + + void add_reaction_internals (ReactionBase* internals) { + reaction_internals_ = internals; + } + + static std::string BankName(const std::string& name); + static std::string HomogName(const std::string& name); + + void set_param (SystemParameterBase *param) { p_param = param; } + + Environment *get_env() { return env; } + + auto homog_fqn() const noexcept -> const std::string& { return homog_name; } + + virtual void construction() = 0; + virtual void wiring() = 0; + void construct() override; + void assemble() override; + + template <typename Fn, typename InputTuple, typename DependencyTuple, typename OutputTuple> + friend class Reaction; + + template <typename ReactorType> + friend class ReactorBank; + + friend class Environment; +}; + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/ReactorBank.hh b/include/reactor-sdk/ReactorBank.hh new file mode 100644 index 00000000..afeee39c --- /dev/null +++ b/include/reactor-sdk/ReactorBank.hh @@ -0,0 +1,699 @@ +#pragma once + +#include "Reactor.hh" + +namespace sdk +{ + +template<typename T> +class Input; + +template<typename T> +class Output; + +template<typename T> +class MultiportOutput; + +template<typename T> +class MultiportInput; + +template <typename ReactorType, typename T> +class ReactorBankInputPort { +public: + ReactorBankInputPort(std::vector<std::unique_ptr<ReactorType>>& reactors, Input<T> ReactorType::*member) + : reactors(reactors), member(member) {} + + using iterator = typename std::vector<std::unique_ptr<ReactorType>>::iterator; + using const_iterator = typename std::vector<std::unique_ptr<ReactorType>>::const_iterator; + + auto operator[](std::size_t index) noexcept -> ReactorType& { return *reactors[index]->get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { return *reactors[index]->get(); } + + auto begin() noexcept -> iterator { return reactors.begin(); }; + auto begin() const noexcept -> const_iterator { return reactors.begin(); }; + auto cbegin() const noexcept -> const_iterator { return reactors.cbegin(); }; + auto end() noexcept -> iterator { return reactors.end(); }; + auto end() const noexcept -> const_iterator { return reactors.end(); }; + auto cend() const noexcept -> const_iterator { return reactors.cend(); }; + + auto size() const noexcept -> size_t { return reactors.size(); }; + [[nodiscard]] auto empty() const noexcept -> bool { return reactors.empty(); }; + + Input<T> ReactorType::* get_member() { return member; } + +private: + std::vector<std::unique_ptr<ReactorType>>& reactors; + Input<T> ReactorType::*member; +}; + +template <typename ReactorType, typename T> +class ReactorBankInputPortOffset { +public: + ReactorBankInputPortOffset(std::vector<std::unique_ptr<ReactorType>>& reactors, std::ptrdiff_t offset) + : reactors(reactors), offset(offset) {} + + using iterator = typename std::vector<std::unique_ptr<ReactorType>>::iterator; + using const_iterator = typename std::vector<std::unique_ptr<ReactorType>>::const_iterator; + + auto operator[](std::size_t index) noexcept -> ReactorType& { return *reactors[index]->get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { return *reactors[index]->get(); } + + auto begin() noexcept -> iterator { return reactors.begin(); }; + auto begin() const noexcept -> const_iterator { return reactors.begin(); }; + auto cbegin() const noexcept -> const_iterator { return reactors.cbegin(); }; + auto end() noexcept -> iterator { return reactors.end(); }; + auto end() const noexcept -> const_iterator { return reactors.end(); }; + auto cend() const noexcept -> const_iterator { return reactors.cend(); }; + + auto size() const noexcept -> size_t { return reactors.size(); }; + [[nodiscard]] auto empty() const noexcept -> bool { return reactors.empty(); }; + + std::ptrdiff_t get_offset() { return offset; } + +private: + std::vector<std::unique_ptr<ReactorType>>& reactors; + std::ptrdiff_t offset; +}; + +template <typename ReactorType, typename T> +class ReactorBankInputMultiPort { +public: + ReactorBankInputMultiPort(std::vector<std::unique_ptr<ReactorType>> &reactors, MultiportInput<T> ReactorType::*member) + : reactors(reactors), member(member) {} + + using iterator = typename std::vector<std::unique_ptr<ReactorType>>::iterator; + using const_iterator = typename std::vector<std::unique_ptr<ReactorType>>::const_iterator; + + auto operator[](std::size_t index) noexcept -> ReactorType& { return *reactors[index]->get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { return *reactors[index]->get(); } + + auto begin() noexcept -> iterator { return reactors.begin(); }; + auto begin() const noexcept -> const_iterator { return reactors.begin(); }; + auto cbegin() const noexcept -> const_iterator { return reactors.cbegin(); }; + auto end() noexcept -> iterator { return reactors.end(); }; + auto end() const noexcept -> const_iterator { return reactors.end(); }; + auto cend() const noexcept -> const_iterator { return reactors.cend(); }; + + auto size() const noexcept -> size_t { return reactors.size(); }; + [[nodiscard]] auto empty() const noexcept -> bool { return reactors.empty(); }; + +private: + std::vector<std::unique_ptr<ReactorType>>& reactors; + MultiportInput<T> ReactorType::*member; +}; + +template <typename ReactorType, typename T> +class ReactorBankInputMultiPortOffset { +public: + ReactorBankInputMultiPortOffset(std::vector<std::unique_ptr<ReactorType>>& reactors, std::ptrdiff_t offset) + : reactors(reactors), offset(offset) {} + + using iterator = typename std::vector<std::unique_ptr<ReactorType>>::iterator; + using const_iterator = typename std::vector<std::unique_ptr<ReactorType>>::const_iterator; + + auto operator[](std::size_t index) noexcept -> ReactorType& { return *reactors[index]->get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { return *reactors[index]->get(); } + + auto begin() noexcept -> iterator { return reactors.begin(); }; + auto begin() const noexcept -> const_iterator { return reactors.begin(); }; + auto cbegin() const noexcept -> const_iterator { return reactors.cbegin(); }; + auto end() noexcept -> iterator { return reactors.end(); }; + auto end() const noexcept -> const_iterator { return reactors.end(); }; + auto cend() const noexcept -> const_iterator { return reactors.cend(); }; + + auto size() const noexcept -> size_t { return reactors.size(); }; + [[nodiscard]] auto empty() const noexcept -> bool { return reactors.empty(); }; + + std::ptrdiff_t get_offset() { return offset; } + +private: + std::vector<std::unique_ptr<ReactorType>>& reactors; + std::ptrdiff_t offset; +}; + +template <typename ReactorType, typename T> +class ReactorBankOutputPort { + class WiringProxy { + public: + WiringProxy(ReactorBankOutputPort& origin) : origin(origin) {} + + void operator>(Input<T>& input) { + origin.connect (input); + } + + void operator>(MultiportInput<T>& input) { + origin.connect (input); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + private: + ReactorBankOutputPort& origin; + }; + + void connect(Input<T>& input); + + void connect(MultiportInput<T>& input); + + template <typename OtherReactorType> + void connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports); + +public: + ReactorBankOutputPort(std::vector<std::unique_ptr<ReactorType>>& reactors, Output<T> ReactorType::*member) + : reactors(reactors), member(member) {} + + using iterator = typename std::vector<std::unique_ptr<ReactorType>>::iterator; + using const_iterator = typename std::vector<std::unique_ptr<ReactorType>>::const_iterator; + + auto operator[](std::size_t index) noexcept -> ReactorType& { return *reactors[index]->get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { return *reactors[index]->get(); } + + auto begin() noexcept -> iterator { return reactors.begin(); }; + auto begin() const noexcept -> const_iterator { return reactors.begin(); }; + auto cbegin() const noexcept -> const_iterator { return reactors.cbegin(); }; + auto end() noexcept -> iterator { return reactors.end(); }; + auto end() const noexcept -> const_iterator { return reactors.end(); }; + auto cend() const noexcept -> const_iterator { return reactors.cend(); }; + + auto size() const noexcept -> size_t { return reactors.size(); }; + [[nodiscard]] auto empty() const noexcept -> bool { return reactors.empty(); }; + + WiringProxy operator--(int) { + return WiringProxy(*this); + } + +private: + std::vector<std::unique_ptr<ReactorType>>& reactors; + Output<T> ReactorType::*member; +}; + +template <typename ReactorType, typename T> +class ReactorBankOutputPortOffset { + class WiringProxy { + public: + WiringProxy(ReactorBankOutputPortOffset& origin) : origin(origin) {} + + void operator>(Input<T>& input) { + origin.connect (input); + } + + void operator>(MultiportInput<T>& input) { + origin.connect (input); + } + + void operator>>(MultiportInput<T>& input) { + origin.connect_fanout (input); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputMultiPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + private: + ReactorBankOutputPortOffset& origin; + }; + + void connect(Input<T>& input); + void connect(MultiportInput<T>& input); + void connect_fanout(MultiportInput<T>& input); + + template <typename OtherReactorType> + void connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputMultiPortOffset<OtherReactorType, T> &&other_bank_ports); + +public: + ReactorBankOutputPortOffset(std::vector<std::unique_ptr<ReactorType>>& reactors, std::ptrdiff_t offset) + : reactors(reactors), offset(offset) {} + + using iterator = typename std::vector<std::unique_ptr<ReactorType>>::iterator; + using const_iterator = typename std::vector<std::unique_ptr<ReactorType>>::const_iterator; + + auto operator[](std::size_t index) noexcept -> ReactorType& { return *reactors[index]->get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { return *reactors[index]->get(); } + + auto begin() noexcept -> iterator { return reactors.begin(); }; + auto begin() const noexcept -> const_iterator { return reactors.begin(); }; + auto cbegin() const noexcept -> const_iterator { return reactors.cbegin(); }; + auto end() noexcept -> iterator { return reactors.end(); }; + auto end() const noexcept -> const_iterator { return reactors.end(); }; + auto cend() const noexcept -> const_iterator { return reactors.cend(); }; + + auto size() const noexcept -> size_t { return reactors.size(); }; + [[nodiscard]] auto empty() const noexcept -> bool { return reactors.empty(); }; + + WiringProxy operator--(int) { + return WiringProxy(*this); + } + +private: + std::vector<std::unique_ptr<ReactorType>>& reactors; + std::ptrdiff_t offset; +}; + +template <typename ReactorType, typename T> +class ReactorBankOutputMultiPort { + class WiringProxy { + public: + WiringProxy(ReactorBankOutputMultiPort& origin) : origin(origin) {} + + void operator>(MultiportInput<T>& input) { + origin.connect (input); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + private: + ReactorBankOutputMultiPort& origin; + }; + + void connect(MultiportInput<T>& input); + + template <typename OtherReactorType> + void connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports); + +public: + ReactorBankOutputMultiPort(std::vector<std::unique_ptr<ReactorType>>& reactors, MultiportOutput<T> ReactorType::*member) + : reactors(reactors), member(member) {} + + using iterator = typename std::vector<std::unique_ptr<ReactorType>>::iterator; + using const_iterator = typename std::vector<std::unique_ptr<ReactorType>>::const_iterator; + + auto operator[](std::size_t index) noexcept -> ReactorType& { return *reactors[index]->get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { return *reactors[index]->get(); } + + auto begin() noexcept -> iterator { return reactors.begin(); }; + auto begin() const noexcept -> const_iterator { return reactors.begin(); }; + auto cbegin() const noexcept -> const_iterator { return reactors.cbegin(); }; + auto end() noexcept -> iterator { return reactors.end(); }; + auto end() const noexcept -> const_iterator { return reactors.end(); }; + auto cend() const noexcept -> const_iterator { return reactors.cend(); }; + + auto size() const noexcept -> size_t { return reactors.size(); }; + [[nodiscard]] auto empty() const noexcept -> bool { return reactors.empty(); }; + + WiringProxy operator--(int) { + return WiringProxy(*this); + } + +private: + std::vector<std::unique_ptr<ReactorType>>& reactors; + MultiportOutput<T> ReactorType::*member; +}; + +template <typename ReactorType, typename T> +class ReactorBankOutputMultiPortOffset { + class WiringProxy { + public: + WiringProxy(ReactorBankOutputMultiPortOffset& origin) : origin(origin) {} + + void operator>(MultiportInput<T>& input) { + origin.connect (input); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template <typename OtherReactorType> + void operator>(ReactorBankInputMultiPortOffset<OtherReactorType, T> &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + private: + ReactorBankOutputMultiPortOffset& origin; + }; + + void connect(MultiportInput<T>& input); + + template <typename OtherReactorType> + void connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports); + + template <typename OtherReactorType> + void connect(ReactorBankInputMultiPortOffset<OtherReactorType, T> &&other_bank_ports); + +public: + ReactorBankOutputMultiPortOffset(std::vector<std::unique_ptr<ReactorType>>& reactors, std::ptrdiff_t offset) + : reactors(reactors), offset(offset) {} + + using iterator = typename std::vector<std::unique_ptr<ReactorType>>::iterator; + using const_iterator = typename std::vector<std::unique_ptr<ReactorType>>::const_iterator; + + auto operator[](std::size_t index) noexcept -> ReactorType& { return *reactors[index]->get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { return *reactors[index]->get(); } + + auto begin() noexcept -> iterator { return reactors.begin(); }; + auto begin() const noexcept -> const_iterator { return reactors.begin(); }; + auto cbegin() const noexcept -> const_iterator { return reactors.cbegin(); }; + auto end() noexcept -> iterator { return reactors.end(); }; + auto end() const noexcept -> const_iterator { return reactors.end(); }; + auto cend() const noexcept -> const_iterator { return reactors.cend(); }; + + auto size() const noexcept -> size_t { return reactors.size(); }; + [[nodiscard]] auto empty() const noexcept -> bool { return reactors.empty(); }; + + WiringProxy operator--(int) { + return WiringProxy(*this); + } + +private: + std::vector<std::unique_ptr<ReactorType>>& reactors; + std::ptrdiff_t offset; +}; + +template <typename ReactorType> +class ReactorBank { +public: + ReactorBank(const std::string &_name, Environment *env) + : name(_name), e_parent(env) {} + + ReactorBank(const std::string &_name, Reactor *container) + : name(_name), r_parent(container) {} + + void reserve(std::size_t size) noexcept { + reactors.reserve(size); + } + + void create_reactor() { + assert (e_parent || r_parent); + std::string bank_name = name + "\r\n" + std::to_string(index); + if (e_parent) { + reactors.emplace_back(std::make_unique<ReactorType>(bank_name, e_parent)); + } else { + reactors.emplace_back(std::make_unique<ReactorType>(bank_name, r_parent)); + } + reactors.back()->bank_index_ = index++; + } + + template <class... Args> void create_reactor(Args&&... args) noexcept { + assert (e_parent || r_parent); + std::string bank_name = name + "\r\n" + std::to_string(index); + if (e_parent) { + reactors.emplace_back(std::make_unique<ReactorType>(bank_name, e_parent, std::forward<Args>(args)...)); + } else { + reactors.emplace_back(std::make_unique<ReactorType>(bank_name, r_parent, std::forward<Args>(args)...)); + } + reactors.back()->bank_index_ = index++; + } + + template <class... Args> void emplace_back(Args&&... args) noexcept { + reactors.emplace_back(std::forward<Args>(args)...); + reactors.back()->bank_index_ = index++; + reactors.back()->homog_name = reactors.back()->parent ? (reactors.back()->parent->homog_name + "." + name) : name; + } + + template <typename T> + std::pair<std::vector<std::unique_ptr<ReactorType>>*, Input<T> ReactorType::*> operator()(Input<T> ReactorType::*member) { + return std::make_pair(&reactors, static_cast<Input<T> ReactorType::*>(member)); + } + + template <typename T> + ReactorBankInputPort<ReactorType, T> operator->*(Input<T> ReactorType::*member) { + return ReactorBankInputPort<ReactorType, T>(reactors, member); + } + + template <typename T> + ReactorBankInputPortOffset<ReactorType, T> operator->*(Input<T> *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast<const char*>(reactor.get()); + if ((reinterpret_cast<const char*>(member) >= base_ptr) && (reinterpret_cast<const char*>(member) < base_ptr + object_size)) { + offset = reinterpret_cast<const char*>(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankInputPortOffset<ReactorType, T>(reactors, offset); + } + + template <typename T> + ReactorBankInputPort<ReactorType, T> for_each(Input<T> ReactorType::*member) { + return ReactorBankInputPort<ReactorType, T>(reactors, member); + } + + template <typename T> + ReactorBankInputPortOffset<ReactorType, T> for_each(Input<T> *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast<const char*>(reactor.get()); + if ((reinterpret_cast<const char*>(member) >= base_ptr) && (reinterpret_cast<const char*>(member) < base_ptr + object_size)) { + offset = reinterpret_cast<const char*>(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankInputPortOffset<ReactorType, T>(reactors, offset); + } + + template <typename T> + ReactorBankInputMultiPort<ReactorType, T> operator->*(MultiportInput<T> ReactorType::*member) { + return ReactorBankInputMultiPort<ReactorType, T>(reactors, member); + } + + template <typename T> + ReactorBankInputMultiPortOffset<ReactorType, T> operator->*(MultiportInput<T> *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast<const char*>(reactor.get()); + if ((reinterpret_cast<const char*>(member) >= base_ptr) && (reinterpret_cast<const char*>(member) < base_ptr + object_size)) { + offset = reinterpret_cast<const char*>(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankInputMultiPortOffset<ReactorType, T>(reactors, offset); + } + + template <typename T> + ReactorBankInputMultiPort<ReactorType, T> for_each(MultiportInput<T> ReactorType::*member) { + return ReactorBankInputMultiPort<ReactorType, T>(reactors, member); + } + + template <typename T> + ReactorBankInputMultiPortOffset<ReactorType, T> for_each(MultiportInput<T> *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast<const char*>(reactor.get()); + if ((reinterpret_cast<const char*>(member) >= base_ptr) && (reinterpret_cast<const char*>(member) < base_ptr + object_size)) { + offset = reinterpret_cast<const char*>(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankInputMultiPortOffset<ReactorType, T>(reactors, offset); + } + + template <typename T> + ReactorBankOutputPort<ReactorType, T> operator->*(Output<T> ReactorType::*member) { + return ReactorBankOutputPort<ReactorType, T>(reactors, member); + } + + template <typename T> + ReactorBankOutputPortOffset<ReactorType, T> operator->*(Output<T> *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast<const char*>(reactor.get()); + if ((reinterpret_cast<const char*>(member) >= base_ptr) && (reinterpret_cast<const char*>(member) < base_ptr + object_size)) { + offset = reinterpret_cast<const char*>(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankOutputPortOffset<ReactorType, T>(reactors, offset); + } + + template <typename T> + ReactorBankOutputPort<ReactorType, T> for_each(Output<T> ReactorType::*member) { + return ReactorBankOutputPort<ReactorType, T>(reactors, member); + } + + template <typename T> + ReactorBankOutputPortOffset<ReactorType, T> for_each(Output<T> *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast<const char*>(reactor.get()); + if ((reinterpret_cast<const char*>(member) >= base_ptr) && (reinterpret_cast<const char*>(member) < base_ptr + object_size)) { + offset = reinterpret_cast<const char*>(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankOutputPortOffset<ReactorType, T>(reactors, offset); + } + + template <typename T> + ReactorBankOutputMultiPort<ReactorType, T> operator->*(MultiportOutput<T> ReactorType::*member) { + return ReactorBankOutputMultiPort<ReactorType, T>(reactors, member); + } + + template <typename T> + ReactorBankOutputMultiPortOffset<ReactorType, T> operator->*(MultiportOutput<T> *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast<const char*>(reactor.get()); + if ((reinterpret_cast<const char*>(member) >= base_ptr) && (reinterpret_cast<const char*>(member) < base_ptr + object_size)) { + offset = reinterpret_cast<const char*>(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankOutputMultiPortOffset<ReactorType, T>(reactors, offset); + } + + template <typename T> + ReactorBankOutputMultiPort<ReactorType, T> for_each(MultiportOutput<T> ReactorType::*member) { + return ReactorBankOutputMultiPort<ReactorType, T>(reactors, member); + } + + template <typename T> + ReactorBankOutputMultiPortOffset<ReactorType, T> for_each(MultiportOutput<T> *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast<const char*>(reactor.get()); + if ((reinterpret_cast<const char*>(member) >= base_ptr) && (reinterpret_cast<const char*>(member) < base_ptr + object_size)) { + offset = reinterpret_cast<const char*>(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankOutputMultiPortOffset<ReactorType, T>(reactors, offset); + } + + ReactorBank& operator->() { + return *this; + } + + auto operator[](std::size_t index) noexcept -> ReactorType& { assert (index < reactors.size()); return *reactors[index].get(); } + auto operator[](std::size_t index) const noexcept -> const ReactorType& { assert (index < reactors.size()); return *reactors[index].get(); } + +private: + std::vector<std::unique_ptr<ReactorType>> reactors; + size_t index = 0; + std::string name = ""; + Reactor *r_parent{nullptr}; + Environment *e_parent{nullptr}; +}; + + +} // namespace sdk + +#include "impl/ReactorBankOutputPort_wiring_impl.hh" +#include "impl/ReactorBankOutputMultiport_wiring_impl.hh" \ No newline at end of file diff --git a/include/reactor-sdk/SystemParameterBase.hh b/include/reactor-sdk/SystemParameterBase.hh new file mode 100644 index 00000000..248b5802 --- /dev/null +++ b/include/reactor-sdk/SystemParameterBase.hh @@ -0,0 +1,16 @@ +#pragma once + +#include <set> +#include <map> +namespace sdk +{ + +class SystemParameterBase { +public: + virtual ~SystemParameterBase() = default; + virtual void fetch_config() = 0; + virtual void print() = 0; + virtual void populate_params(std::set<std::string> &types, std::map<std::string, std::string> &homog_map_entries, std::map<std::string, std::string> &hetero_map_entries) = 0; +}; + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/SystemParameters.hh b/include/reactor-sdk/SystemParameters.hh new file mode 100644 index 00000000..93cee5c6 --- /dev/null +++ b/include/reactor-sdk/SystemParameters.hh @@ -0,0 +1,152 @@ +#pragma once + +#include <string> +#include <cxxabi.h> +#include "SystemParameterBase.hh" +#include "Environment.hh" +#include "time_parser.hh" +namespace sdk +{ + +inline std::string convertToString(const std::string& s) { + std::string annotated = "\"" + s + "\""; + return annotated; +} + +template <typename T> +typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type +convertToString(const T& value) { + return std::to_string(value); +} + +inline std::string time_to_string_(const reactor::Duration& dur) { + if (dur == reactor::Duration::max()) { + return "forever"; + } + + std::stringstream ss; + ss << dur.count() << "ns"; + return ss.str(); +} + +inline std::string convertToString(const reactor::Duration& dur) { + return time_to_string_ (dur); +} + +template <typename T> +struct ParameterMetadata { + std::string name; + std::string description; + T min_value; + T max_value; + T value; + std::string type_name; +}; + +#define REACTOR_PARAMETER(Type, variable_name, description, min_value, max_value, default_value) \ + ParameterMetadata<Type> variable_name = ParameterMetadata<Type>{ #variable_name, description, min_value, max_value, default_value, #Type } + +template <typename... ParameterValueType> +class SystemParametersStandalone : public SystemParameterBase { +public: + using ParameterValue = std::variant<ParameterMetadata<ParameterValueType>*...>; + + struct MapEntry { + std::string alt_name; + ParameterValue param; + }; + + SystemParametersStandalone(Reactor *owner) + : reactor(owner), env(owner->get_env()) { + reactor->set_param (this); + } + + void fetch_config() override { + if (env->get_config_params()) { + for (auto& entry : param_map) { + std::visit([&](auto* paramMetadataPtr) { + env->get_config_params()->PullConfigParameter(false, entry.second.alt_name, paramMetadataPtr); + env->get_config_params()->PullConfigParameter(true, entry.first, paramMetadataPtr); + }, entry.second.param); + } + } + } + + void print() override { + for (const auto& entry : param_map) { + std::cout << "Parameter: " << entry.first << ", alt_name:" << entry.second.alt_name << ", "; + + std::visit([](auto&& param) { + std::cout << "Description: " << param->description + << ", Value: " << param->value + << ", Value Type Key: " << typeid(param->value).name() + << ", Value Type: " << param->type_name + << std::endl; + }, entry.second.param); + } + } + + template <typename... Args> + void register_parameters(Args&... args) { + register_parameters_(reactor->fqn(), reactor->homog_fqn(), args...); + // print(); + } + + void populate_params(std::set<std::string> &types, std::map<std::string, std::string> &homog_map_entries, std::map<std::string, std::string> &hetero_map_entries) { + for (const auto& entry : param_map) { + std::cout << "POPULATING: " << entry.first << ", alt_name:" << entry.second.alt_name << std::endl; + + std::visit([&](auto&& param) { + bool result = types.insert(param->type_name).second; + std::cout << "type:" << param->type_name << (result ? " PUSHED" : " SKIPPED") << std::endl; + if (homog_map_entries.find (entry.second.alt_name) == homog_map_entries.end()) { + std::string homog_entry_str = "{ \"" + entry.second.alt_name + "\", ConfigParameterMetadata<" + param->type_name + "> {" + convertToString(param->value) + "} }"; + homog_map_entries[entry.second.alt_name] = homog_entry_str; + std::cout << "homog-entry:" << homog_entry_str << " PUSHED" << std::endl; + } else { + std::cout << "homog-entry:" << entry.second.alt_name << " SKIPPED" << std::endl; + } + + if (hetero_map_entries.find (entry.first) == hetero_map_entries.end()) { + std::string hetero_entry_str = "{ \"" + entry.first + "\", ConfigParameterMetadata<" + param->type_name + "> {" + convertToString(param->value) + "} }"; + hetero_map_entries[entry.first] = hetero_entry_str; + std::cout << "hetero-entry:" << hetero_entry_str << " PUSHED" << std::endl; + } else { + std::cout << "hetero-entry:" << entry.first << " SKIPPED" << std::endl; + } + }, entry.second.param); + } + } + +private: + std::map<std::string, MapEntry> param_map; + Reactor *reactor; + Environment *env; + + template <typename T> + void register_parameter(const std::string& hetero_name, const std::string& homog_name, ParameterMetadata<T>& param) { + MapEntry entry = MapEntry { + .alt_name = homog_name, + .param = ¶m + }; + param_map[hetero_name] = std::move(entry); + } + + template <typename T, typename... Args> + void register_parameters_(const std::string& hetero_name, const std::string& homog_name, ParameterMetadata<T>& first, Args&... args) { + register_parameter(hetero_name + "." + first.name, homog_name + "." + first.name, first); + if constexpr (sizeof...(args) > 0) { + register_parameters_(hetero_name, homog_name, args...); + } + } +}; + +template <typename Defaults, typename... ParameterValueType> +class SystemParameters : public SystemParametersStandalone<ParameterValueType...> { +public: + Defaults defaults; + SystemParameters(Reactor *owner, Defaults &¶m) + : SystemParametersStandalone<ParameterValueType...>(owner), defaults(std::forward<Defaults>(param)) {} +}; + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/cxxopts.hpp b/include/reactor-sdk/cxxopts.hpp new file mode 100644 index 00000000..870755a2 --- /dev/null +++ b/include/reactor-sdk/cxxopts.hpp @@ -0,0 +1,2610 @@ +/* + +Copyright (c) 2014, 2015, 2016, 2017 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include <cctype> +#include <cstring> +#include <exception> +#include <iostream> +#include <limits> +#include <list> +#include <map> +#include <memory> +#include <sstream> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> +#include <algorithm> + +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif + +#ifndef CXXOPTS_NO_REGEX +# include <regex> +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for <optional> +#ifdef __has_include +# if __has_include(<optional>) +# include <optional> +# ifdef __cpp_lib_optional +# define CXXOPTS_HAS_OPTIONAL +# endif +# endif +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 0 +#define CXXOPTS__VERSION_PATCH 0 + +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 + #define CXXOPTS_NULL_DEREF_IGNORE +#endif + +namespace cxxopts +{ + static constexpr struct { + uint8_t major, minor, patch; + } version = { + CXXOPTS__VERSION_MAJOR, + CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH + }; +} // namespace cxxopts + +//when we ask cxxopts to use Unicode, help strings are processed using ICU, +//which results in the correct lengths being computed for strings when they +//are formatted for the help output +//it is necessary to make sure that <unicode/unistr.h> can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include <unicode/unistr.h> + +namespace cxxopts +{ + using String = icu::UnicodeString; + + inline + String + toLocalString(std::string s) + { + return icu::UnicodeString::fromUTF8(std::move(s)); + } + +#if defined(__GNUC__) +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Weffc++" +// This will be ignored under other compilers like LLVM clang. +#endif + class UnicodeStringIterator : public + std::iterator<std::forward_iterator_tag, int32_t> + { + public: + + UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos) + : s(string) + , i(pos) + { + } + + value_type + operator*() const + { + return s->char32At(i); + } + + bool + operator==(const UnicodeStringIterator& rhs) const + { + return s == rhs.s && i == rhs.i; + } + + bool + operator!=(const UnicodeStringIterator& rhs) const + { + return !(*this == rhs); + } + + UnicodeStringIterator& + operator++() + { + ++i; + return *this; + } + + UnicodeStringIterator + operator+(int32_t v) + { + return UnicodeStringIterator(s, i + v); + } + + private: + const icu::UnicodeString* s; + int32_t i; + }; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + + inline + String& + stringAppend(String&s, String a) + { + return s.append(std::move(a)); + } + + inline + String& + stringAppend(String& s, size_t n, UChar32 c) + { + for (size_t i = 0; i != n; ++i) + { + s.append(c); + } + + return s; + } + + template <typename Iterator> + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + while (begin != end) + { + s.append(*begin); + ++begin; + } + + return s; + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + std::string + toUTF8String(const String& s) + { + std::string result; + s.toUTF8String(result); + + return result; + } + + inline + bool + empty(const String& s) + { + return s.isEmpty(); + } +} + +namespace std +{ + inline + cxxopts::UnicodeStringIterator + begin(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, 0); + } + + inline + cxxopts::UnicodeStringIterator + end(const icu::UnicodeString& s) + { + return cxxopts::UnicodeStringIterator(&s, s.length()); + } +} + +//ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts +{ + using String = std::string; + + template <typename T> + T + toLocalString(T&& t) + { + return std::forward<T>(t); + } + + inline + size_t + stringLength(const String& s) + { + return s.length(); + } + + inline + String& + stringAppend(String&s, const String& a) + { + return s.append(a); + } + + inline + String& + stringAppend(String& s, size_t n, char c) + { + return s.append(n, c); + } + + template <typename Iterator> + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template <typename T> + std::string + toUTF8String(T&& t) + { + return std::forward<T>(t); + } + + inline + bool + empty(const std::string& s) + { + return s.empty(); + } +} // namespace cxxopts + +//ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts +{ + namespace + { +#ifdef _WIN32 + const std::string LQUOTE("\'"); + const std::string RQUOTE("\'"); +#else + const std::string LQUOTE("‘"); + const std::string RQUOTE("’"); +#endif + } // namespace + +#if defined(__GNUC__) +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it: +// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma GCC diagnostic ignored "-Weffc++" +// This will be ignored under other compilers like LLVM clang. +#endif + class Value : public std::enable_shared_from_this<Value> + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr<Value> + clone() const = 0; + + virtual void + parse(const std::string& text) const = 0; + + virtual void + parse() const = 0; + + virtual bool + has_default() const = 0; + + virtual bool + is_container() const = 0; + + virtual bool + has_implicit() const = 0; + + virtual std::string + get_default_value() const = 0; + + virtual std::string + get_implicit_value() const = 0; + + virtual std::shared_ptr<Value> + default_value(const std::string& value) = 0; + + virtual std::shared_ptr<Value> + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr<Value> + no_implicit_value() = 0; + + virtual bool + is_boolean() const = 0; + }; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + class OptionException : public std::exception + { + public: + explicit OptionException(std::string message) + : m_message(std::move(message)) + { + } + + CXXOPTS_NODISCARD + const char* + what() const noexcept override + { + return m_message.c_str(); + } + + private: + std::string m_message; + }; + + class OptionSpecException : public OptionException + { + public: + + explicit OptionSpecException(const std::string& message) + : OptionException(message) + { + } + }; + + class OptionParseException : public OptionException + { + public: + explicit OptionParseException(const std::string& message) + : OptionException(message) + { + } + }; + + class option_exists_error : public OptionSpecException + { + public: + explicit option_exists_error(const std::string& option) + : OptionSpecException("Option " + LQUOTE + option + RQUOTE + " already exists") + { + } + }; + + class invalid_option_format_error : public OptionSpecException + { + public: + explicit invalid_option_format_error(const std::string& format) + : OptionSpecException("Invalid option format " + LQUOTE + format + RQUOTE) + { + } + }; + + class option_syntax_exception : public OptionParseException { + public: + explicit option_syntax_exception(const std::string& text) + : OptionParseException("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") + { + } + }; + + class option_not_exists_exception : public OptionParseException + { + public: + explicit option_not_exists_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " does not exist") + { + } + }; + + class missing_argument_exception : public OptionParseException + { + public: + explicit missing_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is missing an argument" + ) + { + } + }; + + class option_requires_argument_exception : public OptionParseException + { + public: + explicit option_requires_argument_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " requires an argument" + ) + { + } + }; + + class option_not_has_argument_exception : public OptionParseException + { + public: + option_not_has_argument_exception + ( + const std::string& option, + const std::string& arg + ) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + + LQUOTE + arg + RQUOTE + " given" + ) + { + } + }; + + class option_not_present_exception : public OptionParseException + { + public: + explicit option_not_present_exception(const std::string& option) + : OptionParseException("Option " + LQUOTE + option + RQUOTE + " not present") + { + } + }; + + class option_has_no_value_exception : public OptionException + { + public: + explicit option_has_no_value_exception(const std::string& option) + : OptionException( + !option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } + }; + + class argument_incorrect_type : public OptionParseException + { + public: + explicit argument_incorrect_type + ( + const std::string& arg + ) + : OptionParseException( + "Argument " + LQUOTE + arg + RQUOTE + " failed to parse" + ) + { + } + }; + + class option_required_exception : public OptionParseException + { + public: + explicit option_required_exception(const std::string& option) + : OptionParseException( + "Option " + LQUOTE + option + RQUOTE + " is required but not present" + ) + { + } + }; + + template <typename T> + void throw_or_mimic(const std::string& text) + { + static_assert(std::is_base_of<std::exception, T>::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif + } + + namespace values + { + namespace parser_tool + { + struct IntegerDesc + { + std::string negative = ""; + std::string base = ""; + std::string value = ""; + }; + struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; + }; +#ifdef CXXOPTS_NO_REGEX + inline IntegerDesc SplitInteger(const std::string &text) + { + if (text.empty()) + { + throw_or_mimic<argument_incorrect_type>(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') + { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) + { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') + { + desc.value = std::string(pdata); + } + else + { + throw_or_mimic<argument_incorrect_type>(text); + } + return desc; + } + + inline bool IsTrueText(const std::string &text) + { + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') + { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) + { + return true; + } + } + else if (strncmp(pdata, "1\0", 2) == 0) + { + return true; + } + return false; + } + + inline bool IsFalseText(const std::string &text) + { + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') + { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) + { + return true; + } + } + else if (strncmp(pdata, "0\0", 2) == 0) + { + return true; + } + return false; + } + + inline std::pair<std::string, std::string> SplitSwitchDef(const std::string &text) + { + std::string short_sw, long_sw; + const char *pdata = text.c_str(); + if (isalnum(*pdata) && *(pdata + 1) == ',') { + short_sw = std::string(1, *pdata); + pdata += 2; + } + while (*pdata == ' ') { pdata += 1; } + if (isalnum(*pdata)) { + const char *store = pdata; + pdata += 1; + while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') { + pdata += 1; + } + if (*pdata == '\0') { + long_sw = std::string(store, pdata - store); + } else { + throw_or_mimic<invalid_option_format_error>(text); + } + } + return std::pair<std::string, std::string>(short_sw, long_sw); + } + + inline ArguDesc ParseArgument(const char *arg, bool &matched) + { + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) + { + pdata += 2; + if (isalnum(*pdata)) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata) || *pdata == '-' || *pdata == '_') + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) + { + if (*pdata == '=') + { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') + { + argu_desc.value = std::string(pdata); + } + matched = true; + } + else if (*pdata == '\0') + { + matched = true; + } + } + } + } + else if (strncmp(pdata, "-", 1) == 0) + { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata)) + { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; + } + +#else // CXXOPTS_NO_REGEX + + namespace + { + + std::basic_regex<char> integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex<char> truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex<char> falsy_pattern + ("(f|F)(alse)?|0"); + + std::basic_regex<char> option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + std::basic_regex<char> option_specifier + ("(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?"); + + } // namespace + + inline IntegerDesc SplitInteger(const std::string &text) + { + std::smatch match; + std::regex_match(text, match, integer_pattern); + + if (match.length() == 0) + { + throw_or_mimic<argument_incorrect_type>(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) + { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; + } + + inline bool IsTrueText(const std::string &text) + { + std::smatch result; + std::regex_match(text, result, truthy_pattern); + return !result.empty(); + } + + inline bool IsFalseText(const std::string &text) + { + std::smatch result; + std::regex_match(text, result, falsy_pattern); + return !result.empty(); + } + + inline std::pair<std::string, std::string> SplitSwitchDef(const std::string &text) + { + std::match_results<const char*> result; + std::regex_match(text.c_str(), result, option_specifier); + if (result.empty()) + { + throw_or_mimic<invalid_option_format_error>(text); + } + + const std::string& short_sw = result[2]; + const std::string& long_sw = result[3]; + + return std::pair<std::string, std::string>(short_sw, long_sw); + } + + inline ArguDesc ParseArgument(const char *arg, bool &matched) + { + std::match_results<const char*> result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) + { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; + } + +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX + } + + namespace detail + { + template <typename T, bool B> + struct SignedCheck; + + template <typename T> + struct SignedCheck<T, true> + { + template <typename U> + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast<U>((std::numeric_limits<T>::min)())) + { + throw_or_mimic<argument_incorrect_type>(text); + } + } + else + { + if (u > static_cast<U>((std::numeric_limits<T>::max)())) + { + throw_or_mimic<argument_incorrect_type>(text); + } + } + } + }; + + template <typename T> + struct SignedCheck<T, false> + { + template <typename U> + void + operator()(bool, U, const std::string&) const {} + }; + + template <typename T, typename U> + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text); + } + } // namespace detail + + template <typename R, typename T> + void + checked_negate(R& r, T&& t, const std::string&, std::true_type) + { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast<R>(-static_cast<R>(t-1)-1); + } + + template <typename R, typename T> + void + checked_negate(R&, T&&, const std::string& text, std::false_type) + { + throw_or_mimic<argument_incorrect_type>(text); + } + + template <typename T> + void + integer_parser(const std::string& text, T& value) + { + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); + + using US = typename std::make_unsigned<T>::type; + constexpr bool is_signed = std::numeric_limits<T>::is_signed; + + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string & value_match = int_desc.value; + + US result = 0; + + for (char ch : value_match) + { + US digit = 0; + + if (ch >= '0' && ch <= '9') + { + digit = static_cast<US>(ch - '0'); + } + else if (base == 16 && ch >= 'a' && ch <= 'f') + { + digit = static_cast<US>(ch - 'a' + 10); + } + else if (base == 16 && ch >= 'A' && ch <= 'F') + { + digit = static_cast<US>(ch - 'A' + 10); + } + else + { + throw_or_mimic<argument_incorrect_type>(text); + } + + const US next = static_cast<US>(result * base + digit); + if (result > next) + { + throw_or_mimic<argument_incorrect_type>(text); + } + + result = next; + } + + detail::check_signed_range<T>(negative, result, text); + + if (negative) + { + checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>()); + } + else + { + value = static_cast<T>(result); + } + } + + template <typename T> + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic<argument_incorrect_type>(text); + } + } + + template <typename T, + typename std::enable_if<std::is_integral<T>::value>::type* = nullptr + > + void parse_value(const std::string& text, T& value) + { + integer_parser(text, value); + } + + inline + void + parse_value(const std::string& text, bool& value) + { + if (parser_tool::IsTrueText(text)) + { + value = true; + return; + } + + if (parser_tool::IsFalseText(text)) + { + value = false; + return; + } + + throw_or_mimic<argument_incorrect_type>(text); + } + + inline + void + parse_value(const std::string& text, std::string& value) + { + value = text; + } + + // The fallback parser. It uses the stringstream parser to parse all types + // that have not been overloaded explicitly. It has to be placed in the + // source code before all other more specialized templates. + template <typename T, + typename std::enable_if<!std::is_integral<T>::value>::type* = nullptr + > + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template <typename T> + void + parse_value(const std::string& text, std::vector<T>& value) + { + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template <typename T> + void + parse_value(const std::string& text, std::optional<T>& value) + { + T result; + parse_value(text, result); + value = std::move(result); + } +#endif + + inline + void parse_value(const std::string& text, char& c) + { + if (text.length() != 1) + { + throw_or_mimic<argument_incorrect_type>(text); + } + + c = text[0]; + } + + template <typename T> + struct type_is_container + { + static constexpr bool value = false; + }; + + template <typename T> + struct type_is_container<std::vector<T>> + { + static constexpr bool value = true; + }; + + template <typename T> + class abstract_value : public Value + { + using Self = abstract_value<T>; + + public: + abstract_value() + : m_result(std::make_shared<T>()) + , m_store(m_result.get()) + { + } + + explicit abstract_value(T* t) + : m_store(t) + { + } + + ~abstract_value() override = default; + + abstract_value& operator=(const abstract_value&) = default; + + abstract_value(const abstract_value& rhs) + { + if (rhs.m_result) + { + m_result = std::make_shared<T>(); + m_store = m_result.get(); + } + else + { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void + parse(const std::string& text) const override + { + parse_value(text, *m_store); + } + + bool + is_container() const override + { + return type_is_container<T>::value; + } + + void + parse() const override + { + parse_value(m_default_value, *m_store); + } + + bool + has_default() const override + { + return m_default; + } + + bool + has_implicit() const override + { + return m_implicit; + } + + std::shared_ptr<Value> + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr<Value> + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr<Value> + no_implicit_value() override + { + m_implicit = false; + return shared_from_this(); + } + + std::string + get_default_value() const override + { + return m_default_value; + } + + std::string + get_implicit_value() const override + { + return m_implicit_value; + } + + bool + is_boolean() const override + { + return std::is_same<T, bool>::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr<T> m_result{}; + T* m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; + }; + + template <typename T> + class standard_value : public abstract_value<T> + { + public: + using abstract_value<T>::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr<Value> + clone() const override + { + return std::make_shared<standard_value<T>>(*this); + } + }; + + template <> + class standard_value<bool> : public abstract_value<bool> + { + public: + ~standard_value() override = default; + + standard_value() + { + set_default_and_implicit(); + } + + explicit standard_value(bool* b) + : abstract_value(b) + { + set_default_and_implicit(); + } + + std::shared_ptr<Value> + clone() const override + { + return std::make_shared<standard_value<bool>>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } // namespace values + + template <typename T> + std::shared_ptr<Value> + value() + { + return std::make_shared<values::standard_value<T>>(); + } + + template <typename T> + std::shared_ptr<Value> + value(T& t) + { + return std::make_shared<values::standard_value<T>>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + std::string short_, + std::string long_, + String desc, + std::shared_ptr<const Value> val + ) + : m_short(std::move(short_)) + , m_long(std::move(long_)) + , m_desc(std::move(desc)) + , m_value(std::move(val)) + , m_count(0) + { + m_hash = std::hash<std::string>{}(m_long + m_short); + } + + OptionDetails(const OptionDetails& rhs) + : m_desc(rhs.m_desc) + , m_value(rhs.m_value->clone()) + , m_count(rhs.m_count) + { + } + + OptionDetails(OptionDetails&& rhs) = default; + + CXXOPTS_NODISCARD + const String& + description() const + { + return m_desc; + } + + CXXOPTS_NODISCARD + const Value& + value() const { + return *m_value; + } + + CXXOPTS_NODISCARD + std::shared_ptr<Value> + make_storage() const + { + return m_value->clone(); + } + + CXXOPTS_NODISCARD + const std::string& + short_name() const + { + return m_short; + } + + CXXOPTS_NODISCARD + const std::string& + long_name() const + { + return m_long; + } + + size_t + hash() const + { + return m_hash; + } + + private: + std::string m_short{}; + std::string m_long{}; + String m_desc{}; + std::shared_ptr<const Value> m_value{}; + int m_count; + + size_t m_hash{}; + }; + + struct HelpOptionDetails + { + std::string s; + std::string l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; + }; + + struct HelpGroupDetails + { + std::string name{}; + std::string description{}; + std::vector<HelpOptionDetails> options{}; + }; + + class OptionValue + { + public: + void + parse + ( + const std::shared_ptr<const OptionDetails>& details, + const std::string& text + ) + { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_name = &details->long_name(); + } + + void + parse_default(const std::shared_ptr<const OptionDetails>& details) + { + ensure_value(details); + m_default = true; + m_long_name = &details->long_name(); + m_value->parse(); + } + + void + parse_no_value(const std::shared_ptr<const OptionDetails>& details) + { + m_long_name = &details->long_name(); + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnull-dereference" +#endif + + CXXOPTS_NODISCARD + size_t + count() const noexcept + { + return m_count; + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) +#pragma GCC diagnostic pop +#endif + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool + has_default() const noexcept + { + return m_default; + } + + template <typename T> + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic<option_has_no_value_exception>( + m_long_name == nullptr ? "" : *m_long_name); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast<const values::standard_value<T>&>(*m_value).get(); +#else + return dynamic_cast<const values::standard_value<T>&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(const std::shared_ptr<const OptionDetails>& details) + { + if (m_value == nullptr) + { + m_value = details->make_storage(); + } + } + + + const std::string* m_long_name = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. + std::shared_ptr<Value> m_value{}; + size_t m_count = 0; + bool m_default = false; + }; + + class KeyValue + { + public: + KeyValue(std::string key_, std::string value_) + : m_key(std::move(key_)) + , m_value(std::move(value_)) + { + } + + CXXOPTS_NODISCARD + const std::string& + key() const + { + return m_key; + } + + CXXOPTS_NODISCARD + const std::string& + value() const + { + return m_value; + } + + template <typename T> + T + as() const + { + T result; + values::parse_value(m_value, result); + return result; + } + + private: + std::string m_key; + std::string m_value; + }; + + using ParsedHashMap = std::unordered_map<size_t, OptionValue>; + using NameHashMap = std::unordered_map<std::string, size_t>; + + class ParseResult + { + public: + + ParseResult() = default; + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_unmatched(std::move(unmatched_args)) + { + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; + + size_t + count(const std::string& o) const + { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) + { + return 0; + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + return 0; + } + + return viter->second.count(); + } + + const OptionValue& + operator[](const std::string& option) const + { + auto iter = m_keys.find(option); + + if (iter == m_keys.end()) + { + throw_or_mimic<option_not_present_exception>(option); + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + throw_or_mimic<option_not_present_exception>(option); + } + + return viter->second; + } + + const std::vector<KeyValue>& + arguments() const + { + return m_sequential; + } + + const std::vector<std::string>& + unmatched() const + { + return m_unmatched; + } + + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector<KeyValue> m_sequential{}; + std::vector<std::string> m_unmatched{}; + }; + + struct Option + { + Option + ( + std::string opts, + std::string desc, + std::shared_ptr<const Value> value = ::cxxopts::value<bool>(), + std::string arg_help = "" + ) + : opts_(std::move(opts)) + , desc_(std::move(desc)) + , value_(std::move(value)) + , arg_help_(std::move(arg_help)) + { + } + + std::string opts_; + std::string desc_; + std::shared_ptr<const Value> value_; + std::string arg_help_; + }; + + using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>; + using PositionalList = std::vector<std::string>; + using PositionalListIterator = PositionalList::const_iterator; + + class OptionParser + { + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char* const* argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr<OptionDetails>& value, + const std::string& name + ); + + void + add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr<OptionDetails>& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr<OptionDetails>& details); + + void + parse_no_value(const std::shared_ptr<OptionDetails>& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector<KeyValue> m_sequential{}; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; + }; + + class Options + { + public: + + explicit Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_custom_help("[OPTION...]") + , m_positional_help("positional parameters") + , m_show_positional(false) + , m_allow_unrecognised(false) + , m_width(76) + , m_tab_expansion(false) + , m_options(std::make_shared<OptionMap>()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + Options& + custom_help(std::string help_text) + { + m_custom_help = std::move(help_text); + return *this; + } + + Options& + show_positional_help() + { + m_show_positional = true; + return *this; + } + + Options& + allow_unrecognised_options() + { + m_allow_unrecognised = true; + return *this; + } + + Options& + set_width(size_t width) + { + m_width = width; + return *this; + } + + Options& + set_tab_expansion(bool expansion=true) + { + m_tab_expansion = expansion; + return *this; + } + + ParseResult + parse(int argc, const char* const* argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_options + ( + const std::string& group, + std::initializer_list<Option> options + ); + + void + add_option + ( + const std::string& group, + const Option& option + ); + + void + add_option + ( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + const std::shared_ptr<const Value>& value, + std::string arg_help + ); + + //parse positional arguments into the given option + void + parse_positional(std::string option); + + void + parse_positional(std::vector<std::string> options); + + void + parse_positional(std::initializer_list<std::string> options); + + template <typename Iterator> + void + parse_positional(Iterator begin, Iterator end) { + parse_positional(std::vector<std::string>{begin, end}); + } + + std::string + help(const std::vector<std::string>& groups = {}) const; + + std::vector<std::string> + groups() const; + + const HelpGroupDetails& + group_help(const std::string& group) const; + + private: + + void + add_one_option + ( + const std::string& option, + const std::shared_ptr<OptionDetails>& details + ); + + String + help_one_group(const std::string& group) const; + + void + generate_group_help + ( + String& result, + const std::vector<std::string>& groups + ) const; + + void + generate_all_groups_help(String& result) const; + + std::string m_program{}; + String m_help_string{}; + std::string m_custom_help{}; + std::string m_positional_help{}; + bool m_show_positional; + bool m_allow_unrecognised; + size_t m_width; + bool m_tab_expansion; + + std::shared_ptr<OptionMap> m_options; + std::vector<std::string> m_positional{}; + std::unordered_set<std::string> m_positional_set{}; + + //mapping from groups to help options + std::map<std::string, HelpGroupDetails> m_help{}; + + std::list<OptionDetails> m_option_list{}; + std::unordered_map<std::string, decltype(m_option_list)::iterator> m_option_map{}; + }; + + class OptionAdder + { + public: + + OptionAdder(Options& options, std::string group) + : m_options(options), m_group(std::move(group)) + { + } + + OptionAdder& + operator() + ( + const std::string& opts, + const std::string& desc, + const std::shared_ptr<const Value>& value + = ::cxxopts::value<bool>(), + std::string arg_help = "" + ); + + private: + Options& m_options; + std::string m_group; + }; + + namespace + { + constexpr size_t OPTION_LONGEST = 30; + constexpr size_t OPTION_DESC_GAP = 2; + + String + format_option + ( + const HelpOptionDetails& o + ) + { + const auto& s = o.s; + const auto& l = o.l; + + String result = " "; + + if (!s.empty()) + { + result += "-" + toLocalString(s); + if (!l.empty()) + { + result += ","; + } + } + else + { + result += " "; + } + + if (!l.empty()) + { + result += " --" + toLocalString(l); + } + + auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : "arg"; + + if (!o.is_boolean) + { + if (o.has_implicit) + { + result += " [=" + arg + "(=" + toLocalString(o.implicit_value) + ")]"; + } + else + { + result += " " + arg; + } + } + + return result; + } + + String + format_description + ( + const HelpOptionDetails& o, + size_t start, + size_t allowed, + bool tab_expansion + ) + { + auto desc = o.desc; + + if (o.has_default && (!o.is_boolean || o.default_value != "false")) + { + if(!o.default_value.empty()) + { + desc += toLocalString(" (default: " + o.default_value + ")"); + } + else + { + desc += toLocalString(" (default: \"\")"); + } + } + + String result; + + if (tab_expansion) + { + String desc2; + auto size = size_t{ 0 }; + for (auto c = std::begin(desc); c != std::end(desc); ++c) + { + if (*c == '\n') + { + desc2 += *c; + size = 0; + } + else if (*c == '\t') + { + auto skip = 8 - size % 8; + stringAppend(desc2, skip, ' '); + size += skip; + } + else + { + desc2 += *c; + ++size; + } + } + desc = desc2; + } + + desc += " "; + + auto current = std::begin(desc); + auto previous = current; + auto startLine = current; + auto lastSpace = current; + + auto size = size_t{}; + + bool appendNewLine; + bool onlyWhiteSpace = true; + + while (current != std::end(desc)) + { + appendNewLine = false; + + if (std::isblank(*previous)) + { + lastSpace = current; + } + + if (!std::isblank(*current)) + { + onlyWhiteSpace = false; + } + + while (*current == '\n') + { + previous = current; + ++current; + appendNewLine = true; + } + + if (!appendNewLine && size >= allowed) + { + if (lastSpace != startLine) + { + current = lastSpace; + previous = current; + } + appendNewLine = true; + } + + if (appendNewLine) + { + stringAppend(result, startLine, current); + startLine = current; + lastSpace = current; + + if (*previous != '\n') + { + stringAppend(result, "\n"); + } + + stringAppend(result, start, ' '); + + if (*previous != '\n') + { + stringAppend(result, lastSpace, current); + } + + onlyWhiteSpace = true; + size = 0; + } + + previous = current; + ++current; + ++size; + } + + //append whatever is left but ignore whitespace + if (!onlyWhiteSpace) + { + stringAppend(result, startLine, previous); + } + + return result; + } + } // namespace + +inline +void +Options::add_options +( + const std::string &group, + std::initializer_list<Option> options +) +{ + OptionAdder option_adder(*this, group); + for (const auto &option: options) + { + option_adder(option.opts_, option.desc_, option.value_, option.arg_help_); + } +} + +inline +OptionAdder +Options::add_options(std::string group) +{ + return OptionAdder(*this, std::move(group)); +} + +inline +OptionAdder& +OptionAdder::operator() +( + const std::string& opts, + const std::string& desc, + const std::shared_ptr<const Value>& value, + std::string arg_help +) +{ + std::string short_sw, long_sw; + std::tie(short_sw, long_sw) = values::parser_tool::SplitSwitchDef(opts); + + if (!short_sw.length() && !long_sw.length()) + { + throw_or_mimic<invalid_option_format_error>(opts); + } + else if (long_sw.length() == 1 && short_sw.length()) + { + throw_or_mimic<invalid_option_format_error>(opts); + } + + auto option_names = [] + ( + const std::string &short_, + const std::string &long_ + ) + { + if (long_.length() == 1) + { + return std::make_tuple(long_, short_); + } + return std::make_tuple(short_, long_); + }(short_sw, long_sw); + + m_options.add_option + ( + m_group, + std::get<0>(option_names), + std::get<1>(option_names), + desc, + value, + std::move(arg_help) + ); + + return *this; +} + +inline +void +OptionParser::parse_default(const std::shared_ptr<OptionDetails>& details) +{ + // TODO: remove the duplicate code here + auto& store = m_parsed[details->hash()]; + store.parse_default(details); +} + +inline +void +OptionParser::parse_no_value(const std::shared_ptr<OptionDetails>& details) +{ + auto& store = m_parsed[details->hash()]; + store.parse_no_value(details); +} + +inline +void +OptionParser::parse_option +( + const std::shared_ptr<OptionDetails>& value, + const std::string& /*name*/, + const std::string& arg +) +{ + auto hash = value->hash(); + auto& result = m_parsed[hash]; + result.parse(value, arg); + + m_sequential.emplace_back(value->long_name(), arg); +} + +inline +void +OptionParser::checked_parse_arg +( + int argc, + const char* const* argv, + int& current, + const std::shared_ptr<OptionDetails>& value, + const std::string& name +) +{ + if (current + 1 >= argc) + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + throw_or_mimic<missing_argument_exception>(name); + } + } + else + { + if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else + { + parse_option(value, name, argv[current + 1]); + ++current; + } + } +} + +inline +void +OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg) +{ + parse_option(iter->second, option, arg); +} + +inline +bool +OptionParser::consume_positional(const std::string& a, PositionalListIterator& next) +{ + while (next != m_positional.end()) + { + auto iter = m_options.find(*next); + if (iter != m_options.end()) + { + if (!iter->second->value().is_container()) + { + auto& result = m_parsed[iter->second->hash()]; + if (result.count() == 0) + { + add_to_option(iter, *next, a); + ++next; + return true; + } + ++next; + continue; + } + add_to_option(iter, *next, a); + return true; + } + throw_or_mimic<option_not_exists_exception>(*next); + } + + return false; +} + +inline +void +Options::parse_positional(std::string option) +{ + parse_positional(std::vector<std::string>{std::move(option)}); +} + +inline +void +Options::parse_positional(std::vector<std::string> options) +{ + m_positional = std::move(options); + + m_positional_set.insert(m_positional.begin(), m_positional.end()); +} + +inline +void +Options::parse_positional(std::initializer_list<std::string> options) +{ + parse_positional(std::vector<std::string>(options)); +} + +inline +ParseResult +Options::parse(int argc, const char* const* argv) +{ + OptionParser parser(*m_options, m_positional, m_allow_unrecognised); + + return parser.parse(argc, argv); +} + +inline ParseResult +OptionParser::parse(int argc, const char* const* argv) +{ + int current = 1; + bool consume_remaining = false; + auto next_positional = m_positional.begin(); + + std::vector<std::string> unmatched; + + while (current != argc) + { + if (strcmp(argv[current], "--") == 0) + { + consume_remaining = true; + ++current; + break; + } + bool matched = false; + values::parser_tool::ArguDesc argu_desc = + values::parser_tool::ParseArgument(argv[current], matched); + + if (!matched) + { + //not a flag + + // but if it starts with a `-`, then it's an error + if (argv[current][0] == '-' && argv[current][1] != '\0') { + if (!m_allow_unrecognised) { + throw_or_mimic<option_syntax_exception>(argv[current]); + } + } + + //if true is returned here then it was consumed, otherwise it is + //ignored + if (consume_positional(argv[current], next_positional)) + { + } + else + { + unmatched.emplace_back(argv[current]); + } + //if we return from here then it was parsed successfully, so continue + } + else + { + //short or long option? + if (argu_desc.grouping) + { + const std::string& s = argu_desc.arg_name; + + for (std::size_t i = 0; i != s.size(); ++i) + { + std::string name(1, s[i]); + auto iter = m_options.find(name); + + if (iter == m_options.end()) + { + if (m_allow_unrecognised) + { + continue; + } + //error + throw_or_mimic<option_not_exists_exception>(name); + } + + auto value = iter->second; + + if (i + 1 == s.size()) + { + //it must be the last argument + checked_parse_arg(argc, argv, current, value, name); + } + else if (value->value().has_implicit()) + { + parse_option(value, name, value->value().get_implicit_value()); + } + else if (i + 1 < s.size()) + { + std::string arg_value = s.substr(i + 1); + parse_option(value, name, arg_value); + break; + } + else + { + //error + throw_or_mimic<option_requires_argument_exception>(name); + } + } + } + else if (argu_desc.arg_name.length() != 0) + { + const std::string& name = argu_desc.arg_name; + + auto iter = m_options.find(name); + + if (iter == m_options.end()) + { + if (m_allow_unrecognised) + { + // keep unrecognised options in argument list, skip to next argument + unmatched.emplace_back(argv[current]); + ++current; + continue; + } + //error + throw_or_mimic<option_not_exists_exception>(name); + } + + auto opt = iter->second; + + //equals provided for long option? + if (argu_desc.set_value) + { + //parse the option given + + parse_option(opt, name, argu_desc.value); + } + else + { + //parse the next argument + checked_parse_arg(argc, argv, current, opt, name); + } + } + + } + + ++current; + } + + for (auto& opt : m_options) + { + auto& detail = opt.second; + const auto& value = detail->value(); + + auto& store = m_parsed[detail->hash()]; + + if (value.has_default()) { + if (!store.count() && !store.has_default()) { + parse_default(detail); + } + } + else { + parse_no_value(detail); + } + } + + if (consume_remaining) + { + while (current < argc) + { + if (!consume_positional(argv[current], next_positional)) { + break; + } + ++current; + } + + //adjust argv for any that couldn't be swallowed + while (current != argc) { + unmatched.emplace_back(argv[current]); + ++current; + } + } + + finalise_aliases(); + + ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched)); + return parsed; +} + +inline +void +OptionParser::finalise_aliases() +{ + for (auto& option: m_options) + { + auto& detail = *option.second; + auto hash = detail.hash(); + m_keys[detail.short_name()] = hash; + m_keys[detail.long_name()] = hash; + + m_parsed.emplace(hash, OptionValue()); + } +} + +inline +void +Options::add_option +( + const std::string& group, + const Option& option +) +{ + add_options(group, {option}); +} + +inline +void +Options::add_option +( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + const std::shared_ptr<const Value>& value, + std::string arg_help +) +{ + auto stringDesc = toLocalString(std::move(desc)); + auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value); + + if (!s.empty()) + { + add_one_option(s, option); + } + + if (!l.empty()) + { + add_one_option(l, option); + } + + m_option_list.push_front(*option.get()); + auto iter = m_option_list.begin(); + m_option_map[s] = iter; + m_option_map[l] = iter; + + //add the help details + auto& options = m_help[group]; + + options.options.emplace_back(HelpOptionDetails{s, l, stringDesc, + value->has_default(), value->get_default_value(), + value->has_implicit(), value->get_implicit_value(), + std::move(arg_help), + value->is_container(), + value->is_boolean()}); +} + +inline +void +Options::add_one_option +( + const std::string& option, + const std::shared_ptr<OptionDetails>& details +) +{ + auto in = m_options->emplace(option, details); + + if (!in.second) + { + throw_or_mimic<option_exists_error>(option); + } +} + +inline +String +Options::help_one_group(const std::string& g) const +{ + using OptionHelp = std::vector<std::pair<String, String>>; + + auto group = m_help.find(g); + if (group == m_help.end()) + { + return ""; + } + + OptionHelp format; + + size_t longest = 0; + + String result; + + if (!g.empty()) + { + result += toLocalString(" " + g + " options:\n"); + } + + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto s = format_option(o); + longest = (std::max)(longest, stringLength(s)); + format.push_back(std::make_pair(s, String())); + } + longest = (std::min)(longest, OPTION_LONGEST); + + //widest allowed description -- min 10 chars for helptext/line + size_t allowed = 10; + if (m_width > allowed + longest + OPTION_DESC_GAP) + { + allowed = m_width - longest - OPTION_DESC_GAP; + } + + auto fiter = format.begin(); + for (const auto& o : group->second.options) + { + if (m_positional_set.find(o.l) != m_positional_set.end() && + !m_show_positional) + { + continue; + } + + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion); + + result += fiter->first; + if (stringLength(fiter->first) > longest) + { + result += '\n'; + result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' ')); + } + else + { + result += toLocalString(std::string(longest + OPTION_DESC_GAP - + stringLength(fiter->first), + ' ')); + } + result += d; + result += '\n'; + + ++fiter; + } + + return result; +} + +inline +void +Options::generate_group_help +( + String& result, + const std::vector<std::string>& print_groups +) const +{ + for (size_t i = 0; i != print_groups.size(); ++i) + { + const String& group_help_text = help_one_group(print_groups[i]); + if (empty(group_help_text)) + { + continue; + } + result += group_help_text; + if (i < print_groups.size() - 1) + { + result += '\n'; + } + } +} + +inline +void +Options::generate_all_groups_help(String& result) const +{ + std::vector<std::string> all_groups; + + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(all_groups), + [] (const std::map<std::string, HelpGroupDetails>::value_type& group) + { + return group.first; + } + ); + + generate_group_help(result, all_groups); +} + +inline +std::string +Options::help(const std::vector<std::string>& help_groups) const +{ + String result = m_help_string + "\nUsage:\n " + + toLocalString(m_program) + " " + toLocalString(m_custom_help); + + if (!m_positional.empty() && !m_positional_help.empty()) { + result += " " + toLocalString(m_positional_help); + } + + result += "\n\n"; + + if (help_groups.empty()) + { + generate_all_groups_help(result); + } + else + { + generate_group_help(result, help_groups); + } + + return toUTF8String(result); +} + +inline +std::vector<std::string> +Options::groups() const +{ + std::vector<std::string> g; + + std::transform( + m_help.begin(), + m_help.end(), + std::back_inserter(g), + [] (const std::map<std::string, HelpGroupDetails>::value_type& pair) + { + return pair.first; + } + ); + + return g; +} + +inline +const HelpGroupDetails& +Options::group_help(const std::string& group) const +{ + return m_help.at(group); +} + +} // namespace cxxopts + +#endif //CXXOPTS_HPP_INCLUDED diff --git a/include/reactor-sdk/impl/Connection.hh b/include/reactor-sdk/impl/Connection.hh new file mode 100644 index 00000000..a238da8a --- /dev/null +++ b/include/reactor-sdk/impl/Connection.hh @@ -0,0 +1,108 @@ +#pragma once + +#include "reactor-cpp/port.hh" + +/* +Input +MultiportInput +ReactorBank_Input +ReactorBank_MultiportInput + +Output +MultiportOutput +ReactorBank_Output +ReactorBank_MultiportOutput + +Input -> Input +Input -> MultiportInput * +Input -> ReactorBank_Input * +Input -> ReactorBank_MultiportInput * + +Output -> Input +Output -> MultiportInput * +Output -> ReactorBank_Input * +Output -> ReactorBank_MultiportInput * +Output -> Output +Output -> MultiportOutput + +MultiportInput -> Input +MultiportInput -> MultiportInput +MultiportInput -> ReactorBank_Input +MultiportInput -> ReactorBank_MultiportInput + +MultiportOutput -> Input +MultiportOutput -> MultiportInput +MultiportOutput -> ReactorBank_Input +MultiportOutput -> ReactorBank_MultiportInput +MultiportOutput -> Output +MultiportOutput -> MultiportOutput + +ReactorBank_Output -> Input +ReactorBank_Output -> MultiportInput +ReactorBank_Output -> ReactorBank_Input +ReactorBank_Output -> ReactorBank_MultiportInput +ReactorBank_Output -> Output +ReactorBank_Output -> MultiportOutput + +ReactorBank_MultiportOutput -> Input +ReactorBank_MultiportOutput -> MultiportInput +ReactorBank_MultiportOutput -> ReactorBank_Input +ReactorBank_MultiportOutput -> ReactorBank_MultiportInput +ReactorBank_MultiportOutput -> Output +ReactorBank_MultiportOutput -> MultiportOutput + +*/ + +template <typename T> +void display_(std::set<reactor::Port<T>*> &left_ports, std::set<reactor::Port<T>*> &right_ports) { + reactor::log::Warn() << "Left Ports:"; + for (auto *left_port : left_ports) { + reactor::log::Warn() << "\t" << left_port->fqn(); + } + reactor::log::Warn() << "Right Ports:"; + for (auto *right_port : right_ports) { + reactor::log::Warn() << "\t" << right_port->fqn(); + } +} + +template <typename T> +void connect_( std::set<reactor::Port<T>*> &left_ports, std::set<reactor::Port<T>*> &right_ports, + reactor::ConnectionProperties &&property) { + if (left_ports.size() < right_ports.size()) { + reactor::log::Warn() << "There are more right ports (" << right_ports.size() << ") than left ports (" << left_ports.size() << ")"; + display_ (left_ports, right_ports); + } else if (left_ports.size() > right_ports.size()) { + reactor::log::Warn() << "There are more left ports (" << left_ports.size() << ") than right ports (" << right_ports.size() << ")"; + display_ (left_ports, right_ports); + } + + auto right_port_itr = right_ports.begin(); + for (auto *left_port : left_ports) { + if (right_port_itr == right_ports.end()) { + break; + } + left_port->environment()->draw_connection(left_port, (*right_port_itr), reactor::ConnectionProperties{}); + ++right_port_itr; + } +} + +template <typename T> +void connect_fanout_(std::set<reactor::Port<T>*> &left_ports, std::set<reactor::Port<T>*> &right_ports, + reactor::ConnectionProperties &&property) { + assert (left_ports.size() == 1); + if (left_ports.size() < right_ports.size()) { + reactor::log::Warn() << "There are more right ports (" << right_ports.size() << ") than left ports (" << left_ports.size() << ")"; + display_ (left_ports, right_ports); + reactor::log::Warn() << "Fanning out left port to all right ports"; + } else if (left_ports.size() > right_ports.size()) { + reactor::log::Warn() << "There are more left ports (" << left_ports.size() << ") than right ports (" << right_ports.size() << ")"; + display_ (left_ports, right_ports); + reactor::log::Warn() << "Fanning out left port to all right ports"; + } + + auto left_port_itr = left_ports.begin(); + for (auto *right_port : right_ports) { + (*left_port_itr)->environment()->draw_connection((*left_port_itr), right_port, reactor::ConnectionProperties{}); + } + +} \ No newline at end of file diff --git a/include/reactor-sdk/impl/InputMultiport_wiring_impl.hh b/include/reactor-sdk/impl/InputMultiport_wiring_impl.hh new file mode 100644 index 00000000..b5095368 --- /dev/null +++ b/include/reactor-sdk/impl/InputMultiport_wiring_impl.hh @@ -0,0 +1,38 @@ +#pragma once + +#include "Connection.hh" + +namespace sdk +{ +template <typename T> +void MultiportInput<T>::connect(Input<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = false; + for (auto& output_port : *this) { + result = left_ports.insert(&output_port).second; + reactor_assert(result); + } + result = right_ports.insert(&input).second; + reactor_assert(result); + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename T> +void MultiportInput<T>::connect(MultiportInput<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = false; + for (auto& left_port : *this) { + result = left_ports.insert(&left_port).second; + reactor_assert(result); + } + + for (auto& right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/InputPort_wiring_impl.hh b/include/reactor-sdk/impl/InputPort_wiring_impl.hh new file mode 100644 index 00000000..18e2dd5f --- /dev/null +++ b/include/reactor-sdk/impl/InputPort_wiring_impl.hh @@ -0,0 +1,80 @@ +#pragma once + +#include "Connection.hh" + +namespace sdk +{ + +template <typename T> +void Input<T>::connect(Input<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + result = right_ports.insert(&input).second; + reactor_assert(result); + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename T> +void Input<T>::connect(MultiportInput<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + + for (auto& right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename T> +void Input<T>::connect_fanout(MultiportInput<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_fanout_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename T> +template <typename ReactorType> +void Input<T>::connect(ReactorBankInputPortOffset<ReactorType, T> &&other_bank_ports) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &p_reactor : other_bank_ports) { + auto *reactor = p_reactor.get(); + char* reactor_base = reinterpret_cast<char*>(reactor); + Input<T>* port = reinterpret_cast<Input<T>*>(reactor_base + other_bank_ports.get_offset()); + result = right_ports.insert(port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename T> +template <typename ReactorType> +void Input<T>::connect_fanout(ReactorBankInputPortOffset<ReactorType, T> &&other_bank_ports) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &p_reactor : other_bank_ports) { + auto *reactor = p_reactor.get(); + char* reactor_base = reinterpret_cast<char*>(reactor); + Input<T>* port = reinterpret_cast<Input<T>*>(reactor_base + other_bank_ports.get_offset()); + result = right_ports.insert(port).second; + reactor_assert(result); + } + connect_fanout_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/OutputMultiport_wiring_impl.hh b/include/reactor-sdk/impl/OutputMultiport_wiring_impl.hh new file mode 100644 index 00000000..45ad89e8 --- /dev/null +++ b/include/reactor-sdk/impl/OutputMultiport_wiring_impl.hh @@ -0,0 +1,132 @@ +#pragma once + +#include "Connection.hh" +namespace sdk +{ +template <typename T> +void MultiportOutput<T>::connect(Input<T>& input) { + if (n_inputs > 1) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + output_port.environment()->draw_connection(output_port, input, reactor::ConnectionProperties{}); + break; + } +} + +template <typename T> +void MultiportOutput<T>::connect(Output<T>& input) { + if (n_inputs > 1) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + output_port.environment()->draw_connection(output_port, input, reactor::ConnectionProperties{}); + break; + } +} + +template <typename T> +void MultiportOutput<T>::connect(MultiportInput<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = false; + for (auto& left_port : *this) { + result = left_ports.insert(&left_port).second; + reactor_assert(result); + } + + for (auto& right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename T> +void MultiportOutput<T>::connect(MultiportOutput<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = false; + for (auto& left_port : *this) { + result = left_ports.insert(&left_port).second; + reactor_assert(result); + } + + for (auto& right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename T> +template <typename ReactorType> +void MultiportOutput<T>::connect(std::vector<std::unique_ptr<ReactorType>>* reactors, Input<T> ReactorType::*member) { + auto reactor_itr = reactors->begin(); + + if (n_inputs < reactors->size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (n_inputs > reactors->size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + auto *reactor = (*reactor_itr).get(); + output_port.environment()->draw_connection(output_port, reactor->*member, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors->end()) + { + break; + } + } +} + +template <typename T> +template <typename OtherReactorType> +void MultiportOutput<T>::connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = other_bank_ports.begin(); + + if (n_inputs < other_bank_ports.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (n_inputs > other_bank_ports.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + auto *reactor = (*reactor_itr).get(); + output_port.environment()->draw_connection(output_port, reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == other_bank_ports.end()) + { + break; + } + } +} + +template <typename T> +template <typename OtherReactorType> +void MultiportOutput<T>::connect(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result; + + for (auto& left_port : *this) { + result = left_ports.insert(&left_port).second; + reactor_assert(result); + } + + for (auto &reactor : other_bank_ports) { + char* reactor_base = reinterpret_cast<char*>(reactor.get()); + Input<T>* port = reinterpret_cast<Input<T>*>(reactor_base + other_bank_ports.get_offset()); + result = right_ports.insert(port).second; + reactor_assert(result); + } + + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/OutputPort_wiring_impl.hh b/include/reactor-sdk/impl/OutputPort_wiring_impl.hh new file mode 100644 index 00000000..c929cc28 --- /dev/null +++ b/include/reactor-sdk/impl/OutputPort_wiring_impl.hh @@ -0,0 +1,147 @@ +#pragma once + +#include "Connection.hh" + +namespace sdk +{ + +template <typename T> +void Output<T>::connect(Input<T>& input) { + this->environment()->draw_connection(*this, input, reactor::ConnectionProperties{}); +} + +template <typename T> +void Output<T>::connect(Output<T>& input) { + this->environment()->draw_connection(*this, input, reactor::ConnectionProperties{}); +} + +template <typename T> +void Output<T>::connect(MultiportInput<T>& input) { + if (is_accumulated) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = false; + result = left_ports.insert(this).second; + reactor_assert(result); + for (auto *l_port : accumulated) { + result = left_ports.insert(l_port).second; + reactor_assert(result); + } + + for (auto& right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); + is_accumulated = false; + accumulated.clear(); + } else { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); + } +} + +template <typename T> +void Output<T>::connect_fanout(MultiportInput<T>& input) { + if (is_accumulated) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = false; + result = left_ports.insert(this).second; + reactor_assert(result); + for (auto *l_port : accumulated) { + result = left_ports.insert(l_port).second; + reactor_assert(result); + } + + for (auto& right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); + is_accumulated = false; + accumulated.clear(); + } else { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_fanout_ (left_ports, right_ports, reactor::ConnectionProperties{}); + } +} + +template <typename T> +template <typename ReactorType> +void Output<T>::connect(std::vector<std::unique_ptr<ReactorType>>* reactors, Input<T> ReactorType::*member) { + + if (1 < reactors->size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } + for (auto &p_reactor : *reactors) { + auto *reactor = p_reactor.get(); + this->environment()->draw_connection(*this, reactor->*member, reactor::ConnectionProperties{}); + } +} + +template <typename T> +template <typename ReactorType> +void Output<T>::connect(ReactorBankInputPort<ReactorType, T> &&other_bank_ports) { + + if (1 < other_bank_ports.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } + for (auto &p_reactor : other_bank_ports) { + auto *reactor = p_reactor.get(); + this->environment()->draw_connection(*this, reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + break; + } +} + +template <typename T> +template <typename ReactorType> +void Output<T>::connect(ReactorBankInputPortOffset<ReactorType, T> &&other_bank_ports) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &p_reactor : other_bank_ports) { + auto *reactor = p_reactor.get(); + char* reactor_base = reinterpret_cast<char*>(reactor); + Input<T>* port = reinterpret_cast<Input<T>*>(reactor_base + other_bank_ports.get_offset()); + result = right_ports.insert(port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename T> +template <typename ReactorType> +void Output<T>::connect_fanout(ReactorBankInputPortOffset<ReactorType, T> &&other_bank_ports) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &p_reactor : other_bank_ports) { + auto *reactor = p_reactor.get(); + char* reactor_base = reinterpret_cast<char*>(reactor); + Input<T>* port = reinterpret_cast<Input<T>*>(reactor_base + other_bank_ports.get_offset()); + result = right_ports.insert(port).second; + reactor_assert(result); + } + connect_fanout_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/ReactorBankOutputMultiport_wiring_impl.hh b/include/reactor-sdk/impl/ReactorBankOutputMultiport_wiring_impl.hh new file mode 100644 index 00000000..1b2ba8e0 --- /dev/null +++ b/include/reactor-sdk/impl/ReactorBankOutputMultiport_wiring_impl.hh @@ -0,0 +1,354 @@ +#pragma once + +namespace sdk +{ +template <typename ReactorType, typename T> +void ReactorBankOutputMultiPort<ReactorType, T>::connect(MultiportInput<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = false; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + for (auto &l_port : left_reactor->*member) { + result = left_ports.insert(&l_port).second; + reactor_assert(result); + } + } + + for (auto &right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputMultiPort<ReactorType, T>::connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = other_bank_ports.size(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + left_ports += (left_reactor->*member).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + + for (auto &l_port : left_reactor->*member) { + auto *right_reactor = (*right_reactor_itr).get(); + l_port.environment()->draw_connection(l_port, right_reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++right_reactor_itr; + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputMultiPort<ReactorType, T>::connect(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + left_ports += (left_reactor->*member).get_nports(); + } + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + right_ports += (right_reactor->*(other_bank_ports.get_member())).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + auto right_port_itr = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).begin(); + auto right_port_itr_end = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).end(); + size_t right_port_count = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_port_count == right_ports) { + break; + } + + for (auto &l_port : left_reactor->*member) { + l_port.environment()->draw_connection(l_port, *right_port_itr, reactor::ConnectionProperties{}); + ++right_port_count; + if (right_port_count == right_ports) { + break; + } + ++right_port_itr; + if (right_port_itr == right_port_itr_end) { + ++right_reactor_itr; + right_port_itr = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).begin(); + right_port_itr_end = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).end(); + } + } + } +} + + +template <typename ReactorType, typename T> +void ReactorBankOutputMultiPortOffset<ReactorType, T>::connect(MultiportInput<T>& input) { + std::set<reactor::Port<T>*> left_ports; + std::set<reactor::Port<T>*> right_ports; + bool result = false; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + result = left_ports.insert(&l_port).second; + reactor_assert(result); + } + } + + for (auto &right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputMultiPortOffset<ReactorType, T>::connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = other_bank_ports.size(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + left_ports += (*l_ports).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + auto *right_reactor = (*right_reactor_itr).get(); + l_port.environment()->draw_connection(l_port, right_reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++right_reactor_itr; + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputMultiPortOffset<ReactorType, T>::connect(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + left_ports += (*l_ports).get_nports(); + } + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + right_ports += (right_reactor->*(other_bank_ports.get_member())).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + auto right_port_itr = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).begin(); + auto right_port_itr_end = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).end(); + size_t right_port_count = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_port_count == right_ports) { + break; + } + + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + l_port.environment()->draw_connection(l_port, *right_port_itr, reactor::ConnectionProperties{}); + ++right_port_count; + if (right_port_count == right_ports) { + break; + } + ++right_port_itr; + if (right_port_itr == right_port_itr_end) { + ++right_reactor_itr; + right_port_itr = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).begin(); + right_port_itr_end = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).end(); + } + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputMultiPortOffset<ReactorType, T>::connect(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = other_bank_ports.size(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + left_ports += (*l_ports).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + auto *right_reactor = (*right_reactor_itr).get(); + char* r_reactor_base = reinterpret_cast<char*>(right_reactor); + Input<T>* r_port= reinterpret_cast<Input<T>*>(r_reactor_base + other_bank_ports.get_offset()); + l_port.environment()->draw_connection(l_port, *r_port, reactor::ConnectionProperties{}); + ++right_reactor_itr; + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputMultiPortOffset<ReactorType, T>::connect(ReactorBankInputMultiPortOffset<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + left_ports += (*l_ports).get_nports(); + } + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + char* r_reactor_base = reinterpret_cast<char*>(right_reactor); + MultiportInput<T>* r_ports = reinterpret_cast<MultiportInput<T>*>(r_reactor_base + other_bank_ports.get_offset()); + right_ports += (*r_ports).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + char* r_reactor_base = reinterpret_cast<char*>(*right_reactor_itr); + MultiportInput<T>* r_ports = reinterpret_cast<MultiportInput<T>*>(r_reactor_base + other_bank_ports.get_offset()); + auto right_port_itr = (*r_ports).begin(); + auto right_port_itr_end = (*r_ports).end(); + size_t right_port_count = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_port_count == right_ports) { + break; + } + + char* l_reactor_base = reinterpret_cast<char*>(left_reactor); + MultiportOutput<T>* l_ports= reinterpret_cast<MultiportOutput<T>*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + l_port.environment()->draw_connection(l_port, *right_port_itr, reactor::ConnectionProperties{}); + ++right_port_count; + if (right_port_count == right_ports) { + break; + } + ++right_port_itr; + if (right_port_itr == right_port_itr_end) { + ++right_reactor_itr; + r_reactor_base = reinterpret_cast<char*>(*right_reactor_itr); + r_ports = reinterpret_cast<MultiportInput<T>*>(r_reactor_base + other_bank_ports.get_offset()); + right_port_itr = (*r_ports).begin(); + right_port_itr_end = (*r_ports).end(); + } + } + } +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/ReactorBankOutputPort_wiring_impl.hh b/include/reactor-sdk/impl/ReactorBankOutputPort_wiring_impl.hh new file mode 100644 index 00000000..33d4779d --- /dev/null +++ b/include/reactor-sdk/impl/ReactorBankOutputPort_wiring_impl.hh @@ -0,0 +1,313 @@ +#pragma once + +namespace sdk +{ + +template <typename ReactorType, typename T> +void ReactorBankOutputPort<ReactorType, T>::connect(Input<T>& input) { + auto reactor_itr = reactors.begin(); + + if (1 < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto *reactor = (*reactor_itr).get(); + input.environment()->draw_connection(reactor->*member, input, reactor::ConnectionProperties{}); +} + +template <typename ReactorType, typename T> +void ReactorBankOutputPort<ReactorType, T>::connect(MultiportInput<T>& input) { + auto reactor_itr = reactors.begin(); + + if (input.get_nports() > reactors.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (input.get_nports() < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& input_port : input) { + auto *reactor = (*reactor_itr).get(); + input_port.environment()->draw_connection(reactor->*member, input_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputPort<ReactorType, T>::connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = other_bank_ports.size(); + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *reactor = (*reactor_itr).get(); + auto *right_reactor = p_right_reactor.get(); + (reactor->*member).environment()->draw_connection(reactor->*member, right_reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputPort<ReactorType, T>::connect(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = 0; + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + right_ports += (right_reactor->*(other_bank_ports.get_member())).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + if (reactor_itr == reactors.end()) + { + break; + } + + for (auto& right_port : right_reactor->*(other_bank_ports.get_member())) { + auto *reactor = (*reactor_itr).get(); + (reactor->*member).environment()->draw_connection(reactor->*member, right_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } + } +} + + +// ReactorBankOutputPortOffset +template <typename ReactorType, typename T> +void ReactorBankOutputPortOffset<ReactorType, T>::connect(Input<T>& input) { + auto reactor_itr = reactors.begin(); + + if (1 < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto *reactor = (*reactor_itr).get(); + char* reactor_base = reinterpret_cast<char*>(reactor); + Output<T>* port = reinterpret_cast<Output<T>*>(reactor_base + offset); + input.environment()->draw_connection(*port, input, reactor::ConnectionProperties{}); +} + +template <typename ReactorType, typename T> +void ReactorBankOutputPortOffset<ReactorType, T>::connect(MultiportInput<T>& input) { + auto reactor_itr = reactors.begin(); + + if (input.get_nports() > reactors.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (input.get_nports() < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& input_port : input) { + auto *reactor = (*reactor_itr).get(); + char* reactor_base = reinterpret_cast<char*>(reactor); + Output<T>* port = reinterpret_cast<Output<T>*>(reactor_base + offset); + input_port.environment()->draw_connection(*port, input_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template <typename ReactorType, typename T> +void ReactorBankOutputPortOffset<ReactorType, T>::connect_fanout(MultiportInput<T>& input) { + auto reactor_itr = reactors.begin(); + + if (input.get_nports() > reactors.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Fanning Out!"; + } else if (input.get_nports() < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& input_port : input) { + auto *reactor = (*reactor_itr).get(); + char* reactor_base = reinterpret_cast<char*>(reactor); + Output<T>* port = reinterpret_cast<Output<T>*>(reactor_base + offset); + input_port.environment()->draw_connection(*port, input_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + reactor_itr = reactors.begin(); + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputPortOffset<ReactorType, T>::connect(ReactorBankInputPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = other_bank_ports.size(); + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *reactor = (*reactor_itr).get(); + char* l_reactor_base = reinterpret_cast<char*>(reactor); + Output<T>* l_port = reinterpret_cast<Output<T>*>(l_reactor_base + offset); + auto *right_reactor = p_right_reactor.get(); + (*l_port).environment()->draw_connection(*l_port, right_reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputPortOffset<ReactorType, T>::connect(ReactorBankInputMultiPort<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = 0; + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + right_ports += (right_reactor->*(other_bank_ports.get_member())).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + if (reactor_itr == reactors.end()) + { + break; + } + + for (auto& right_port : right_reactor->*(other_bank_ports.get_member())) { + auto *reactor = (*reactor_itr).get(); + char* l_reactor_base = reinterpret_cast<char*>(reactor); + Output<T>* l_port = reinterpret_cast<Output<T>*>(l_reactor_base + offset); + (*l_port).environment()->draw_connection(*l_port, right_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputPortOffset<ReactorType, T>::connect(ReactorBankInputPortOffset<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = other_bank_ports.size(); + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *reactor = (*reactor_itr).get(); + char* l_reactor_base = reinterpret_cast<char*>(reactor); + Output<T>* l_port = reinterpret_cast<Output<T>*>(l_reactor_base + offset); + auto *right_reactor = p_right_reactor.get(); + char* r_reactor_base = reinterpret_cast<char*>(right_reactor); + Input<T>* r_port = reinterpret_cast<Input<T>*>(r_reactor_base + other_bank_ports.get_offset()); + (*l_port).environment()->draw_connection(*l_port, *r_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template <typename ReactorType, typename T> +template <typename OtherReactorType> +void ReactorBankOutputPortOffset<ReactorType, T>::connect(ReactorBankInputMultiPortOffset<OtherReactorType, T> &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = 0; + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + char* r_reactor_base = reinterpret_cast<char*>(right_reactor); + MultiportInput<T>* r_port = reinterpret_cast<MultiportInput<T>*>(r_reactor_base + other_bank_ports.get_offset()); + right_ports += (*r_port).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + if (reactor_itr == reactors.end()) + { + break; + } + + char* r_reactor_base = reinterpret_cast<char*>(right_reactor); + MultiportInput<T>* r_port = reinterpret_cast<MultiportInput<T>*>(r_reactor_base + other_bank_ports.get_offset()); + for (auto& right_port : *r_port) { + auto *reactor = (*reactor_itr).get(); + char* l_reactor_base = reinterpret_cast<char*>(reactor); + Output<T>* l_port = reinterpret_cast<Output<T>*>(l_reactor_base + offset); + (*l_port).environment()->draw_connection(*l_port, right_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } + } +} + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/reactor-sdk.hh b/include/reactor-sdk/reactor-sdk.hh new file mode 100644 index 00000000..fa19129b --- /dev/null +++ b/include/reactor-sdk/reactor-sdk.hh @@ -0,0 +1,17 @@ +#pragma once + +using namespace std; + +#include "reactor-cpp/reactor-cpp.hh" +#include "time_parser.hh" +#include "Misc.hh" +#include "ReactorBank.hh" +#include "MultiportInput.hh" +#include "MultiportOutput.hh" +#include "OutputPort.hh" +#include "InputPort.hh" +#include "Environment.hh" +#include "Reaction.hh" +#include "Reactor.hh" +#include "ConfigParameters.hh" +#include "SystemParameters.hh" \ No newline at end of file diff --git a/include/reactor-sdk/time_parser.hh b/include/reactor-sdk/time_parser.hh new file mode 100644 index 00000000..dfe872fe --- /dev/null +++ b/include/reactor-sdk/time_parser.hh @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020, TU Dresden. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" +#include <sstream> + +std::stringstream& operator>>(std::stringstream& in, reactor::Duration& dur); +#include "cxxopts.hpp" + +#include <algorithm> +#include <fstream> +#include <iostream> +#include <regex> +#include <string> + +inline bool iequals(const std::string& a, const std::string& b) { + return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); }); +} + +class argument_incorrect_type_with_reason : public cxxopts::OptionParseException { +public: + explicit argument_incorrect_type_with_reason(const std::string& arg, const std::string& reason) + : cxxopts::OptionParseException("Argument " + cxxopts::LQUOTE + arg + cxxopts::RQUOTE + " failed to parse (" + + reason + ")") {} +}; + +std::string validate_time_string(const std::string& time); + +/** + * converts a reactor::Duration to a string with ns as unit + */ +inline std::string time_to_string(const reactor::Duration& dur) { + if (dur == reactor::Duration::max()) { + return "forever"; + } + + std::stringstream ss; + ss << dur.count() << " ns"; + return ss.str(); +} + +template <typename T> std::string any_to_string(const T val) { + std::stringstream ss; + ss << val; + return ss.str(); +} + +inline std::stringstream& operator>>(std::stringstream& in, reactor::Duration& dur) { + double value; + std::string unit; + + const std::string validation_msg = validate_time_string(in.str()); + if (!validation_msg.empty()) { + // throw cxxopts error + throw argument_incorrect_type_with_reason(in.str(), validation_msg); + } + + if (iequals(in.str(), "forever")) { + in >> unit; // parse the entire string + dur = reactor::Duration::max(); + return in; + } + + // try to read as double + in >> value; + + if (value == 0.0) { + dur = reactor::Duration::zero(); + if (!in.eof()) { + // parse whatever remains + in >> unit; + } + } else { + in >> unit; + if (unit == "nsec" || unit == "nsecs" || unit == "ns") { + std::chrono::duration<double, std::nano> tmp{value}; + dur = std::chrono::duration_cast<reactor::Duration>(tmp); + } else if (unit == "usec" || unit == "usecs" || unit == "us") { + std::chrono::duration<double, std::micro> tmp{value}; + dur = std::chrono::duration_cast<reactor::Duration>(tmp); + } else if (unit == "msec" || unit == "msecs" || unit == "ms") { + std::chrono::duration<double, std::milli> tmp{value}; + dur = std::chrono::duration_cast<reactor::Duration>(tmp); + } else if (unit == "sec" || unit == "secs" || unit == "second" || unit == "seconds" || unit == "s") { + std::chrono::duration<double, std::ratio<1, 1>> tmp{value}; + dur = std::chrono::duration_cast<reactor::Duration>(tmp); + } else if (unit == "min" || unit == "mins" || unit == "minute" || unit == "minutes" || unit == "m") { + std::chrono::duration<double, std::ratio<60, 1>> tmp{value}; + dur = std::chrono::duration_cast<reactor::Duration>(tmp); + } else if (unit == "hour" || unit == "hours" || unit == "h") { + std::chrono::duration<double, std::ratio<3600, 1>> tmp{value}; + dur = std::chrono::duration_cast<reactor::Duration>(tmp); + } else if (unit == "day" || unit == "days" || unit == "d") { + std::chrono::duration<double, std::ratio<24 * 3600, 1>> tmp{value}; + dur = std::chrono::duration_cast<reactor::Duration>(tmp); + } else if (unit == "week" || unit == "weeks" || unit == "w") { + std::chrono::duration<double, std::ratio<7 * 24 * 3600, 1>> tmp{value}; + dur = std::chrono::duration_cast<reactor::Duration>(tmp); + } else { + // mark as error + in.setstate(std::ifstream::failbit); + } + } + + return in; +} + +/** + * Tests for correct syntax in unit usage for time strings + **/ +inline std::string validate_time_string(const std::string& time) { + auto trimmed = std::regex_replace(time, std::regex("^ +| +$|( ) +"), "$1"); + if (trimmed.size() == 0) { + return "The empty string is not a valid time!"; + } else if (trimmed[0] == '-') { + return "Negative values are not a valid time!"; + } else if (iequals("forever", time)) { + return ""; // "forever" is a valid value + } else if (trimmed.find_first_not_of("0.") == std::string::npos) { + return ""; + } else { + auto pos = trimmed.find_first_not_of("0123456789. \n\r\t"); + if (pos == std::string::npos) { + return "No unit given!"; + } else { + auto unit = trimmed.substr(pos); + if (unit == "nsec" || unit == "nsecs" || unit == "ns" || unit == "usec" || unit == "usecs" || unit == "us" || + unit == "msec" || unit == "msecs" || unit == "ms" || unit == "sec" || unit == "secs" || unit == "second" || + unit == "seconds" || unit == "s" || unit == "min" || unit == "mins" || unit == "minute" || + unit == "minutes" || unit == "m" || unit == "hour" || unit == "hours" || unit == "h" || unit == "day" || + unit == "days" || unit == "d" || unit == "week" || unit == "weeks" || unit == "w") { + return ""; + } else { + std::stringstream ss; + ss << "Not a valid unit: " << unit; + return ss.str(); + } + } + } + return "Unexpected error!"; +} diff --git a/lib/environment.cc b/lib/environment.cc index 52c0b6bf..937afe3f 100644 --- a/lib/environment.cc +++ b/lib/environment.cc @@ -67,6 +67,24 @@ void Environment::optimize() { optimized_graph_ = graph_; } +void recursive_construct(Reactor* container) { + container->construct(); + for (auto* reactor : container->reactors()) { + recursive_construct(reactor); + } +} + +void Environment::construct() { + log::Debug() << "Start Contruction of reactors"; + for (auto* reactor : top_level_reactors_) { + recursive_construct(reactor); + } + + for (auto* env : contained_environments_) { + env->construct(); + } +} + void recursive_assemble(Reactor* container) { container->assemble(); for (auto* reactor : container->reactors()) { @@ -112,6 +130,8 @@ void Environment::assemble() { // NOLINT(readability-function-cognitive-complexi source_port->add_outward_binding(destination_port); log::Debug() << "from: " << source_port->fqn() << "(" << source_port << ")" << " --> to: " << destination_port->fqn() << "(" << destination_port << ")"; + reactor::validate(source_port != destination_port, + "Self wiring detected; from " + source_port->fqn() + " --> " + destination_port->fqn()); } } else { if (properties.type_ == ConnectionType::Enclaved || properties.type_ == ConnectionType::PhysicalEnclaved || @@ -221,6 +241,8 @@ void Environment::export_dependency_graph(const std::string& path) { std::ofstream dot; dot.open(path); + dependency_graph_and_indexes(); + // sort all reactions_ by their index std::map<unsigned int, std::vector<Reaction*>> reactions_by_index; for (auto* reaction : reactions_) { @@ -259,7 +281,7 @@ void Environment::export_dependency_graph(const std::string& path) { dot.close(); - log_.info() << "Reaction graph was written to " << path; + log_.debug() << "Reaction graph was written to " << path; } void Environment::calculate_indexes() { @@ -317,7 +339,7 @@ auto Environment::startup() -> std::thread { return startup(get_physical_time()); } -auto Environment::startup(const TimePoint& start_time) -> std::thread { +void Environment::dependency_graph_and_indexes() { validate(this->phase() == Phase::Assembly, "startup() may only be called during assembly phase!"); log::Debug() << "Building the Dependency-Graph"; @@ -327,6 +349,16 @@ auto Environment::startup(const TimePoint& start_time) -> std::thread { calculate_indexes(); + phase_ = Phase::Indexing; +} + +auto Environment::startup(const TimePoint& start_time) -> std::thread { + if (phase_ == Phase::Assembly) { + dependency_graph_and_indexes(); + } + + validate(this->phase() == Phase::Indexing, "startup() may only be called during Indexing phase!"); + log_.debug() << "Starting the execution"; phase_ = Phase::Startup; diff --git a/lib/reactor.cc b/lib/reactor.cc index f42488b0..ae9bdb57 100644 --- a/lib/reactor.cc +++ b/lib/reactor.cc @@ -47,7 +47,7 @@ void Reactor::register_output(BasePort* port) { reactor_assert(port != nullptr); reactor::validate(this->environment()->phase() == Phase::Construction, "Ports can only be registered during construction phase!"); - [[maybe_unused]] bool result = inputs_.insert(port).second; + [[maybe_unused]] bool result = outputs_.insert(port).second; reactor_assert(result); Statistics::increment_ports(); } @@ -55,8 +55,9 @@ void Reactor::register_output(BasePort* port) { void Reactor::register_reaction([[maybe_unused]] Reaction* reaction) { reactor_assert(reaction != nullptr); - validate(this->environment()->phase() == Phase::Construction, - "Reactions can only be registered during construction phase!"); + validate((this->environment()->phase() == Phase::Construction) || + (this->environment()->phase() == Phase::Assembly), + "Reactions can only be registered during construction or assembly phase!"); [[maybe_unused]] bool result = reactions_.insert(reaction).second; reactor_assert(result); Statistics::increment_reactions(); diff --git a/lib/reactor_element.cc b/lib/reactor_element.cc index 32db319f..b0faae83 100644 --- a/lib/reactor_element.cc +++ b/lib/reactor_element.cc @@ -25,7 +25,8 @@ ReactorElement::ReactorElement(const std::string& name, ReactorElement::Type typ this->environment_ = container->environment(); reactor_assert(this->environment_ != nullptr); validate(this->environment_->phase() == Phase::Construction || - (type == Type::Action && this->environment_->phase() == Phase::Assembly), + (type == Type::Action && this->environment_->phase() == Phase::Assembly) || + (type == Type::Reaction && this->environment_->phase() == Phase::Assembly), "Reactor elements can only be created during construction phase!"); // We need a reinterpret_cast here as the derived class is not yet created // when this constructor is executed. dynamic_cast only works for diff --git a/reactor-sdk/CMakeLists.txt b/reactor-sdk/CMakeLists.txt new file mode 100644 index 00000000..bf3f9021 --- /dev/null +++ b/reactor-sdk/CMakeLists.txt @@ -0,0 +1,20 @@ +set (ReactorSDK_LIB reactor-sdk) + +include(GNUInstallDirs) + +find_package(reactor-cpp PATHS ) + +set(REACTOR_CPP_SDK_INCLUDE "include") + +add_library(${ReactorSDK_LIB} SHARED Environment.cc Reactor.cc) + +target_link_libraries(${ReactorSDK_LIB} PUBLIC reactor-cpp) + +install(TARGETS ${ReactorSDK_LIB} EXPORT ${ReactorSDK_LIB}Config + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) + +install(EXPORT ${ReactorSDK_LIB}Config DESTINATION share/${ReactorSDK_LIB}/cmake) + +export(TARGETS ${ReactorSDK_LIB} FILE ${ReactorSDK_LIB}Config.cmake) \ No newline at end of file diff --git a/reactor-sdk/Environment.cc b/reactor-sdk/Environment.cc new file mode 100644 index 00000000..f0718b93 --- /dev/null +++ b/reactor-sdk/Environment.cc @@ -0,0 +1,127 @@ +#include <fstream> +#include <unordered_map> +#include <map> +#include <cstdlib> + +#include "reactor-sdk/Environment.hh" +#include "reactor-sdk/Reactor.hh" + +using namespace std; + +namespace sdk +{ + +std::map<std::string, std::string> type_convert = { + {"i", "int"}, + {"j", "uint32_t"}, + {"b", "bool"}, + {"f", "float"}, + {"d", "double"}, + {"c", "char"}, + {"s", "std::string"}, + {"ll", "long long"}, + {"ul", "unsigned long"}, + {"uc", "unsigned char"}, + {"ld", "long double"}, + {"uint", "unsigned int"} +}; + +Environment::Environment( ConfigParameterBase *cfg_param, unsigned int num_workers, bool fast_fwd_execution, + const reactor::Duration& timeout, bool _cfg_gen) + : reactor::Environment (num_workers, fast_fwd_execution, timeout), config_parameters(cfg_param), cfg_gen(_cfg_gen) { +} + + +void Environment::run() +{ + if (this->config_parameters) { + this->config_parameters->pull_config(); + // instance->config_parameters->display(); + } + + this->construct(); + this->assemble(); + + if (this->config_parameters) { + if (this->config_parameters->validate() != 0) { + reactor::log::Error() << "INVALID CONFIGURATION!"; + return; + } + } + + this->export_dependency_graph("graph.dot"); + int ret = std::system("dot -Tpng graph.dot -o graph.png"); + if (ret != 0) { + reactor::log::Error() << "Error: Failed to generate graph diagram from dot file"; + } + + if (cfg_gen) { + std::set<std::string> types; + std::map<std::string, std::string> homog_map_entries; + std::map<std::string, std::string> hetero_map_entries; + for (auto *reactor : top_tier_reactors) { + reactor->populate_params (types, homog_map_entries, hetero_map_entries); + } + std::string type_str = ""; + bool first = true; + for (auto &type : types) { + type_str += first ? type : (", " + type); + first = false; + } + std::cout << "TYPE_STR:" << type_str << std::endl; + + std::string homog_entry_str = ""; + first = true; + for (auto &entry : homog_map_entries) { + homog_entry_str += first ? ("\t\t" + entry.second) : (",\n\t\t" + entry.second); + first = false; + } + std::cout << "HOMOG_ENTRY_STR:" << homog_entry_str << std::endl; + + std::string hetero_entry_str = ""; + first = true; + for (auto &entry : hetero_map_entries) { + hetero_entry_str += first ? ("\t\t" + entry.second) : (",\n\t\t" + entry.second); + first = false; + } + std::cout << "HETERO_ENTRY_STR:" << hetero_entry_str << std::endl; + + std::string header_file_str = std::string("#pragma once\n") + + "#include <reactor-sdk/reactor-sdk.hh>\n" + + "#include <map>\n" + + "#include <variant>\n" + + "#include <string>\n\n" + + "using namespace sdk;\n\n" + + "struct UserParameters : public ConfigParameter<" + type_str + "> {\n" + + "\tConfigParameter<" + type_str + ">::ParametersMap homogeneous_config();\n" + + "\tConfigParameter<" + type_str + ">::ParametersMap heterogeneous_config();\n" + + "};\n" + + "extern UserParameters cfg_parameters;"; + + std::ofstream header("GeneratedConfig.hh"); + if (!header) { + cout << "ERROR: Failed to open header file\n"; + } else { + header << header_file_str; + } + + std::string source_file_str = std::string("#include \"GeneratedConfig.hh\"\n\n") + + "UserParameters cfg_parameters;\n\n" + + "ConfigParameter<" + type_str + ">::ParametersMap UserParameters::homogeneous_config() {\n" + + "\treturn {\n" + homog_entry_str + "\n\t};\n}\n\n" + + "ConfigParameter<" + type_str + ">::ParametersMap UserParameters::heterogeneous_config() {\n" + + "\treturn {\n" + hetero_entry_str + "\n\t};\n}"; + + std::ofstream source("GeneratedConfig.cc"); + if (!source) { + cout << "ERROR: Failed to open source file\n"; + } else { + source << source_file_str; + } + return; + } + auto thread = this->startup(); + thread.join(); +} + +} // namespace sdk \ No newline at end of file diff --git a/reactor-sdk/Reactor.cc b/reactor-sdk/Reactor.cc new file mode 100644 index 00000000..d2406cc8 --- /dev/null +++ b/reactor-sdk/Reactor.cc @@ -0,0 +1,75 @@ +#include <fstream> +#include <unordered_map> +#include <map> +#include "reactor-sdk/Reactor.hh" +#include "reactor-sdk/Environment.hh" + +using namespace std; + +namespace sdk +{ + +std::string Reactor::BankName(const std::string& name) { + std::string bank_name = name; + size_t index = bank_name.rfind("\r\n"); + if (index != std::string::npos) { + bank_name.replace(index, strlen("\r\n"), "_"); + } + return bank_name; +} + +std::string Reactor::HomogName(const std::string& name) { + std::string h_name = name; + size_t index = h_name.rfind("\r\n"); + if (index != std::string::npos) { + return h_name.substr(0, index); + } + return name; +} + +Reactor::Reactor(const std::string &name, Environment *env) + : reactor::Reactor(BankName(name), (reactor::Environment*)env), env(env) { + env->add_reactor(this); + homog_name = HomogName(name); +} + +Reactor::Reactor(const std::string &name, Reactor *container) + : reactor::Reactor(BankName(name), container), env(container->env), parent(container) { + container->add_child (this); + homog_name = container->homog_name + "." + HomogName(name); +} + +void Reactor::add_child(Reactor* reactor) { + [[maybe_unused]] bool result = child_reactors.insert(reactor).second; + reactor_assert(result); +} + +void Reactor::add_to_reaction_map (std::string &name, std::shared_ptr<ReactionBase> reaction) { + reaction_map[name] = reaction; +} + +void Reactor::construct() { + if (p_param) { + p_param->fetch_config(); + } + construction(); +} +void Reactor::assemble() { + if (reaction_internals_) { + reaction_internals_->assemble(); + } + wiring(); +} + +void Reactor::populate_params(std::set<std::string> &types, std::map<std::string, std::string> &homog_map_entries, std::map<std::string, std::string> &hetero_map_entries) { + if (p_param) { + p_param->populate_params (types, homog_map_entries, hetero_map_entries); + } + + for (auto *reactor : child_reactors) { + reactor->populate_params(types, homog_map_entries, hetero_map_entries); + } + +} + +} // namespace sdk \ No newline at end of file diff --git a/test/ActionDelay/CMakeLists.txt b/test/ActionDelay/CMakeLists.txt new file mode 100644 index 00000000..31bb9c9a --- /dev/null +++ b/test/ActionDelay/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(ActionDelay VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET ActionDelay) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/test/ActionDelay/main.cc b/test/ActionDelay/main.cc new file mode 100644 index 00000000..12a6b6b6 --- /dev/null +++ b/test/ActionDelay/main.cc @@ -0,0 +1,176 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +using namespace std; +using namespace sdk; + +class GeneratedDelay : public Reactor { + LogicalAction<void> act{"act", this, 100ms}; + + REACTION_SCOPE_START_NO_PARAMS(GeneratedDelay) + int y_state = 0; + + void add_reactions (GeneratedDelay *reactor) { + reaction ("reaction_1"). + triggers(&reactor->y_in). + dependencies(). + effects(&reactor->act). + function ( + [this](Input<int> &y_in, LogicalAction<void> &act) { + y_state = *y_in.get(); + act.schedule(); + } + ); + + reaction ("reaction_2"). + triggers(&reactor->act). + dependencies(). + effects(&reactor->y_out). + function ( + [this](LogicalAction<void> &act, Output<int> &y_out) { + y_out.set(y_state); + } + ); + } + REACTION_SCOPE_END_NO_PARAMS(this) +public: + GeneratedDelay(const std::string &name, Environment *env) + : Reactor(name, env) {} + GeneratedDelay(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Input<int> y_in{"y_in", this}; + Output<int> y_out{"y_out", this}; + + void construction() {} + + void wiring() {} +}; + +class Source : public Reactor { + REACTION_SCOPE_START_NO_PARAMS(Source) + void add_reactions (Source *reactor) { + reaction ("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(&reactor->out). + function ( + [this](Startup &startup, Output<int> &out) { + out.set(1); + } + ); + } + REACTION_SCOPE_END_NO_PARAMS(this) +public: + Source(const std::string &name, Environment *env) + : Reactor(name, env) {} + Source(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Output<int> out{"out", this}; + + void construction() {} + void wiring() {} +}; + +class Sink : public Reactor { + REACTION_SCOPE_START_NO_PARAMS(Sink) + void add_reactions (Sink *reactor) { + reaction ("reaction_1"). + triggers(&reactor->in). + dependencies(). + effects(). + function ( + [this](Input<int> &in) { + auto elapsed_logical = get_elapsed_logical_time(); + auto logical = get_logical_time(); + auto physical = get_physical_time(); + std::cout << "logical time: " << logical << '\n'; + std::cout << "physical time: " << physical << '\n'; + std::cout << "elapsed logical time: " << elapsed_logical << '\n'; + if (elapsed_logical != 100ms) { + std::cerr << "ERROR: Expected 100 msecs but got " << elapsed_logical << '\n'; + exit(1); + } else { + std::cout << "SUCCESS. Elapsed logical time is 100 msec.\n"; + } + } + ); + } + REACTION_SCOPE_END_NO_PARAMS(this) +public: + Sink(const std::string &name, Environment *env) + : Reactor(name, env) {} + Sink(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Input<int> in{"in", this}; + + void construction() {} + void wiring() {} +}; + +class ActionDelay : public Reactor { + std::unique_ptr<Source> source; + std::unique_ptr<Sink> sink; + std::unique_ptr<GeneratedDelay> g; +public: + ActionDelay(const std::string &name, Environment *env) + : Reactor(name, env) {} + ActionDelay(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() { + source = std::make_unique<Source>("Source", this); + sink = std::make_unique<Sink>("Sink", this); + g = std::make_unique<GeneratedDelay>("GeneratedDelay", this); + } + + void wiring() { + source->out --> g->y_in; + g->y_out --> sink->in; + } +}; + +int main(int argc, char **argv) { + cxxopts::Options options("ActionDelay", "Multiport source connecting to banked sink reactors"); + + unsigned workers = std::thread::hardware_concurrency(); + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + bool cfg_gen{false}; + + // the timeout variable needs to be tested beyond fitting the Duration-type + options + .set_width(120) + .add_options() + ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value<unsigned>(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value<reactor::Duration>(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value<bool>(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value<bool>(cfg_gen)->default_value("false")) + ("help", "Print help"); + + cxxopts::ParseResult result{}; + bool parse_error{false}; + try { + result = options.parse(argc, argv); + } catch (const cxxopts::OptionException& e) { + reactor::log::Error() << e.what(); + parse_error = true; + } + + // if parameter --help was used or there was a parse error, print help + if (parse_error || result.count("help")) + { + std::cout << options.help({""}); + return parse_error ? -1 : 0; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " cfg_gen:" << (cfg_gen ? "True" : "False") << std::endl; + + Environment sim {nullptr, workers, fast, timeout, cfg_gen}; + auto action_delay = new ActionDelay("ActionDelay", &sim); + + sim.run(); + return 0; +} diff --git a/test/ActionIsPresent/CMakeLists.txt b/test/ActionIsPresent/CMakeLists.txt new file mode 100644 index 00000000..0adc43de --- /dev/null +++ b/test/ActionIsPresent/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(ActionIsPresent VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET ActionIsPresent) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/test/ActionIsPresent/main.cc b/test/ActionIsPresent/main.cc new file mode 100644 index 00000000..c065299d --- /dev/null +++ b/test/ActionIsPresent/main.cc @@ -0,0 +1,110 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +using namespace std; +using namespace sdk; + +class ActionIsPresent : public Reactor { + struct Parameters : public SystemParametersStandalone<Duration> { + REACTOR_PARAMETER (Duration, offset, "offset", 1ns, 10ns, 1ns); + REACTOR_PARAMETER (Duration, period, "period", 500ms, 1s, 500ms); + + Parameters(Reactor *container) + : SystemParametersStandalone<Duration>(container) { + register_parameters (offset, period); + } + }; + Parameters parameters{this}; + + REACTION_SCOPE_START(ActionIsPresent, Parameters) + bool success = false; + Duration zero = 0ns; + void add_reactions(ActionIsPresent *reactor) { + reaction ("reaction_1"). + triggers(&reactor->startup, &reactor->a). + dependencies(). + effects(). + function ( + [this](Startup &startup, LogicalAction<void> &a) { + if (!a.is_present()) { + if (parameters.offset.value == zero) { + std::cout << "Hello World!" << '\n'; + success = true; + } else { + a.schedule(parameters.offset.value); + } + } else { + std::cout << "Hello World 2!" << '\n'; + success = true; + } + } + ); + + reaction ("reaction_2"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function ( + [this](Shutdown &shutdown) { + if (!success) { + std::cerr << "Failed to print 'Hello World!'" << '\n'; + exit(1); + } + } + ); + } + REACTION_SCOPE_END(this, parameters) + + LogicalAction<void> a{"a", this}; +public: + ActionIsPresent(const std::string &name, Environment *env) + : Reactor(name, env) {} + ActionIsPresent(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() {} + void wiring() {} +}; + +int main(int argc, char **argv) { + cxxopts::Options options("ActionIsPresent", "Multiport source connecting to banked sink reactors"); + + unsigned workers = std::thread::hardware_concurrency(); + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + bool cfg_gen{false}; + + // the timeout variable needs to be tested beyond fitting the Duration-type + options + .set_width(120) + .add_options() + ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value<unsigned>(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value<reactor::Duration>(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value<bool>(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value<bool>(cfg_gen)->default_value("false")) + ("help", "Print help"); + + cxxopts::ParseResult result{}; + bool parse_error{false}; + try { + result = options.parse(argc, argv); + } catch (const cxxopts::OptionException& e) { + reactor::log::Error() << e.what(); + parse_error = true; + } + + // if parameter --help was used or there was a parse error, print help + if (parse_error || result.count("help")) + { + std::cout << options.help({""}); + return parse_error ? -1 : 0; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " cfg_gen:" << (cfg_gen ? "True" : "False") << std::endl; + + Environment sim {nullptr, workers, fast, timeout, cfg_gen}; + auto action_delay = new ActionIsPresent("ActionIsPresent", &sim); + + sim.run(); + return 0; +} diff --git a/test/Deadlines/CMakeLists.txt b/test/Deadlines/CMakeLists.txt new file mode 100644 index 00000000..aef0a597 --- /dev/null +++ b/test/Deadlines/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(Deadlines VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET Deadlines) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/test/Deadlines/main.cc b/test/Deadlines/main.cc new file mode 100644 index 00000000..ee938cb7 --- /dev/null +++ b/test/Deadlines/main.cc @@ -0,0 +1,174 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +using namespace std; +using namespace sdk; + +class Source : public Reactor { + struct Parameters : public SystemParametersStandalone<Duration> { + REACTOR_PARAMETER (Duration, period, "period", 1s, 5s, 2s); + + Parameters(Reactor *container) + : SystemParametersStandalone<Duration>(container) { + register_parameters (period); + } + }; + Parameters parameters{this}; + + REACTION_SCOPE_START(Source, Parameters) + int count = 0; + void add_reactions(Source *reactor) { + reaction ("reaction_1"). + triggers(&reactor->t). + dependencies(). + effects(&reactor->y). + function ( + [this](Timer &t, Output<int> &y) { + if (count % 2 == 1) { + // The count variable is odd. + // Take time to cause a deadline violation. + std::this_thread::sleep_for(400ms); + } + std::cout << "Source sends: " << count << std::endl; + y.set(count); + count++; + } + ); + } + REACTION_SCOPE_END(this, parameters) + + Timer t{"t", this}; +public: + Source(const std::string &name, Environment *env) + : Reactor(name, env) {} + Source(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Output<int> y{"y", this}; + + void construction() { + t.set_timer (parameters.period.value, 0ns); + } + + void wiring() {} +}; + +class Destination : public Reactor { + struct Parameters : public SystemParametersStandalone<Duration> { + REACTOR_PARAMETER (Duration, timeout, "timeout", 100ms, 1s, 200ms); + + Parameters(Reactor *container) + : SystemParametersStandalone<Duration>(container) { + register_parameters (timeout); + } + }; + Parameters parameters{this}; + + REACTION_SCOPE_START(Destination, Parameters) + int count = 0; + void add_reactions(Destination *reactor) { + reaction ("reaction_1"). + triggers(&reactor->x). + dependencies(). + effects(). + function ( + [this](Input<int> &x) { + std::cout << "Destination receives: " << *x.get() << std::endl; + if (count % 2 == 1) { + // The count variable is odd, so the deadline should have been + // violated + std::cerr << "ERROR: Failed to detect deadline." << std::endl; + exit(1); + } + count++; + } + ).deadline (parameters.timeout.value, + [this](Input<int> &x) { + std::cout << "Destination deadline handler receives: " + << *x.get() << std::endl; + if (count % 2 == 0) { + // The count variable is even, so the deadline should not have + // been violated. + std::cerr << "ERROR: Deadline handler invoked without deadline " + << "violation." << std::endl; + exit(2); + } + count++; + }); + } + REACTION_SCOPE_END(this, parameters) +public: + Destination(const std::string &name, Environment *env) + : Reactor(name, env) {} + Destination(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Input<int> x{"x", this}; + + void construction() {} + + void wiring() { + } +}; + +class Deadline : public Reactor { + std::unique_ptr<Source> s; + std::unique_ptr<Destination> d; +public: + Deadline(const std::string &name, Environment *env) + : Reactor(name, env) {} + Deadline(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() { + s = std::make_unique<Source>("Source", this); + d = std::make_unique<Destination>("Destination", this); + } + + void wiring() { + s->y --> d->x; + } +}; + +int main(int argc, char **argv) { + cxxopts::Options options("Deadlines", "Multiport source connecting to banked sink reactors"); + + unsigned workers = std::thread::hardware_concurrency(); + bool fast{false}; + reactor::Duration timeout = 4s; + bool cfg_gen{false}; + + // the timeout variable needs to be tested beyond fitting the Duration-type + options + .set_width(120) + .add_options() + ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value<unsigned>(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value<reactor::Duration>(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value<bool>(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value<bool>(cfg_gen)->default_value("false")) + ("help", "Print help"); + + cxxopts::ParseResult result{}; + bool parse_error{false}; + try { + result = options.parse(argc, argv); + } catch (const cxxopts::OptionException& e) { + reactor::log::Error() << e.what(); + parse_error = true; + } + + // if parameter --help was used or there was a parse error, print help + if (parse_error || result.count("help")) + { + std::cout << options.help({""}); + return parse_error ? -1 : 0; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " cfg_gen:" << (cfg_gen ? "True" : "False") << std::endl; + + Environment sim {nullptr, workers, fast, timeout, cfg_gen}; + auto dl = new Deadline("Deadline", &sim); + + sim.run(); + return 0; +} diff --git a/test/ReactionOrder/CMakeLists.txt b/test/ReactionOrder/CMakeLists.txt new file mode 100644 index 00000000..73c3a1d6 --- /dev/null +++ b/test/ReactionOrder/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(ReactionOrder VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET ReactionOrder) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/test/ReactionOrder/main.cc b/test/ReactionOrder/main.cc new file mode 100644 index 00000000..8cae5e35 --- /dev/null +++ b/test/ReactionOrder/main.cc @@ -0,0 +1,120 @@ + +#include <reactor-sdk/reactor-sdk.hh> + +using namespace std; +using namespace sdk; + +class Src : public Reactor { + REACTION_SCOPE_START_NO_PARAMS(Src) + void add_reactions (Src *reactor) { + reaction ("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(&reactor->out). + function ( + [this](Startup &startup, Output<unsigned> &out) { + out.set(42); + } + ); + } + REACTION_SCOPE_END_NO_PARAMS(this) +public: + Output<unsigned> out{"out", this}; + + Src(const std::string &name, Environment *env) + : Reactor(name, env) {} + Src(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() {} + void wiring() {} +}; + +class Sink : public Reactor { + REACTION_SCOPE_START_NO_PARAMS(Sink) + void add_reactions (Sink *reactor) { + reaction ("reaction_1"). + triggers(&reactor->startup). + dependencies(&reactor->in). + effects(). + function ( + [this](Startup &startup, Input<unsigned> &in) { + if (!in.is_present()) { + reactor::log::Error() << "Received no value"; + exit(1); + } + if(*in.get() != 42) { + reactor::log::Error() << "Received an unexpected value"; + exit(1); + } + } + ); + + reaction ("reaction_2"). + triggers(&reactor->shutdown). + dependencies(). + effects(). + function ( + [this](Shutdown &shutdown) { + reactor::log::Info() << "Success!"; + } + ); + } + REACTION_SCOPE_END_NO_PARAMS(this) +public: + Input<unsigned> in{"in", this}; + + Sink(const std::string &name, Environment *env) + : Reactor(name, env) {} + Sink(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() {} + void wiring() {} +}; + +int main(int argc, char **argv) { + cxxopts::Options options("ReactionOrder", "Multiport source connecting to banked sink reactors"); + + unsigned workers = std::thread::hardware_concurrency(); + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + bool cfg_gen{false}; + + // the timeout variable needs to be tested beyond fitting the Duration-type + options + .set_width(120) + .add_options() + ("w,workers", "the number of worker threads used by the scheduler", cxxopts::value<unsigned>(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value<reactor::Duration>(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value<bool>(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value<bool>(cfg_gen)->default_value("false")) + ("help", "Print help"); + + cxxopts::ParseResult result{}; + bool parse_error{false}; + try { + result = options.parse(argc, argv); + } catch (const cxxopts::OptionException& e) { + reactor::log::Error() << e.what(); + parse_error = true; + } + + // if parameter --help was used or there was a parse error, print help + if (parse_error || result.count("help")) + { + std::cout << options.help({""}); + return parse_error ? -1 : 0; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " cfg_gen:" << (cfg_gen ? "True" : "False") << std::endl; + + Environment sim {nullptr, workers, fast, timeout, cfg_gen}; + + auto src = new Src("src", &sim); + auto sink = new Sink("sink", &sim); + src->out --> sink->in; + + sim.run(); + return 0; +}