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::ParametersMap UserParameters::homogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata { 5 } } + }; +} + +ConfigParameter::ParametersMap UserParameters::heterogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata { 20 } }, + {"Main.Sink.n_ports", ConfigParameterMetadata { 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 +#include +#include +#include + +using namespace sdk; + +struct UserParameters : public ConfigParameter { + ConfigParameter::ParametersMap homogeneous_config(); + ConfigParameter::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("Source", this); + snk = std::make_unique("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 + +#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 { + REACTOR_PARAMETER(string, alias, "Alternate name", "another", "another", defaults.alias); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters(container, std::forward(param)) { + register_parameters (alias); + } + }; + PublishParameters parameters; + + REACTION_SCOPE_START(MainReactor, PublishParameters) + void add_reactions(MainReactor *reactor); + REACTION_SCOPE_END(this, parameters) + + std::unique_ptr src; + std::unique_ptr 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(param)} {} + MainReactor(const std::string &name, Reactor *container, Parameters && param) + : Reactor(name, container), parameters{this, std::forward(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& req, MultiportOutput& 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 +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 { + 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(container, std::forward(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& req, MultiportOutput& 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(param)} {} + SinkReactor(const std::string &name, Reactor *container, Parameters && param) + : Reactor(name, container), parameters{this, std::forward(param)} {} + + MultiportInput req{"req", this}; + MultiportOutput 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& 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& sch, Output& 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& rsp, LogicalAction& 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 +using namespace std; +using namespace sdk; + +class SourceReactor : public Reactor { +public: + struct Parameters : public SystemParametersStandalone { + REACTOR_PARAMETER(int, iterations, "Number of iterations", 1, 100, 10); + + Parameters(Reactor *container) + : SystemParametersStandalone(container) { + register_parameters (iterations); + } + }; +private: + LogicalAction 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 rsp{"rsp", this}; + Output 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 + +#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(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value(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::ParametersMap UserParameters::homogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata { 5 } }, + // {"Main.Sink.name", ConfigParameterMetadata { "Homog Name" } }, + }; +} + +ConfigParameter::ParametersMap UserParameters::heterogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata { 20 } }, + {"Main.Source.n_ports", ConfigParameterMetadata { 4 } }, + {"Main.n_sinks", ConfigParameterMetadata { 4 } }, + // {"Main.Sink_0.name", ConfigParameterMetadata { "Hetero Name 0" } }, + // {"Main.Sink_1.name", ConfigParameterMetadata { "Hetero Name 1" } }, + // {"Main.Sink_2.name", ConfigParameterMetadata { "Hetero Name 2" } }, + // {"Main.Sink_3.name", ConfigParameterMetadata { "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 +#include +#include +#include + +using namespace sdk; + +struct UserParameters : public ConfigParameter { + ConfigParameter::ParametersMap homogeneous_config(); + ConfigParameter::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("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 + +#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 { + 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(container, std::forward(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 src; + ReactorBank 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(param)} {} + MainReactor(const std::string &name, Reactor *container, Parameters && param) + : Reactor(name, container), parameters{this, std::forward(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& req, Output& 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 +using namespace std; +using namespace sdk; + +class SinkReactor : public Reactor { +public: + + struct Parameters { + string name = "Sink"; + }; +private: + struct PublishParameters : public SystemParameters { + REACTOR_PARAMETER(string, name, "Alternate name", "Sink", "Sink", defaults.name); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters(container, std::forward(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& req, Output& 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(param)} {} + SinkReactor(const std::string &name, Reactor *container, Parameters && param) + : Reactor(name, container), parameters{this, std::forward(param)} {} + + Input req{"req", this}; + Output 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& 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& sch, MultiportOutput& 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& rsp, LogicalAction& 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 +using namespace std; +using namespace sdk; + +class SourceReactor : public Reactor { +public: + struct Parameters : public SystemParametersStandalone { + 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(container) { + register_parameters (iterations, n_ports); + } + }; +private: + LogicalAction 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 rsp{"rsp", this}; + MultiportOutput 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 + +#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(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value(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 + +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 &in_req, Output &all_workers_busy, MultiportOutput &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 &in_rsp, Output &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 in_req{"in_req", this}; + Output out_rsp{"out_rsp", this}; + MultiportOutput out_req{"out_req", this}; + MultiportInput in_rsp{"in_rsp", this}; + + Output all_workers_busy{"all_workers_busy", this}; + + Relay(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward(param)} {} + Relay(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward(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 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 &req, LogicalAction &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(std::chrono::nanoseconds(processing_delay))); + } + ); + + reaction("reaction_3"). + triggers(&reactor->sch_rsp). + dependencies(). + effects(&reactor->rsp). + function( + [this](LogicalAction &sch_rsp, Output &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(param)} {} + Worker(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward(param)} {} + + Input req{"req", this}; + Output 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 sch_rsp{"sch_rsp", this}; + + ReactorBank workers{"workers", this}; + std::unique_ptr 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(param)} {} + Pool(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward(param)} {} + + Input req{"req", this}; + Output rsp{"rsp", this}; + + Output 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", 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 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 &sch) { + sch.schedule (-1, std::chrono::duration_cast(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 &sch, MultiportOutput &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(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 &rsp, LogicalAction &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(std::chrono::nanoseconds(0))); + } + } + ); + + reaction("reaction_4"). + triggers(&reactor->hybernate). + dependencies(). + effects(). + function( + [this](MultiportInput &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(param)} {} + Tasks(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward(param)} {} + + MultiportOutput req{"req", this}; + MultiportInput rsp{"rsp", this}; + MultiportInput 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(param)} {} + Main(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward(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; + ReactorBank pool{"pool", this}; + +public: + void construction() override { + tasks = std::make_unique("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 +=} + +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(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(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(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(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 + +using namespace sdk; + +struct UserParameters : public ConfigParameter { + ConfigParameter::ParametersMap homogeneous_config(); + ConfigParameter::ParametersMap heterogeneous_config(); +}; + +UserParameters cfg_parameters; + +ConfigParameter::ParametersMap UserParameters::homogeneous_config() { + return { + { "Main.n_pools", ConfigParameterMetadata {2} }, + { "Main.n_tasks", ConfigParameterMetadata {10} }, + { "Main.pool.n_workers", ConfigParameterMetadata {4} }, + { "Main.pool.workers.processing_delay", ConfigParameterMetadata {1000000000ns} } + }; +} + +ConfigParameter::ParametersMap UserParameters::heterogeneous_config() { + return { + { "Main.n_pools", ConfigParameterMetadata {2} }, + { "Main.n_tasks", ConfigParameterMetadata {10} }, + { "Main.pool_0.n_workers", ConfigParameterMetadata {2} }, + { "Main.pool_0.workers_0.processing_delay", ConfigParameterMetadata {100000000ns} }, + { "Main.pool_0.workers_1.processing_delay", ConfigParameterMetadata {1000000000ns} }, + { "Main.pool_1.n_workers", ConfigParameterMetadata {3} }, + { "Main.pool_1.workers_0.processing_delay", ConfigParameterMetadata {1000000000ns} }, + { "Main.pool_1.workers_1.processing_delay", ConfigParameterMetadata {100000000ns} }, + { "Main.pool_1.workers_2.processing_delay", ConfigParameterMetadata {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 &in_req, Output &all_workers_busy, MultiportOutput &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 &in_rsp, Output &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 in_req{"in_req", this}; + Output out_rsp{"out_rsp", this}; + MultiportOutput out_req{"out_req", this}; + MultiportInput in_rsp{"in_rsp", this}; + + Output all_workers_busy{"all_workers_busy", this}; + + Relay(const std::string &name, Environment *env, Parameters &¶m) + : Reactor(name, env), parameters{std::forward(param)} {} + Relay(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward(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 sch_rsp{"sch_rsp", this}; + + struct PublishParameters : public SystemParameters { + REACTOR_PARAMETER(Duration, processing_delay, "Worker's processing delay", 1ms, 2s, defaults.processing_delay); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters(container, std::forward(param)) { + register_parameters (processing_delay); + } + }; + + PublishParameters parameters; + class Chamber : public ReactionChamber { + const Duration &processing_delay = parameters.processing_delay.value; + public: + Chamber(Reactor *reactor, PublishParameters ¶ms) + : ReactionChamber(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 &req, LogicalAction &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(std::chrono::nanoseconds(processing_delay))); + } + ); + + reaction("reaction_3"). + triggers(&reactor->sch_rsp). + dependencies(). + effects(&reactor->rsp). + function( + [this](LogicalAction &sch_rsp, Output &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(param)} {} + Worker(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{this, std::forward(param)} {} + + Input req{"req", this}; + Output 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 { + REACTOR_PARAMETER(int, n_workers, "Number of workers in the pool", 1, 10, defaults.n_workers); + + PublishParameters(Reactor *container, Parameters &¶m) + : SystemParameters(container, std::forward(param)) { + register_parameters (n_workers); + } + }; + PublishParameters parameters; + const int &n_workers = parameters.n_workers.value; + + LogicalAction sch_rsp{"sch_rsp", this}; + + ReactorBank workers{"workers", this}; + std::unique_ptr relay; + + class Chamber : public ReactionChamber { + public: + Chamber(Reactor *reactor, PublishParameters ¶ms) + : ReactionChamber(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(param)} {} + Pool(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{this, std::forward(param)} {} + + Input req{"req", this}; + Output rsp{"rsp", this}; + + Output 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", 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 sch{"sch", this}; + + class Chamber : public ReactionChamber { + 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(reactor, params) {} + + void add_reactions(Tasks *reactor) override { + reaction("reaction_1"). + triggers(&reactor->startup). + dependencies(). + effects(&reactor->sch). + function( + [this](Startup& startup, LogicalAction &sch) { + sch.schedule (-1, std::chrono::duration_cast(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 &sch, MultiportOutput &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(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 &rsp, LogicalAction &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(std::chrono::nanoseconds(0))); + } + } + ); + + reaction("reaction_4"). + triggers(&reactor->hybernate). + dependencies(). + effects(). + function( + [this](MultiportInput &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(param)} {} + Tasks(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{std::forward(param)} {} + + MultiportOutput req{"req", this}; + MultiportInput rsp{"rsp", this}; + MultiportInput 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(param)} {} + Main(const std::string &name, Reactor *container, Parameters &¶m) + : Reactor(name, container), parameters{this, std::forward(param)} {} +private: + struct PublishParameters : public SystemParameters { + 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(container, std::forward(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; + ReactorBank pool{"pool", this}; + +public: + + void construction() override { + tasks = std::make_unique("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(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value(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 +=} + +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(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(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(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(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::ParametersMap UserParameters::homogeneous_config() { + return { + }; +} + +ConfigParameter::ParametersMap UserParameters::heterogeneous_config() { + return { + {"Main.slow.period", ConfigParameterMetadata { 1s } }, + {"Main.slow.duration", ConfigParameterMetadata { 5s } }, + {"Main.n_fast", ConfigParameterMetadata { 3 } }, + {"Main.fast_0.period", ConfigParameterMetadata { 500ms } }, + {"Main.fast_0.duration", ConfigParameterMetadata { 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 +#include +#include +#include + +using namespace sdk; + +struct UserParameters : public ConfigParameter { + ConfigParameter::ParametersMap homogeneous_config(); + ConfigParameter::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("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 + +#include "Node/NodeReactor.hh" + +using namespace sdk; + +class MainReactor: public Reactor { +public: + struct Parameters : public SystemParametersStandalone { + REACTOR_PARAMETER(int, n_fast, "Number of fast nodes", 1, 10, 2); + + Parameters(Reactor *container) + : SystemParametersStandalone(container) { + register_parameters (n_fast); + } + }; +private: + Parameters parameters{this}; + std::unique_ptr slow; + ReactorBank 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 &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 &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 + +using namespace sdk; + +class NodeReactor: public Reactor { +public: + struct Parameters : public SystemParametersStandalone { + REACTOR_PARAMETER(Duration, period, "Schedule and deadline period", 10ms, 10s, 500ms); + REACTOR_PARAMETER(Duration, duration, "Sleep duration", 5ms, 5s, 10ms); + + Parameters(Reactor *container) + : SystemParametersStandalone(container) { + register_parameters (period, duration); + } + }; + +private: + Parameters parameters{this}; + LogicalAction 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 + +#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(workers)->default_value(std::to_string(workers)), "'unsigned'") + ("o,timeout", "Time after which the execution is aborted.", cxxopts::value(timeout)->default_value(time_to_string(timeout)), "'FLOAT UNIT'") + ("f,fast", "Allow logical time to run faster than physical time.", cxxopts::value(fast)->default_value("false")) + ("c,config-gen", "Generate configuration files for the topology.", cxxopts::value(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 + +using namespace sdk; + +class Hello : public Reactor { +private: + struct Parameters { + }; + Parameters parameters; + + Timer timer{"timer", this}; + + class Chamber : public ReactionChamber { + public: + Chamber(Reactor *reactor, Parameters ¶ms) + : ReactionChamber(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 +#include +#include + +namespace sdk +{ + +template +struct is_comparable : std::false_type {}; + +template +struct is_comparable< + T, + std::void_t() < std::declval())> +> : std::true_type {}; + +extern std::map type_convert; +template +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 + int PullConfigParameter(const bool &is_heterogeneous, const std::string &key, ParameterMetadata* user_param) { + return pull_config_parameter(is_heterogeneous, key, static_cast(user_param), typeid(T)); + } + friend class Environment; +}; + +template +struct ConfigParameterMetadata { + std::vector values; + + ConfigParameterMetadata(std::initializer_list val) : values(val) {} +}; + +template +class ConfigParameter : public ConfigParameterBase { +public: + using ParameterValue = std::variant...>; + using ParametersMap = std::map; + + 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 *param_map = is_heterogeneous ? &hetero_param_map : &homoge_param_map; + std::set *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; + using U = typename ContainerType::value_type; + + if (ti == typeid(U)) { + ParameterMetadata* param = static_cast*>(user_param); + if constexpr (is_comparable::value && !std::is_same::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 homoge_param_map; + std::set homoge_invalid_keys; + std::map hetero_param_map; + std::set 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 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 +class Input : public reactor::Input { + class WiringProxy { + public: + WiringProxy(Input& origin) : origin(origin) {} + + void operator>(Input& input) { + origin.connect (input); + } + + void operator>(MultiportInput& input) { + origin.connect (input); + } + + void operator>>(MultiportInput& input) { + origin.connect_fanout (input); + } + + template + void operator>(ReactorBankInputPortOffset &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + template + void operator>>(ReactorBankInputPortOffset &&other_bank_ports) { + origin.connect_fanout(std::move(other_bank_ports)); + } + + private: + Input& origin; + }; + + void connect(Input& input); + void connect(MultiportInput& input); + void connect_fanout(MultiportInput& input); + + template + void connect(ReactorBankInputPortOffset &&other_bank_ports); + + template + void connect_fanout(ReactorBankInputPortOffset &&other_bank_ports); + +public: + using value_type = T; + Input(const std::string& name, reactor::Reactor* container) + : reactor::Input(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 + +namespace sdk +{ + +class Reactor; + +template +using LogicalAction = reactor::LogicalAction; + +using Startup = reactor::StartupTrigger; +using Shutdown = reactor::ShutdownTrigger; + +using Duration = reactor::Duration; +using TimePoint = reactor::TimePoint; + +#define select_default(obj) &obj[0] + +template +struct inspect_function_args; + +template +struct inspect_function_args { + static constexpr size_t nargs = sizeof...(Args); +}; + +template +auto bind_function(Object* obj, Func&& func) { + constexpr size_t nargs = inspect_function_args::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), obj, std::placeholders::_1); + } else if constexpr (nargs == 2) { + return std::bind(std::forward(func), obj, std::placeholders::_1, std::placeholders::_2); + } else if constexpr (nargs == 3) { + return std::bind(std::forward(func), obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); + } else if constexpr (nargs == 4) { + return std::bind(std::forward(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), 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), 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), 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), 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), 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), 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::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 buf{}; + time_t time = + std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(tp)); + auto res = std::strftime(buf.data(), sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&time)); + auto epoch = std::chrono::duration_cast(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 +#include "reactor-cpp/reactor-cpp.hh" + +namespace sdk +{ + +template +class Input; + +class Reactor; + +template +class MultiportInput : public reactor::ModifableMultiport> { + size_t n_inputs; + std::string name; + Reactor *reactor; + + class WiringProxy { + public: + WiringProxy(MultiportInput& origin) : origin(origin) {} + + void operator>(Input& input) { + origin.connect (input); + } + + void operator>(MultiportInput& input) { + origin.connect (input); + } + + private: + MultiportInput& origin; + }; + + void connect(Input& input); + void connect(MultiportInput& 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 +#include "reactor-cpp/reactor-cpp.hh" + +namespace sdk +{ + +template +class Input; + +template +class Output; + +template +class MultiportOutput; + +template +class MultiportInput; + +class Reactor; + +template +class MultiportOutput : public reactor::ModifableMultiport> { + size_t n_inputs; + std::string name; + Reactor *reactor; + class WiringProxy { + public: + WiringProxy(MultiportOutput& origin) : origin(origin) {} + + void operator>(Input& input) { + origin.connect (input); + } + + void operator>(Output& input) { + origin.connect (input); + } + + void operator>(MultiportInput& input) { + origin.connect (input); + } + + void operator>(MultiportOutput& input) { + origin.connect (input); + } + + template + void operator>(std::pair>*, Input ReactorType::*> connections) + { + origin.connect (connections.first, connections.second); + } + + template + void operator>(ReactorBankInputPort &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputPortOffset &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + private: + MultiportOutput& origin; + }; + + void connect(Input& input); + void connect(Output& input); + void connect(MultiportInput& input); + void connect(MultiportOutput& input); + + template + void connect(std::vector>* reactors, Input ReactorType::*member); + + template + void connect(ReactorBankInputPort &&other_bank_ports); + + template + void connect(ReactorBankInputPortOffset &&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 +class Input; + +template +class Output; + +template +class MultiportOutput; + +template +class MultiportInput; + +class Reactor; + +template +class Output : public reactor::Output { + std::set*> accumulated; + bool is_accumulated = false; + + class WiringProxy { + public: + WiringProxy(Output& origin) : origin(origin) {} + + void operator>(Input& input) { + origin.connect (input); + } + + void operator>(Output& input) { + origin.connect (input); + } + + void operator>(MultiportInput& input) { + origin.connect (input); + } + + void operator>>(MultiportInput& input) { + origin.connect_fanout (input); + } + + template + void operator>(std::pair>*, Input ReactorType::*> connections) + { + origin.connect (connections.first, connections.second); + } + + template + void operator>(ReactorBankInputPort &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputPortOffset &&other_bank_ports) { + origin.connect(std::move(other_bank_ports)); + } + + template + void operator>>(ReactorBankInputPortOffset &&other_bank_ports) { + origin.connect_fanout(std::move(other_bank_ports)); + } + + private: + Output& origin; + }; + + void connect(Input& input); + void connect(Output& input); + void connect(MultiportInput& input); + void connect_fanout(MultiportInput& input); + + template + void connect(std::vector>* reactors, Input ReactorType::*member); + + template + void connect(ReactorBankInputPort &&other_bank_ports); + + template + void connect(ReactorBankInputPortOffset &&other_bank_ports); + + template + void connect_fanout(ReactorBankInputPortOffset &&other_bank_ports); + +public: + using value_type = T; + Output(const std::string& name, reactor::Reactor* container) + : reactor::Output(name, container) {} + + ~Output() {} + + Output(Output&&) noexcept = default; + + WiringProxy operator--(int) { + return WiringProxy(*this); + } + + Output& operator+(Output &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 +class Reaction; + +template class Template> +struct is_specialization : std::false_type +{ +}; + +template class Template> +struct is_specialization, Template> : std::true_type +{ +}; + +template class Template> +inline constexpr bool is_specialization_v = is_specialization::value; + +// fix for gcc < 13 +template +constexpr bool templated_false = false; + +template +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 + Reaction &function(Fn func) + { + if constexpr (std::is_bind_expression::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> (name, reactor, std::move(input_triggers), std::move(dependencies), std::move(output_triggers), std::forward(func)); + ReactionRef->execute(); + return *ReactionRef; + } +}; + +template +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 + ReactionOutput> &effects(Outputs&&... outputs) + { + auto output_tuple = std::make_tuple(outputs...); + auto ReactionOutputRef = std::make_shared>> (name, reactor, std::move(input_triggers), std::move(dependencies), std::move(output_tuple)); + next = ReactionOutputRef; + return *ReactionOutputRef; + } +}; + +template +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 + ReactionDependency> &dependencies(Dependencies&&... deps) + { + auto deps_tuple = std::make_tuple(deps...); + auto ReactionDependenciesRef = std::make_shared>> (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 + ReactionInput> &triggers(Inputs&&... inputs) + { + auto input_tuple = std::make_tuple(inputs...); + auto ReactionInputRef = std::make_shared>> (name, reactor, std::move(input_tuple)); + next = ReactionInputRef; + return *ReactionInputRef; + } +}; + +template +class Reaction: public ReactionBase +{ +private: + InputTuple input_triggers; + DependencyTuple dependencies; + OutputTuple output_triggers; + Fn user_function; + std::unique_ptr reaction; + + template + void set_input_trigger(Reaction &reaction, Trigger &&trigger) + { + if constexpr (is_specialization_v>, MultiportInput>) + { + for (auto& port : *trigger) { + reaction.declare_trigger(&port); + } + } + else if constexpr (is_specialization_v>, MultiportOutput>) + { + for (auto& port : *trigger) { + reaction.declare_trigger(&port); + } + } + else { + reaction.declare_trigger(trigger); + } + } + + template + void set_input_triggers(std::unique_ptr &reaction, const std::tuple &inputs) + { + std::apply([this, &reaction](auto &&...input) + { + (void)this; + (..., set_input_trigger(*reaction, std::forward(input))); + }, + inputs); + } + + template + void set_dependency(Reaction &reaction, Trigger &&trigger) + { + if constexpr (is_specialization_v>, MultiportInput>) + { + for (auto& port : *trigger) { + reaction.declare_dependency(&port); + } + } + else { + reaction.declare_dependency(trigger); + } + } + + template + void set_dependencies(std::unique_ptr &reaction, const std::tuple &deps) + { + std::apply([this, &reaction](auto &&...dep) + { + (void)this; + (..., set_dependency(*reaction, std::forward(dep))); + }, + deps); + } + + template + void set_output_trigger(Reaction &reaction, Trigger &&trigger) + { + if constexpr (is_specialization_v>, Output>) + { + reaction.declare_antidependency(trigger); + } else if constexpr (is_specialization_v>, Input>) + { + reaction.declare_antidependency(trigger); + } + else if constexpr (is_specialization_v>, reactor::LogicalAction>) + { + reaction.declare_schedulable_action(trigger); + } + else if constexpr (is_specialization_v>, MultiportOutput>) + { + for (auto& port : *trigger) { + reaction.declare_antidependency(&port); + } + } + else + { + static_assert(templated_false, "Unsupported trigger type"); + } + } + + template + void set_output_triggers(std::unique_ptr &reaction, const std::tuple &outputs) + { + std::apply([this, &reaction](auto &&...output) + { + (void)this; + (..., set_output_trigger(*reaction, std::forward(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(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(func), (*ptrs)...); + }, + std::forward(tuple)); + }; + + apply_to_dereferenced(func, std::tuple_cat(this->input_triggers, this->dependencies, this->output_triggers)); + }; + + reaction = std::make_unique(name, priority, reactor, reactor_func); + + set_input_triggers(reaction, input_triggers); + set_dependencies(reaction, dependencies); + set_output_triggers(reaction, output_triggers); + } + + template + 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(func), (*ptrs)...); + }, + std::forward(tuple)); + }; + + apply_to_dereferenced(func, std::tuple_cat(this->input_triggers, this->dependencies, this->output_triggers)); + }; + + reaction->set_deadline(deadline_period, deadline_func); + } +}; + +template +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(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 +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(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 { \ +public: \ + Internals(Reactor *reactor, ParamType ¶ms) \ + : ReactionChamber(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 { \ +public: \ + Internals(Reactor *reactor) \ + : ReactionChamberParameterless(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 +{ +public: + Reactor *reactor; + std::shared_ptr 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 +#include "ReactionBase.hh" +#include "SystemParameterBase.hh" +#include "Environment.hh" + +namespace sdk +{ + +template +class ReactionInput; + +template +class Reaction; + +template +class Input; + +template +class Output; + +template +class MultiportOutput; + +template +class MultiportInput; + +class Timer; + +template +struct trigger_value_type; + +template +struct trigger_value_type *> +{ + using type = reactor::LogicalAction&; +}; + +template +struct trigger_value_type *> +{ + using type = Input&; +}; + +template +struct trigger_value_type *> +{ + using type = MultiportInput&; +}; + +template +struct trigger_value_type *> +{ + using type = MultiportOutput&; +}; + +template +struct trigger_value_type *> +{ + using type = Output&; +}; + +template <> +struct trigger_value_type +{ + using type = reactor::StartupTrigger&; +}; + +template <> +struct trigger_value_type +{ + using type = reactor::ShutdownTrigger&; +}; + +template <> +struct trigger_value_type +{ + 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> reaction_map; + ReactionBase *reaction_internals_; + int priority = 1; + std::set child_reactors; + std::string homog_name = ""; + + void add_child(Reactor* reactor); + void add_to_reaction_map (std::string &name, std::shared_ptr reaction); + int get_priority() { return priority++;} + + template + void validate_reaction(Fn func, std::tuple inputs, std::tuple deps, std::tuple outputs) { + (void)func; + (void)inputs; + (void)deps; + (void)outputs; + static_assert( + std::is_invocable_v< + Fn, + typename trigger_value_type::type..., + typename trigger_value_type::type..., + typename trigger_value_type::type... + >, + "Reaction function parameters must match the declared input and output types."); + } + + void populate_params(std::set &types, std::map &homog_map_entries, std::map &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 + friend class Reaction; + + template + 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 +class Input; + +template +class Output; + +template +class MultiportOutput; + +template +class MultiportInput; + +template +class ReactorBankInputPort { +public: + ReactorBankInputPort(std::vector>& reactors, Input ReactorType::*member) + : reactors(reactors), member(member) {} + + using iterator = typename std::vector>::iterator; + using const_iterator = typename std::vector>::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 ReactorType::* get_member() { return member; } + +private: + std::vector>& reactors; + Input ReactorType::*member; +}; + +template +class ReactorBankInputPortOffset { +public: + ReactorBankInputPortOffset(std::vector>& reactors, std::ptrdiff_t offset) + : reactors(reactors), offset(offset) {} + + using iterator = typename std::vector>::iterator; + using const_iterator = typename std::vector>::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>& reactors; + std::ptrdiff_t offset; +}; + +template +class ReactorBankInputMultiPort { +public: + ReactorBankInputMultiPort(std::vector> &reactors, MultiportInput ReactorType::*member) + : reactors(reactors), member(member) {} + + using iterator = typename std::vector>::iterator; + using const_iterator = typename std::vector>::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>& reactors; + MultiportInput ReactorType::*member; +}; + +template +class ReactorBankInputMultiPortOffset { +public: + ReactorBankInputMultiPortOffset(std::vector>& reactors, std::ptrdiff_t offset) + : reactors(reactors), offset(offset) {} + + using iterator = typename std::vector>::iterator; + using const_iterator = typename std::vector>::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>& reactors; + std::ptrdiff_t offset; +}; + +template +class ReactorBankOutputPort { + class WiringProxy { + public: + WiringProxy(ReactorBankOutputPort& origin) : origin(origin) {} + + void operator>(Input& input) { + origin.connect (input); + } + + void operator>(MultiportInput& input) { + origin.connect (input); + } + + template + void operator>(ReactorBankInputPort &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputMultiPort &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + private: + ReactorBankOutputPort& origin; + }; + + void connect(Input& input); + + void connect(MultiportInput& input); + + template + void connect(ReactorBankInputPort &&other_bank_ports); + + template + void connect(ReactorBankInputMultiPort &&other_bank_ports); + +public: + ReactorBankOutputPort(std::vector>& reactors, Output ReactorType::*member) + : reactors(reactors), member(member) {} + + using iterator = typename std::vector>::iterator; + using const_iterator = typename std::vector>::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>& reactors; + Output ReactorType::*member; +}; + +template +class ReactorBankOutputPortOffset { + class WiringProxy { + public: + WiringProxy(ReactorBankOutputPortOffset& origin) : origin(origin) {} + + void operator>(Input& input) { + origin.connect (input); + } + + void operator>(MultiportInput& input) { + origin.connect (input); + } + + void operator>>(MultiportInput& input) { + origin.connect_fanout (input); + } + + template + void operator>(ReactorBankInputPort &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputMultiPort &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputPortOffset &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputMultiPortOffset &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + private: + ReactorBankOutputPortOffset& origin; + }; + + void connect(Input& input); + void connect(MultiportInput& input); + void connect_fanout(MultiportInput& input); + + template + void connect(ReactorBankInputPort &&other_bank_ports); + + template + void connect(ReactorBankInputMultiPort &&other_bank_ports); + + template + void connect(ReactorBankInputPortOffset &&other_bank_ports); + + template + void connect(ReactorBankInputMultiPortOffset &&other_bank_ports); + +public: + ReactorBankOutputPortOffset(std::vector>& reactors, std::ptrdiff_t offset) + : reactors(reactors), offset(offset) {} + + using iterator = typename std::vector>::iterator; + using const_iterator = typename std::vector>::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>& reactors; + std::ptrdiff_t offset; +}; + +template +class ReactorBankOutputMultiPort { + class WiringProxy { + public: + WiringProxy(ReactorBankOutputMultiPort& origin) : origin(origin) {} + + void operator>(MultiportInput& input) { + origin.connect (input); + } + + template + void operator>(ReactorBankInputPort &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputMultiPort &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + private: + ReactorBankOutputMultiPort& origin; + }; + + void connect(MultiportInput& input); + + template + void connect(ReactorBankInputPort &&other_bank_ports); + + template + void connect(ReactorBankInputMultiPort &&other_bank_ports); + +public: + ReactorBankOutputMultiPort(std::vector>& reactors, MultiportOutput ReactorType::*member) + : reactors(reactors), member(member) {} + + using iterator = typename std::vector>::iterator; + using const_iterator = typename std::vector>::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>& reactors; + MultiportOutput ReactorType::*member; +}; + +template +class ReactorBankOutputMultiPortOffset { + class WiringProxy { + public: + WiringProxy(ReactorBankOutputMultiPortOffset& origin) : origin(origin) {} + + void operator>(MultiportInput& input) { + origin.connect (input); + } + + template + void operator>(ReactorBankInputPort &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputMultiPort &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputPortOffset &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + template + void operator>(ReactorBankInputMultiPortOffset &&other_bank_ports) { + origin.connect (std::move(other_bank_ports)); + } + + private: + ReactorBankOutputMultiPortOffset& origin; + }; + + void connect(MultiportInput& input); + + template + void connect(ReactorBankInputPort &&other_bank_ports); + + template + void connect(ReactorBankInputMultiPort &&other_bank_ports); + + template + void connect(ReactorBankInputPortOffset &&other_bank_ports); + + template + void connect(ReactorBankInputMultiPortOffset &&other_bank_ports); + +public: + ReactorBankOutputMultiPortOffset(std::vector>& reactors, std::ptrdiff_t offset) + : reactors(reactors), offset(offset) {} + + using iterator = typename std::vector>::iterator; + using const_iterator = typename std::vector>::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>& reactors; + std::ptrdiff_t offset; +}; + +template +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(bank_name, e_parent)); + } else { + reactors.emplace_back(std::make_unique(bank_name, r_parent)); + } + reactors.back()->bank_index_ = index++; + } + + template 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(bank_name, e_parent, std::forward(args)...)); + } else { + reactors.emplace_back(std::make_unique(bank_name, r_parent, std::forward(args)...)); + } + reactors.back()->bank_index_ = index++; + } + + template void emplace_back(Args&&... args) noexcept { + reactors.emplace_back(std::forward(args)...); + reactors.back()->bank_index_ = index++; + reactors.back()->homog_name = reactors.back()->parent ? (reactors.back()->parent->homog_name + "." + name) : name; + } + + template + std::pair>*, Input ReactorType::*> operator()(Input ReactorType::*member) { + return std::make_pair(&reactors, static_cast ReactorType::*>(member)); + } + + template + ReactorBankInputPort operator->*(Input ReactorType::*member) { + return ReactorBankInputPort(reactors, member); + } + + template + ReactorBankInputPortOffset operator->*(Input *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast(reactor.get()); + if ((reinterpret_cast(member) >= base_ptr) && (reinterpret_cast(member) < base_ptr + object_size)) { + offset = reinterpret_cast(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankInputPortOffset(reactors, offset); + } + + template + ReactorBankInputPort for_each(Input ReactorType::*member) { + return ReactorBankInputPort(reactors, member); + } + + template + ReactorBankInputPortOffset for_each(Input *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast(reactor.get()); + if ((reinterpret_cast(member) >= base_ptr) && (reinterpret_cast(member) < base_ptr + object_size)) { + offset = reinterpret_cast(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankInputPortOffset(reactors, offset); + } + + template + ReactorBankInputMultiPort operator->*(MultiportInput ReactorType::*member) { + return ReactorBankInputMultiPort(reactors, member); + } + + template + ReactorBankInputMultiPortOffset operator->*(MultiportInput *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast(reactor.get()); + if ((reinterpret_cast(member) >= base_ptr) && (reinterpret_cast(member) < base_ptr + object_size)) { + offset = reinterpret_cast(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankInputMultiPortOffset(reactors, offset); + } + + template + ReactorBankInputMultiPort for_each(MultiportInput ReactorType::*member) { + return ReactorBankInputMultiPort(reactors, member); + } + + template + ReactorBankInputMultiPortOffset for_each(MultiportInput *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast(reactor.get()); + if ((reinterpret_cast(member) >= base_ptr) && (reinterpret_cast(member) < base_ptr + object_size)) { + offset = reinterpret_cast(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankInputMultiPortOffset(reactors, offset); + } + + template + ReactorBankOutputPort operator->*(Output ReactorType::*member) { + return ReactorBankOutputPort(reactors, member); + } + + template + ReactorBankOutputPortOffset operator->*(Output *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast(reactor.get()); + if ((reinterpret_cast(member) >= base_ptr) && (reinterpret_cast(member) < base_ptr + object_size)) { + offset = reinterpret_cast(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankOutputPortOffset(reactors, offset); + } + + template + ReactorBankOutputPort for_each(Output ReactorType::*member) { + return ReactorBankOutputPort(reactors, member); + } + + template + ReactorBankOutputPortOffset for_each(Output *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast(reactor.get()); + if ((reinterpret_cast(member) >= base_ptr) && (reinterpret_cast(member) < base_ptr + object_size)) { + offset = reinterpret_cast(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankOutputPortOffset(reactors, offset); + } + + template + ReactorBankOutputMultiPort operator->*(MultiportOutput ReactorType::*member) { + return ReactorBankOutputMultiPort(reactors, member); + } + + template + ReactorBankOutputMultiPortOffset operator->*(MultiportOutput *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast(reactor.get()); + if ((reinterpret_cast(member) >= base_ptr) && (reinterpret_cast(member) < base_ptr + object_size)) { + offset = reinterpret_cast(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankOutputMultiPortOffset(reactors, offset); + } + + template + ReactorBankOutputMultiPort for_each(MultiportOutput ReactorType::*member) { + return ReactorBankOutputMultiPort(reactors, member); + } + + template + ReactorBankOutputMultiPortOffset for_each(MultiportOutput *member) { + std::size_t object_size = sizeof(ReactorType); + std::ptrdiff_t offset = -1; + + for (auto &reactor : reactors) { + const char* base_ptr = reinterpret_cast(reactor.get()); + if ((reinterpret_cast(member) >= base_ptr) && (reinterpret_cast(member) < base_ptr + object_size)) { + offset = reinterpret_cast(member) - base_ptr; + break; + } + } + + if (offset < 0) { + std::cerr << "Member passed in is not a valid member\n"; + reactor_assert(false); + } + + return ReactorBankOutputMultiPortOffset(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> 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 +#include +namespace sdk +{ + +class SystemParameterBase { +public: + virtual ~SystemParameterBase() = default; + virtual void fetch_config() = 0; + virtual void print() = 0; + virtual void populate_params(std::set &types, std::map &homog_map_entries, std::map &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 +#include +#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 std::enable_if::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 +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 variable_name = ParameterMetadata{ #variable_name, description, min_value, max_value, default_value, #Type } + +template +class SystemParametersStandalone : public SystemParameterBase { +public: + using ParameterValue = std::variant*...>; + + 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 + void register_parameters(Args&... args) { + register_parameters_(reactor->fqn(), reactor->homog_fqn(), args...); + // print(); + } + + void populate_params(std::set &types, std::map &homog_map_entries, std::map &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 param_map; + Reactor *reactor; + Environment *env; + + template + void register_parameter(const std::string& hetero_name, const std::string& homog_name, ParameterMetadata& param) { + MapEntry entry = MapEntry { + .alt_name = homog_name, + .param = ¶m + }; + param_map[hetero_name] = std::move(entry); + } + + template + void register_parameters_(const std::string& hetero_name, const std::string& homog_name, ParameterMetadata& 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 +class SystemParameters : public SystemParametersStandalone { +public: + Defaults defaults; + SystemParameters(Reactor *owner, Defaults &¶m) + : SystemParametersStandalone(owner), defaults(std::forward(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__GNUC__) && !defined(__clang__) +# if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +# define CXXOPTS_NO_REGEX true +# endif +#endif + +#ifndef CXXOPTS_NO_REGEX +# include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +#ifdef __has_include +# if __has_include() +# include +# 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 can be found by the +//compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +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' 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 + { + 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 + 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 + T + toLocalString(T&& t) + { + return std::forward(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 + String& + stringAppend(String& s, Iterator begin, Iterator end) + { + return s.append(begin, end); + } + + template + std::string + toUTF8String(T&& t) + { + return std::forward(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' 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 + { + public: + + virtual ~Value() = default; + + virtual + std::shared_ptr + 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 + default_value(const std::string& value) = 0; + + virtual std::shared_ptr + implicit_value(const std::string& value) = 0; + + virtual std::shared_ptr + 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 + void throw_or_mimic(const std::string& text) + { + static_assert(std::is_base_of::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(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(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 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(text); + } + } + return std::pair(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 integer_pattern + ("(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"); + std::basic_regex truthy_pattern + ("(t|T)(rue)?|1"); + std::basic_regex falsy_pattern + ("(f|F)(alse)?|0"); + + std::basic_regex option_matcher + ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); + std::basic_regex 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(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 SplitSwitchDef(const std::string &text) + { + std::match_results result; + std::regex_match(text.c_str(), result, option_specifier); + if (result.empty()) + { + throw_or_mimic(text); + } + + const std::string& short_sw = result[2]; + const std::string& long_sw = result[3]; + + return std::pair(short_sw, long_sw); + } + + inline ArguDesc ParseArgument(const char *arg, bool &matched) + { + std::match_results 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 + struct SignedCheck; + + template + struct SignedCheck + { + template + void + operator()(bool negative, U u, const std::string& text) + { + if (negative) + { + if (u > static_cast((std::numeric_limits::min)())) + { + throw_or_mimic(text); + } + } + else + { + if (u > static_cast((std::numeric_limits::max)())) + { + throw_or_mimic(text); + } + } + } + }; + + template + struct SignedCheck + { + template + void + operator()(bool, U, const std::string&) const {} + }; + + template + void + check_signed_range(bool negative, U value, const std::string& text) + { + SignedCheck::is_signed>()(negative, value, text); + } + } // namespace detail + + template + 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(-static_cast(t-1)-1); + } + + template + void + checked_negate(R&, T&&, const std::string& text, std::false_type) + { + throw_or_mimic(text); + } + + template + void + integer_parser(const std::string& text, T& value) + { + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); + + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::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(ch - '0'); + } + else if (base == 16 && ch >= 'a' && ch <= 'f') + { + digit = static_cast(ch - 'a' + 10); + } + else if (base == 16 && ch >= 'A' && ch <= 'F') + { + digit = static_cast(ch - 'A' + 10); + } + else + { + throw_or_mimic(text); + } + + const US next = static_cast(result * base + digit); + if (result > next) + { + throw_or_mimic(text); + } + + result = next; + } + + detail::check_signed_range(negative, result, text); + + if (negative) + { + checked_negate(value, result, text, std::integral_constant()); + } + else + { + value = static_cast(result); + } + } + + template + void stringstream_parser(const std::string& text, T& value) + { + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } + } + + template ::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(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 ::value>::type* = nullptr + > + void + parse_value(const std::string& text, T& value) { + stringstream_parser(text, value); + } + + template + void + parse_value(const std::string& text, std::vector& 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 + void + parse_value(const std::string& text, std::optional& 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(text); + } + + c = text[0]; + } + + template + struct type_is_container + { + static constexpr bool value = false; + }; + + template + struct type_is_container> + { + static constexpr bool value = true; + }; + + template + class abstract_value : public Value + { + using Self = abstract_value; + + public: + abstract_value() + : m_result(std::make_shared()) + , 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(); + 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::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 + default_value(const std::string& value) override + { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr + implicit_value(const std::string& value) override + { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr + 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::value; + } + + const T& + get() const + { + if (m_store == nullptr) + { + return *m_result; + } + return *m_store; + } + + protected: + std::shared_ptr m_result{}; + T* m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; + }; + + template + class standard_value : public abstract_value + { + public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr + clone() const override + { + return std::make_shared>(*this); + } + }; + + template <> + class standard_value : public abstract_value + { + 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 + clone() const override + { + return std::make_shared>(*this); + } + + private: + + void + set_default_and_implicit() + { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } + }; + } // namespace values + + template + std::shared_ptr + value() + { + return std::make_shared>(); + } + + template + std::shared_ptr + value(T& t) + { + return std::make_shared>(&t); + } + + class OptionAdder; + + class OptionDetails + { + public: + OptionDetails + ( + std::string short_, + std::string long_, + String desc, + std::shared_ptr 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{}(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 + 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 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 options{}; + }; + + class OptionValue + { + public: + void + parse + ( + const std::shared_ptr& 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& details) + { + ensure_value(details); + m_default = true; + m_long_name = &details->long_name(); + m_value->parse(); + } + + void + parse_no_value(const std::shared_ptr& 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 + const T& + as() const + { + if (m_value == nullptr) { + throw_or_mimic( + m_long_name == nullptr ? "" : *m_long_name); + } + +#ifdef CXXOPTS_NO_RTTI + return static_cast&>(*m_value).get(); +#else + return dynamic_cast&>(*m_value).get(); +#endif + } + + private: + void + ensure_value(const std::shared_ptr& 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 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 + 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; + using NameHashMap = std::unordered_map; + + class ParseResult + { + public: + + ParseResult() = default; + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, std::vector&& 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); + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) + { + throw_or_mimic(option); + } + + return viter->second; + } + + const std::vector& + arguments() const + { + return m_sequential; + } + + const std::vector& + unmatched() const + { + return m_unmatched; + } + + private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_unmatched{}; + }; + + struct Option + { + Option + ( + std::string opts, + std::string desc, + std::shared_ptr value = ::cxxopts::value(), + 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 value_; + std::string arg_help_; + }; + + using OptionMap = std::unordered_map>; + using PositionalList = std::vector; + 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& 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& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + void + parse_no_value(const std::shared_ptr& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector 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()) + { + } + + 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