From 5b07ca5b955f7dfe5500911a222f301360d0ba78 Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Thu, 20 Feb 2025 03:42:19 +0500 Subject: [PATCH 01/22] base changes for sdk, bug fix for export_dependency_graph to a .dot file --- include/reactor-cpp/action.hh | 2 +- include/reactor-cpp/environment.hh | 11 +++++--- include/reactor-cpp/reactor.hh | 1 + lib/environment.cc | 40 +++++++++++++++++++++++++++--- lib/reactor.cc | 2 +- 5 files changed, 47 insertions(+), 9 deletions(-) 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/lib/environment.cc b/lib/environment.cc index 52c0b6bf..a2ab4ee7 100644 --- a/lib/environment.cc +++ b/lib/environment.cc @@ -67,6 +67,26 @@ void Environment::optimize() { optimized_graph_ = graph_; } +void recursive_construct(Reactor* container) { + container->construct(); + for (auto* reactor : container->reactors()) { + recursive_construct(reactor); + } +} + +void Environment::construct() { + // phase_ = Phase::Assembly; + + log::Info() << "Start Contruction of reactors"; + for (auto* reactor : top_level_reactors_) { + recursive_construct(reactor); + } + + for (auto* env : contained_environments_) { + env->construct(); + } +} + void recursive_assemble(Reactor* container) { container->assemble(); for (auto* reactor : container->reactors()) { @@ -80,7 +100,7 @@ void Environment::assemble() { // NOLINT(readability-function-cognitive-complexi // constructing all the reactors // this mainly tell the reactors that they should connect their ports and actions not ports and ports - log::Debug() << "start assembly of reactors"; + log::Info() << "start assembly of reactors"; for (auto* reactor : top_level_reactors_) { recursive_assemble(reactor); } @@ -112,6 +132,8 @@ void Environment::assemble() { // NOLINT(readability-function-cognitive-complexi source_port->add_outward_binding(destination_port); log::Debug() << "from: " << source_port->fqn() << "(" << source_port << ")" << " --> to: " << destination_port->fqn() << "(" << destination_port << ")"; + reactor::validate(source_port != destination_port, + "Self wiring detected; from " + source_port->fqn() + " --> " + destination_port->fqn()); } } else { if (properties.type_ == ConnectionType::Enclaved || properties.type_ == ConnectionType::PhysicalEnclaved || @@ -221,6 +243,8 @@ void Environment::export_dependency_graph(const std::string& path) { std::ofstream dot; dot.open(path); + dependency_graph_and_indexes(); + // sort all reactions_ by their index std::map> reactions_by_index; for (auto* reaction : reactions_) { @@ -317,7 +341,7 @@ auto Environment::startup() -> std::thread { return startup(get_physical_time()); } -auto Environment::startup(const TimePoint& start_time) -> std::thread { +void Environment::dependency_graph_and_indexes() { validate(this->phase() == Phase::Assembly, "startup() may only be called during assembly phase!"); log::Debug() << "Building the Dependency-Graph"; @@ -327,7 +351,17 @@ auto Environment::startup(const TimePoint& start_time) -> std::thread { calculate_indexes(); - log_.debug() << "Starting the execution"; + phase_ = Phase::Indexing; +} + +auto Environment::startup(const TimePoint& start_time) -> std::thread { + if (phase_ == Phase::Assembly) { + dependency_graph_and_indexes(); + } + + validate(this->phase() == Phase::Indexing, "startup() may only be called during Indexing phase!"); + + log_.info() << "Starting the execution"; phase_ = Phase::Startup; this->start_tag_ = Tag::from_physical_time(start_time); diff --git a/lib/reactor.cc b/lib/reactor.cc index f42488b0..dc73c60e 100644 --- a/lib/reactor.cc +++ b/lib/reactor.cc @@ -47,7 +47,7 @@ void Reactor::register_output(BasePort* port) { reactor_assert(port != nullptr); reactor::validate(this->environment()->phase() == Phase::Construction, "Ports can only be registered during construction phase!"); - [[maybe_unused]] bool result = inputs_.insert(port).second; + [[maybe_unused]] bool result = outputs_.insert(port).second; reactor_assert(result); Statistics::increment_ports(); } From 0035b41520f1fbf13ec26d823497f320e8aaee4d Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Thu, 20 Feb 2025 04:38:46 +0500 Subject: [PATCH 02/22] first draft of sdk, work in progress --- CMakeLists.txt | 12 + cmake_uninstall.cmake.in | 21 + examples/sdk-SrcSink-Fanout/.gitignore | 1 + examples/sdk-SrcSink-Fanout/CMakeLists.txt | 28 + .../sdk-SrcSink-Fanout/Config-a/Config-a.cc | 23 + .../Config-a/Config-a.cmake | 16 + .../sdk-SrcSink-Fanout/Config-a/Config-a.hh | 20 + .../sdk-SrcSink-Fanout/Main/MainReactor.cc | 26 + .../sdk-SrcSink-Fanout/Main/MainReactor.cmake | 16 + .../sdk-SrcSink-Fanout/Main/MainReactor.hh | 51 ++ .../sdk-SrcSink-Fanout/Sink/SinkReactor.cc | 42 ++ .../sdk-SrcSink-Fanout/Sink/SinkReactor.cmake | 14 + .../sdk-SrcSink-Fanout/Sink/SinkReactor.hh | 47 ++ .../Source/SourceReactor.cc | 55 ++ .../Source/SourceReactor.cmake | 13 + .../Source/SourceReactor.hh | 41 ++ examples/sdk-SrcSink-Fanout/example.png | Bin 0 -> 107632 bytes examples/sdk-SrcSink-Fanout/graph.dot | 44 ++ examples/sdk-SrcSink-Fanout/main.cc | 29 + examples/sdk-SrcSink/.gitignore | 1 + examples/sdk-SrcSink/CMakeLists.txt | 28 + examples/sdk-SrcSink/Config-a/Config-a.cc | 24 + examples/sdk-SrcSink/Config-a/Config-a.cmake | 16 + examples/sdk-SrcSink/Config-a/Config-a.hh | 20 + examples/sdk-SrcSink/Main/MainReactor.cc | 49 ++ examples/sdk-SrcSink/Main/MainReactor.cmake | 16 + examples/sdk-SrcSink/Main/MainReactor.hh | 59 ++ examples/sdk-SrcSink/Sink/SinkReactor.cc | 36 + examples/sdk-SrcSink/Sink/SinkReactor.cmake | 14 + examples/sdk-SrcSink/Sink/SinkReactor.hh | 39 + examples/sdk-SrcSink/Source/SourceReactor.cc | 69 ++ .../sdk-SrcSink/Source/SourceReactor.cmake | 13 + examples/sdk-SrcSink/Source/SourceReactor.hh | 50 ++ examples/sdk-SrcSink/example.png | Bin 0 -> 107632 bytes examples/sdk-SrcSink/graph.dot | 44 ++ examples/sdk-SrcSink/main.cc | 29 + examples/sdk-deadlines/CMakeLists.txt | 27 + examples/sdk-deadlines/Config-a/Config-a.cc | 25 + .../sdk-deadlines/Config-a/Config-a.cmake | 16 + examples/sdk-deadlines/Config-a/Config-a.hh | 20 + examples/sdk-deadlines/Main/MainReactor.cc | 17 + examples/sdk-deadlines/Main/MainReactor.cmake | 16 + examples/sdk-deadlines/Main/MainReactor.hh | 40 ++ examples/sdk-deadlines/Node/NodeReactor.cc | 26 + examples/sdk-deadlines/Node/NodeReactor.cmake | 16 + examples/sdk-deadlines/Node/NodeReactor.hh | 46 ++ examples/sdk-deadlines/main.cc | 29 + include/reactor-sdk/BaseTrigger.hh | 20 + include/reactor-sdk/ConfigParameters.hh | 115 +++ include/reactor-sdk/Environment.hh | 26 + include/reactor-sdk/InputPort.hh | 49 ++ include/reactor-sdk/Misc.hh | 81 +++ include/reactor-sdk/MultiportInput.hh | 65 ++ include/reactor-sdk/MultiportOutput.hh | 108 +++ include/reactor-sdk/OutputPort.hh | 105 +++ include/reactor-sdk/Reaction.hh | 216 ++++++ include/reactor-sdk/Reactor.hh | 166 +++++ include/reactor-sdk/ReactorBank.hh | 667 ++++++++++++++++++ include/reactor-sdk/SystemParameterBase.hh | 13 + include/reactor-sdk/SystemParameters.hh | 81 +++ include/reactor-sdk/impl/Connection.hh | 108 +++ .../impl/InputMultiport_wiring_impl.hh | 39 + .../reactor-sdk/impl/InputPort_wiring_impl.hh | 58 ++ .../impl/OutputMultiport_wiring_impl.hh | 163 +++++ .../impl/OutputPort_wiring_impl.hh | 161 +++++ .../ReactorBankOutputMultiport_wiring_impl.hh | 389 ++++++++++ .../impl/ReactorBankOutputPort_wiring_impl.hh | 313 ++++++++ include/reactor-sdk/reactor-sdk.hh | 16 + lib/reactor.cc | 5 +- lib/reactor_element.cc | 3 +- reactor-sdk/CMakeLists.txt | 20 + reactor-sdk/Environment.cc | 56 ++ reactor-sdk/Reactor.cc | 45 ++ test/ActionDelay/CMakeLists.txt | 23 + test/ActionDelay/main.cc | 141 ++++ test/ActionIsPresent/CMakeLists.txt | 23 + test/ActionIsPresent/main.cc | 97 +++ test/Deadlines/CMakeLists.txt | 23 + test/Deadlines/main.cc | 152 ++++ 79 files changed, 4828 insertions(+), 3 deletions(-) create mode 100644 cmake_uninstall.cmake.in create mode 100644 examples/sdk-SrcSink-Fanout/.gitignore create mode 100644 examples/sdk-SrcSink-Fanout/CMakeLists.txt create mode 100644 examples/sdk-SrcSink-Fanout/Config-a/Config-a.cc create mode 100644 examples/sdk-SrcSink-Fanout/Config-a/Config-a.cmake create mode 100644 examples/sdk-SrcSink-Fanout/Config-a/Config-a.hh create mode 100644 examples/sdk-SrcSink-Fanout/Main/MainReactor.cc create mode 100644 examples/sdk-SrcSink-Fanout/Main/MainReactor.cmake create mode 100644 examples/sdk-SrcSink-Fanout/Main/MainReactor.hh create mode 100644 examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cc create mode 100644 examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cmake create mode 100644 examples/sdk-SrcSink-Fanout/Sink/SinkReactor.hh create mode 100644 examples/sdk-SrcSink-Fanout/Source/SourceReactor.cc create mode 100644 examples/sdk-SrcSink-Fanout/Source/SourceReactor.cmake create mode 100644 examples/sdk-SrcSink-Fanout/Source/SourceReactor.hh create mode 100644 examples/sdk-SrcSink-Fanout/example.png create mode 100644 examples/sdk-SrcSink-Fanout/graph.dot create mode 100644 examples/sdk-SrcSink-Fanout/main.cc create mode 100644 examples/sdk-SrcSink/.gitignore create mode 100644 examples/sdk-SrcSink/CMakeLists.txt create mode 100644 examples/sdk-SrcSink/Config-a/Config-a.cc create mode 100644 examples/sdk-SrcSink/Config-a/Config-a.cmake create mode 100644 examples/sdk-SrcSink/Config-a/Config-a.hh create mode 100644 examples/sdk-SrcSink/Main/MainReactor.cc create mode 100644 examples/sdk-SrcSink/Main/MainReactor.cmake create mode 100644 examples/sdk-SrcSink/Main/MainReactor.hh create mode 100644 examples/sdk-SrcSink/Sink/SinkReactor.cc create mode 100644 examples/sdk-SrcSink/Sink/SinkReactor.cmake create mode 100644 examples/sdk-SrcSink/Sink/SinkReactor.hh create mode 100644 examples/sdk-SrcSink/Source/SourceReactor.cc create mode 100644 examples/sdk-SrcSink/Source/SourceReactor.cmake create mode 100644 examples/sdk-SrcSink/Source/SourceReactor.hh create mode 100644 examples/sdk-SrcSink/example.png create mode 100644 examples/sdk-SrcSink/graph.dot create mode 100644 examples/sdk-SrcSink/main.cc create mode 100644 examples/sdk-deadlines/CMakeLists.txt create mode 100644 examples/sdk-deadlines/Config-a/Config-a.cc create mode 100644 examples/sdk-deadlines/Config-a/Config-a.cmake create mode 100644 examples/sdk-deadlines/Config-a/Config-a.hh create mode 100644 examples/sdk-deadlines/Main/MainReactor.cc create mode 100644 examples/sdk-deadlines/Main/MainReactor.cmake create mode 100644 examples/sdk-deadlines/Main/MainReactor.hh create mode 100644 examples/sdk-deadlines/Node/NodeReactor.cc create mode 100644 examples/sdk-deadlines/Node/NodeReactor.cmake create mode 100644 examples/sdk-deadlines/Node/NodeReactor.hh create mode 100644 examples/sdk-deadlines/main.cc create mode 100644 include/reactor-sdk/BaseTrigger.hh create mode 100644 include/reactor-sdk/ConfigParameters.hh create mode 100644 include/reactor-sdk/Environment.hh create mode 100644 include/reactor-sdk/InputPort.hh create mode 100644 include/reactor-sdk/Misc.hh create mode 100644 include/reactor-sdk/MultiportInput.hh create mode 100644 include/reactor-sdk/MultiportOutput.hh create mode 100644 include/reactor-sdk/OutputPort.hh create mode 100644 include/reactor-sdk/Reaction.hh create mode 100644 include/reactor-sdk/Reactor.hh create mode 100644 include/reactor-sdk/ReactorBank.hh create mode 100644 include/reactor-sdk/SystemParameterBase.hh create mode 100644 include/reactor-sdk/SystemParameters.hh create mode 100644 include/reactor-sdk/impl/Connection.hh create mode 100644 include/reactor-sdk/impl/InputMultiport_wiring_impl.hh create mode 100644 include/reactor-sdk/impl/InputPort_wiring_impl.hh create mode 100644 include/reactor-sdk/impl/OutputMultiport_wiring_impl.hh create mode 100644 include/reactor-sdk/impl/OutputPort_wiring_impl.hh create mode 100644 include/reactor-sdk/impl/ReactorBankOutputMultiport_wiring_impl.hh create mode 100644 include/reactor-sdk/impl/ReactorBankOutputPort_wiring_impl.hh create mode 100644 include/reactor-sdk/reactor-sdk.hh create mode 100644 reactor-sdk/CMakeLists.txt create mode 100644 reactor-sdk/Environment.cc create mode 100644 reactor-sdk/Reactor.cc create mode 100644 test/ActionDelay/CMakeLists.txt create mode 100644 test/ActionDelay/main.cc create mode 100644 test/ActionIsPresent/CMakeLists.txt create mode 100644 test/ActionIsPresent/main.cc create mode 100644 test/Deadlines/CMakeLists.txt create mode 100644 test/Deadlines/main.cc 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..8cac6354 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/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 { 0 } } + }; +} + +ConfigParameter::ParametersMap UserParameters::heterogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata { 20 } }, + {"Main.Sink.n_ports", ConfigParameterMetadata { 2 } } + + }; +} + +// UserParameters::filter_out () { +// if (cfg_map["T0.P0.L1.n_ervers"] != cfg_map["T0.P0.L2.n_ervers"]) { + +// } +// } 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..2187270b --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Config-a/Config-a.hh @@ -0,0 +1,20 @@ +#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(); +}; + +// using ParametersMap = std::map...>>>; + +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..59e40e87 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Main/MainReactor.cc @@ -0,0 +1,26 @@ +#include "MainReactor.hh" + +void MainReactor::construction() { + + cout << "Construction Main\n"; + + src = std::make_unique("Source", this); + snk = std::make_unique("Sink", this); +} + +void MainReactor::assembling() { + cout << "Assembling Main\n"; + + src->req -->> snk->req; + snk->rsp --> src->rsp; + + reaction("reaction_1"). + triggers(&startup). + effects(). + function( + [&](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..bbe48e49 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Main/MainReactor.hh @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "Source/SourceReactor.hh" +#include "Sink/SinkReactor.hh" + +using namespace sdk; + +class MainReactor: public Reactor { +public: + struct Parameters : public SystemParameter { + ParameterMetadata alias = ParameterMetadata { + .name = "alias", + .description = "Alternate name", + .min_value = "another", + .max_value = "another", + .value = "another" + }; + + ParameterMetadata log_level = ParameterMetadata { + .name = "log_level", + .description = "Log level", + .min_value = 0, + .max_value = 1, + .value = 1 + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (alias, log_level); + } + }; + +private: + Parameters parameters{this}; + + std::unique_ptr src; + std::unique_ptr snk; + +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 assembling() 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..66690970 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.cc @@ -0,0 +1,42 @@ +#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::assembling() { + + cout << "Assembling Sink\n"; + + reaction("startup_reaction"). + triggers(&startup). + effects(). + function(pass_function(startup_reaction) + ); + + reaction("process_request"). + triggers(&req). + effects(&rsp). + function(pass_function(process_request) + ); +} + + + +void 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 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..ecea3d87 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Sink/SinkReactor.hh @@ -0,0 +1,47 @@ +#pragma once + +#include +using namespace std; +using namespace sdk; + +class SinkReactor : public Reactor { +public: + struct Parameters : public SystemParameter { + ParameterMetadata name = ParameterMetadata { + .name = "Name", + .description = "Alternate name", + .min_value = "Sink", + .max_value = "Sink", + .value = "Sink" + }; + + ParameterMetadata n_ports = ParameterMetadata { + .name = "n_ports", + .description = "Size of multiports", + .min_value = 1, + .max_value = 10, + .value = 1 + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (name, n_ports); + } + }; + + SinkReactor(const std::string &name, Environment *env) + : Reactor(name, env) {} + SinkReactor(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Parameters parameters{this}; + + MultiportInput req{"req", this}; + MultiportOutput rsp{"rsp", this}; + + void construction() override; + void assembling() override; + + void startup_reaction (Startup &startup); + void process_request (MultiportInput& req, MultiportOutput& rsp); +}; diff --git a/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cc b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cc new file mode 100644 index 00000000..fececbf8 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.cc @@ -0,0 +1,55 @@ + +#include "SourceReactor.hh" +using namespace std; + +void SourceReactor::construction() { + + cout << "Construction Source iterations:" << parameters.iterations.value << "\n"; +} + +void SourceReactor::assembling() { + cout << "Assembling Source iterations:" << parameters.iterations.value << "\n"; + reaction("reaction_1"). + triggers(&startup). + effects(&sch). + function( + [&](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(&sch). + effects(&req). + function( + [&](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(&rsp). + effects(&sch). + function( + [&](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..daa2d9a9 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/Source/SourceReactor.hh @@ -0,0 +1,41 @@ +#pragma once + +#include +using namespace std; +using namespace sdk; + +class SourceReactor : public Reactor { +public: + struct Parameters : public SystemParameter { + + ParameterMetadata iterations = ParameterMetadata { + .name = "iterations", + .description = "Number of iterations", + .min_value = 1, + .max_value = 100, + .value = 10 + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (iterations); + } + }; +private: + Parameters parameters{this}; + + std::string name = "Source"; + int itr = 0; +public: + SourceReactor(const std::string &name, Environment *env) + : Reactor(name, env) {} + SourceReactor(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + LogicalAction sch{"sch", this}; + Input rsp{"rsp", this}; + Output req{"req", this}; + + void construction() override; + void assembling() 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 0000000000000000000000000000000000000000..ba059554a7c45df942f2849d53332b0708c2f3f3 GIT binary patch literal 107632 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 Q%qAW&wmaDt+IU6&2O%7xc>n+a literal 0 HcmV?d00001 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..32b7d893 --- /dev/null +++ b/examples/sdk-SrcSink-Fanout/main.cc @@ -0,0 +1,29 @@ + +#include + +#include "Config-a/Config-a.hh" +#include "Main/MainReactor.hh" + +using namespace std; +using namespace sdk; + +int main(int argc, char **argv) { + unsigned workers = 1; + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + + bool visualize = false; + + if (argc > 1) { + string v_str = argv[1]; + visualize = (v_str == "true") ? true : false; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << visualize << std::endl; + + Environment sim {&cfg_parameters, workers, fast, timeout, visualize}; + 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..c93bef8d --- /dev/null +++ b/examples/sdk-SrcSink/Config-a/Config-a.cc @@ -0,0 +1,24 @@ +#include "Config-a.hh" + +UserParameters cfg_parameters; + +ConfigParameter::ParametersMap UserParameters::homogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata { 0 } } + }; +} + +ConfigParameter::ParametersMap UserParameters::heterogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata { 20 } }, + {"Main.Source.n_ports", ConfigParameterMetadata { 2 } }, + {"Main.n_sinks", ConfigParameterMetadata { 2 } }, + + }; +} + +// UserParameters::filter_out () { +// if (cfg_map["T0.P0.L1.n_ervers"] != cfg_map["T0.P0.L2.n_ervers"]) { + +// } +// } 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..2187270b --- /dev/null +++ b/examples/sdk-SrcSink/Config-a/Config-a.hh @@ -0,0 +1,20 @@ +#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(); +}; + +// using ParametersMap = std::map...>>>; + +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..4c937cd1 --- /dev/null +++ b/examples/sdk-SrcSink/Main/MainReactor.cc @@ -0,0 +1,49 @@ +#include "MainReactor.hh" + +void MainReactor::construction() { + + cout << "Construction Main n_sinks:" << parameters.n_sinks.value << "\n"; + + src = std::make_unique("Source", this); + + for (int __lf_idx = 0; __lf_idx < parameters.n_sinks.value; __lf_idx++) { + std::string __lf_inst_name = "Sink_" + std::to_string(__lf_idx); + snk.emplace_back(std::make_unique(__lf_inst_name, this)); + } +} + +void MainReactor::assembling() { + cout << "Assembling Main n_sinks:" << parameters.n_sinks.value << "\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 + + reaction("reaction_1"). + triggers(&startup). + effects(). + function( + [&](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; + } + ); + + reaction("reaction_2"). + triggers(&snk[0].rsp). + effects(). + function( + [&](Output& rsp) { + cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << + "Response\n" << "Bank:" << bank_index << " value:" << *rsp.get() << 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..10bf5fb1 --- /dev/null +++ b/examples/sdk-SrcSink/Main/MainReactor.hh @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include "Source/SourceReactor.hh" +#include "Sink/SinkReactor.hh" + +using namespace sdk; + +class MainReactor: public Reactor { +public: + struct Parameters : public SystemParameter { + ParameterMetadata alias = ParameterMetadata { + .name = "alias", + .description = "Alternate name", + .min_value = "another", + .max_value = "another", + .value = "another" + }; + + ParameterMetadata n_sinks = ParameterMetadata { + .name = "n_sinks", + .description = "Sink reactors bank width", + .min_value = 1, + .max_value = 10, + .value = 1 + }; + + ParameterMetadata log_level = ParameterMetadata { + .name = "log_level", + .description = "Log level", + .min_value = 0, + .max_value = 1, + .value = 1 + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (alias, n_sinks, log_level); + } + }; + +private: + Parameters parameters{this}; + + std::unique_ptr src; + ReactorBank snk; + +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 assembling() override; +}; + + diff --git a/examples/sdk-SrcSink/Sink/SinkReactor.cc b/examples/sdk-SrcSink/Sink/SinkReactor.cc new file mode 100644 index 00000000..9c53305b --- /dev/null +++ b/examples/sdk-SrcSink/Sink/SinkReactor.cc @@ -0,0 +1,36 @@ +#include "SinkReactor.hh" +using namespace std; + +void SinkReactor::construction() { + cout << "Construction Sink\n"; +} + +void SinkReactor::assembling() { + + cout << "Assembling Sink\n"; + + reaction("startup_reaction"). + triggers(&startup). + effects(). + function(pass_function(startup_reaction) + ); + + reaction("process_request"). + triggers(&req). + effects(&rsp). + function(pass_function(process_request) + ); +} + + + +void 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 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..64700c5d --- /dev/null +++ b/examples/sdk-SrcSink/Sink/SinkReactor.hh @@ -0,0 +1,39 @@ +#pragma once + +#include +using namespace std; +using namespace sdk; + +class SinkReactor : public Reactor { +public: + struct Parameters : public SystemParameter { + ParameterMetadata name = ParameterMetadata { + .name = "Name", + .description = "Alternate name", + .min_value = "Sink", + .max_value = "Sink", + .value = "Sink" + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (name); + } + }; + + SinkReactor(const std::string &name, Environment *env) + : Reactor(name, env) {} + SinkReactor(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Parameters parameters{this}; + + Input req{"req", this}; + Output rsp{"rsp", this}; + + void construction() override; + void assembling() override; + + void startup_reaction (Startup &startup); + void process_request (Input& req, Output& rsp); +}; diff --git a/examples/sdk-SrcSink/Source/SourceReactor.cc b/examples/sdk-SrcSink/Source/SourceReactor.cc new file mode 100644 index 00000000..3919c71a --- /dev/null +++ b/examples/sdk-SrcSink/Source/SourceReactor.cc @@ -0,0 +1,69 @@ + +#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::assembling() { + cout << "Assembling Source n_ports:" << parameters.n_ports.value << "\n"; + reaction("reaction_1"). + triggers(&startup). + effects(&sch). + function( + [&](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(&sch). + effects(&req). + function( + [&](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(&rsp). + effects(&sch). + function( + [&](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..f363be5d --- /dev/null +++ b/examples/sdk-SrcSink/Source/SourceReactor.hh @@ -0,0 +1,50 @@ +#pragma once + +#include +using namespace std; +using namespace sdk; + +class SourceReactor : public Reactor { +public: + struct Parameters : public SystemParameter { + + ParameterMetadata iterations = ParameterMetadata { + .name = "iterations", + .description = "Number of iterations", + .min_value = 1, + .max_value = 100, + .value = 10 + }; + + ParameterMetadata n_ports = ParameterMetadata { + .name = "n_ports", + .description = "Size of multiports", + .min_value = 1, + .max_value = 10, + .value = 1 + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (iterations, n_ports); + } + }; +private: + Parameters parameters{this}; + + std::string name = "Source"; + int itr = 0; + int rsp_itr = 0; +public: + SourceReactor(const std::string &name, Environment *env) + : Reactor(name, env) {} + SourceReactor(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + LogicalAction sch{"sch", this}; + MultiportInput rsp{"rsp", this}; + MultiportOutput req{"req", this}; + + void construction() override; + void assembling() 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 0000000000000000000000000000000000000000..ba059554a7c45df942f2849d53332b0708c2f3f3 GIT binary patch literal 107632 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 Q%qAW&wmaDt+IU6&2O%7xc>n+a literal 0 HcmV?d00001 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..32b7d893 --- /dev/null +++ b/examples/sdk-SrcSink/main.cc @@ -0,0 +1,29 @@ + +#include + +#include "Config-a/Config-a.hh" +#include "Main/MainReactor.hh" + +using namespace std; +using namespace sdk; + +int main(int argc, char **argv) { + unsigned workers = 1; + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + + bool visualize = false; + + if (argc > 1) { + string v_str = argv[1]; + visualize = (v_str == "true") ? true : false; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << visualize << std::endl; + + Environment sim {&cfg_parameters, workers, fast, timeout, visualize}; + auto main = new MainReactor("Main", &sim); + + sim.run(); + return 0; +} 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..b008eded --- /dev/null +++ b/examples/sdk-deadlines/Config-a/Config-a.cc @@ -0,0 +1,25 @@ +#include "Config-a.hh" + +UserParameters cfg_parameters; + +ConfigParameter::ParametersMap UserParameters::homogeneous_config() { + return { + {"Main.Source.iterations", ConfigParameterMetadata { 0 } } + }; +} + +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 } } + }; +} + +// UserParameters::filter_out () { +// if (cfg_map["T0.P0.L1.n_ervers"] != cfg_map["T0.P0.L2.n_ervers"]) { + +// } +// } 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..6e88372b --- /dev/null +++ b/examples/sdk-deadlines/Config-a/Config-a.hh @@ -0,0 +1,20 @@ +#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(); +}; + +// using ParametersMap = std::map...>>>; + +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..38a738d7 --- /dev/null +++ b/examples/sdk-deadlines/Main/MainReactor.cc @@ -0,0 +1,17 @@ +#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 __lf_idx = 0; __lf_idx < parameters.n_fast.value; __lf_idx++) { + std::string __lf_inst_name = "fast_" + std::to_string(__lf_idx); + fast.emplace_back(std::make_unique(__lf_inst_name, this)); + } +} + +void MainReactor::assembling() { + std::cout << "Assembling 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..845c3b40 --- /dev/null +++ b/examples/sdk-deadlines/Main/MainReactor.hh @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "Node/NodeReactor.hh" + +using namespace sdk; + +class MainReactor: public Reactor { +public: + struct Parameters : public SystemParameter { + ParameterMetadata n_fast = ParameterMetadata { + .name = "n_fast", + .description = "Number of fast nodes", + .min_value = 1, + .max_value = 10, + .value = 2 + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (n_fast); + } + }; +private: + Parameters parameters{this}; + std::unique_ptr slow; + ReactorBank fast; + +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 assembling() override; +}; + + diff --git a/examples/sdk-deadlines/Node/NodeReactor.cc b/examples/sdk-deadlines/Node/NodeReactor.cc new file mode 100644 index 00000000..154c0b15 --- /dev/null +++ b/examples/sdk-deadlines/Node/NodeReactor.cc @@ -0,0 +1,26 @@ +#include "NodeReactor.hh" + +void NodeReactor::construction() { + std::cout << "Construction:" << fqn() << " period:" << parameters.period.value << " duration:" << parameters.duration.value << "\n"; +} + +void NodeReactor::assembling() { + std::cout << "Assembling Node\n"; + + reaction("reaction_1"). + triggers(&startup, &a). + effects(). + function( + [&](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, + [&](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..d5b0fcb5 --- /dev/null +++ b/examples/sdk-deadlines/Node/NodeReactor.hh @@ -0,0 +1,46 @@ +#pragma once + +#include + +using namespace sdk; + +class NodeReactor: public Reactor { +public: + struct Parameters : public SystemParameter { + ParameterMetadata period = ParameterMetadata { + .name = "period", + .description = "Schedule and deadline period", + .min_value = 10ms, + .max_value = 10s, + .value = 500ms + }; + + ParameterMetadata duration = ParameterMetadata { + .name = "duration", + .description = "Sleep duration", + .min_value = 5ms, + .max_value = 5s, + .value = 10ms + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (period, duration); + } + }; + +private: + Parameters parameters{this}; + LogicalAction a{"a", this}; + +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 assembling() override; +}; + + diff --git a/examples/sdk-deadlines/main.cc b/examples/sdk-deadlines/main.cc new file mode 100644 index 00000000..32b7d893 --- /dev/null +++ b/examples/sdk-deadlines/main.cc @@ -0,0 +1,29 @@ + +#include + +#include "Config-a/Config-a.hh" +#include "Main/MainReactor.hh" + +using namespace std; +using namespace sdk; + +int main(int argc, char **argv) { + unsigned workers = 1; + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + + bool visualize = false; + + if (argc > 1) { + string v_str = argv[1]; + visualize = (v_str == "true") ? true : false; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << visualize << std::endl; + + Environment sim {&cfg_parameters, workers, fast, timeout, visualize}; + auto main = new MainReactor("Main", &sim); + + sim.run(); + return 0; +} diff --git a/include/reactor-sdk/BaseTrigger.hh b/include/reactor-sdk/BaseTrigger.hh new file mode 100644 index 00000000..0031576b --- /dev/null +++ b/include/reactor-sdk/BaseTrigger.hh @@ -0,0 +1,20 @@ +#pragma once + +namespace sdk +{ +class Reactor; + +class BaseTrigger : public std::enable_shared_from_this +{ +public: + Reactor *reactor; + std::shared_ptr next; + std::string name; + +public: + BaseTrigger(std::string name, Reactor *parent) + : reactor(parent), next(nullptr), name (name) {} + virtual ~BaseTrigger() = default; +}; + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/ConfigParameters.hh b/include/reactor-sdk/ConfigParameters.hh new file mode 100644 index 00000000..c544c438 --- /dev/null +++ b/include/reactor-sdk/ConfigParameters.hh @@ -0,0 +1,115 @@ +#pragma once + +#include +#include + +namespace sdk +{ + +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 void pull_config_parameter(const std::string &key, void *user_param, const std::type_info& ti) = 0; + + template + void PullConfigParameter(const std::string &key, ParameterMetadata* user_param) { + pull_config_parameter(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; + void pull_config_parameter(const std::string &key, void *user_param, const std::type_info& ti) override { + auto itr_system = hetero_param_map.find(key); + if (itr_system != hetero_param_map.end()) { + auto v_it = hetero_invalid_keys.find(key); + if (v_it != hetero_invalid_keys.end()) { + hetero_invalid_keys.erase(v_it); + } + std::visit([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 ((system_param.values[0] < param->min_value) || + (system_param.values[0] > param->max_value)) { + reactor::log::Error() << "Error: 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); + } + } + +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() << "Invalid key:" << key << "\n"; + } + return hetero_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..75a6f5f4 --- /dev/null +++ b/include/reactor-sdk/Environment.hh @@ -0,0 +1,26 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" +#include "ConfigParameters.hh" + +namespace sdk +{ + +class Environment: public reactor::Environment { +private: + ConfigParameterBase *config_parameters; + bool visualize = 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 visualize = false); + + Environment(const Environment&) = delete; + Environment& operator=(const Environment&) = delete; + void run(); + + 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..dca303c3 --- /dev/null +++ b/include/reactor-sdk/InputPort.hh @@ -0,0 +1,49 @@ +#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); + } + + private: + Input& origin; + }; + + void connect(Input& input); + void connect(MultiportInput& input); + void connect_fanout(MultiportInput& input); + +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..2d65b8f2 --- /dev/null +++ b/include/reactor-sdk/Misc.hh @@ -0,0 +1,81 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" + +namespace sdk +{ + +class Reactor; + +template +using LogicalAction = reactor::LogicalAction; + +using Startup = reactor::StartupTrigger; +using Shutdown = reactor::ShutdownTrigger; + +using Duration = std::chrono::nanoseconds; + +#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; +}; + + +} // 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..9f25733a --- /dev/null +++ b/include/reactor-sdk/OutputPort.hh @@ -0,0 +1,105 @@ +#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)); + } + + 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); + +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..d7f58761 --- /dev/null +++ b/include/reactor-sdk/Reaction.hh @@ -0,0 +1,216 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" +#include "BaseTrigger.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; + + + +template +class ReactionOutput: public BaseTrigger +{ +private: + InputTuple input_triggers; + OutputTuple output_triggers; + +public: + explicit ReactionOutput(std::string name, Reactor *parent, InputTuple inputs, OutputTuple outputs) + : BaseTrigger (name, parent), input_triggers(std::move(inputs)), output_triggers(std::move(outputs)) {} + ~ReactionOutput() {} + + template + void operator=(Fn func) + { + auto ReactionRef = std::make_shared> (name, reactor, std::move(input_triggers), std::move(output_triggers), std::forward(func)); + ReactionRef->execute(); + } + + template + Reaction &function(Fn func) + { + auto ReactionRef = std::make_shared> (name, reactor, std::move(input_triggers), std::move(output_triggers), std::forward(func)); + ReactionRef->execute(); + return *ReactionRef; + } +}; + +template +class ReactionInput: public BaseTrigger +{ +private: + InputTuple input_triggers; + +public: + explicit ReactionInput(std::string name, Reactor *parent, InputTuple inputs) + : BaseTrigger (name, parent), input_triggers(std::move(inputs)) {} + ~ReactionInput() {} + + template + ReactionOutput> &operator>(std::tuple &&outputs) + { + auto ReactionOutputRef = std::make_shared>> (name, reactor, std::move(input_triggers), std::move(outputs)); + next = ReactionOutputRef; + return *ReactionOutputRef; + } + + 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(output_tuple)); + next = ReactionOutputRef; + return *ReactionOutputRef; + } +}; + +template +class Reaction: public BaseTrigger +{ +private: + InputTuple input_triggers; + 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_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(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, OutputTuple outputs, Fn func) + : BaseTrigger(name, parent), input_triggers(std::move(inputs)), 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, 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->output_triggers)); + }; + + reaction = std::make_unique(name, priority, reactor, reactor_func); + + set_input_triggers(reaction, input_triggers); + set_output_triggers(reaction, output_triggers); + + } + + template + void deadline(reactor::Duration deadline_period, Dfn fn) + { + reactor->validate_reaction (fn, input_triggers, 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->output_triggers)); + }; + + reaction->set_deadline(deadline_period, deadline_func); + } +}; + +} // 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..50b75c43 --- /dev/null +++ b/include/reactor-sdk/Reactor.hh @@ -0,0 +1,166 @@ +#pragma once + +#include "reactor-cpp/reactor-cpp.hh" +#include +#include "BaseTrigger.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: + SystemParameterBase *p_param = nullptr; + Environment *env; + std::string current_reaction_name; + std::unordered_map> reaction_map; + int priority = 1; + std::set child_reactors; + + 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 outputs) { + (void)func; + (void)inputs; + (void)outputs; + static_assert( + std::is_invocable_v< + Fn, + typename trigger_value_type::type..., + typename trigger_value_type::type... + >, + "Reaction function parameters must match the declared input and output types."); + } + +public: + size_t bank_index = 0; + + Reactor(const std::string &name, Environment *env); + + Reactor(const std::string &name, Reactor *container); + + void set_param (SystemParameterBase *param) { p_param = param; } + + Environment *get_env() { return env; } + + Reactor &reaction (const std::string name); + + template + ReactionInput> &operator()(Inputs&&... inputs) + { + auto input_tuple = std::make_tuple(inputs...); + auto ReactionInputRef = std::make_shared>> (current_reaction_name, this, std::move(input_tuple)); + reaction_map[current_reaction_name] = ReactionInputRef; + return *ReactionInputRef; + } + + template + ReactionInput> &triggers(Inputs&&... inputs) + { + auto input_tuple = std::make_tuple(inputs...); + auto ReactionInputRef = std::make_shared>> (current_reaction_name, this, std::move(input_tuple)); + reaction_map[current_reaction_name] = ReactionInputRef; + return *ReactionInputRef; + } + + template + void reaction( const std::string &name, + std::tuple &&inputs, + std::tuple &&outputs, + Fn &&function) + { + auto ReactionRef = std::make_shared, std::tuple>>(name, this, std::move(inputs), std::move(outputs), std::forward(function)); + ReactionRef->execute(); + } + + void request_stop() { environment()->sync_shutdown(); } + virtual void construction() = 0; + virtual void assembling() = 0; + void construct() override; + void assemble() override; + + template + friend class Reaction; +}; + +} // 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..4c0dd03e --- /dev/null +++ b/include/reactor-sdk/ReactorBank.hh @@ -0,0 +1,667 @@ +#pragma once + +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() = default; + + void reserve(std::size_t size) noexcept { + reactors.reserve(size); + } + + template void emplace_back(Args&&... args) noexcept { + reactors.emplace_back(std::forward(args)...); + reactors.back()->bank_index = index++; + } + + 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; +}; + + +} // 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..dc9fb214 --- /dev/null +++ b/include/reactor-sdk/SystemParameterBase.hh @@ -0,0 +1,13 @@ +#pragma once + +namespace sdk +{ + +class SystemParameterBase { +public: + virtual ~SystemParameterBase() = default; + virtual void fetch_config() = 0; + virtual void print() = 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..551c81f7 --- /dev/null +++ b/include/reactor-sdk/SystemParameters.hh @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include "SystemParameterBase.hh" +#include "Environment.hh" + +namespace sdk +{ + +template +struct ParameterMetadata { + std::string name; + std::string description; + T min_value; + T max_value; + T value; +}; + +template +class SystemParameter : public SystemParameterBase { +public: + using ParameterValue = std::variant*...>; + + SystemParameter(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(entry.first, paramMetadataPtr); + }, entry.second); + } + } + } + + void print() override { + for (const auto& entry : param_map) { + std::cout << "Parameter: " << entry.first << ", "; + + std::visit([](auto&& param) { + int status; + char* demangled_name = abi::__cxa_demangle(typeid(param->value).name(), nullptr, nullptr, &status); + std::free(demangled_name); + std::cout << "Description: " << param->description + << ", Value: " << param->value + << ", Value Type Key: " << typeid(param->value).name() + << ", Value Type: " << type_convert [typeid(param->value).name()] + << std::endl; + }, entry.second); + } + } + + template + void register_parameters(Args&... args) { + register_parameters_(reactor->fqn(), args...); + } + +private: + std::map param_map; + Reactor *reactor; + Environment *env; + + template + void register_parameter(const std::string& name, ParameterMetadata& param) { + param_map[name] = ¶m; + } + + template + void register_parameters_(const std::string& base_name, ParameterMetadata& first, Args&... args) { + register_parameter(base_name + "." + first.name, first); + if constexpr (sizeof...(args) > 0) { + register_parameters_(base_name, args...); + } + } +}; + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/impl/Connection.hh b/include/reactor-sdk/impl/Connection.hh new file mode 100644 index 00000000..b359533e --- /dev/null +++ b/include/reactor-sdk/impl/Connection.hh @@ -0,0 +1,108 @@ +#pragma once + +#include "reactor-cpp/port.hh" + +/* +Input +MultiportInput +ReactorBank_Input +ReactorBank_MultiportInput + +Output +MultiportOutput +ReactorBank_Output +ReactorBank_MultiportOutput + +Input -> Input +Input -> MultiportInput * +Input -> ReactorBank_Input * +Input -> ReactorBank_MultiportInput * + +Output -> Input +Output -> MultiportInput * +Output -> ReactorBank_Input * +Output -> ReactorBank_MultiportInput * +Output -> Output +Output -> MultiportOutput + +MultiportInput -> Input +MultiportInput -> MultiportInput +MultiportInput -> ReactorBank_Input +MultiportInput -> ReactorBank_MultiportInput + +MultiportOutput -> Input +MultiportOutput -> MultiportInput +MultiportOutput -> ReactorBank_Input +MultiportOutput -> ReactorBank_MultiportInput +MultiportOutput -> Output +MultiportOutput -> MultiportOutput + +ReactorBank_Output -> Input +ReactorBank_Output -> MultiportInput +ReactorBank_Output -> ReactorBank_Input +ReactorBank_Output -> ReactorBank_MultiportInput +ReactorBank_Output -> Output +ReactorBank_Output -> MultiportOutput + +ReactorBank_MultiportOutput -> Input +ReactorBank_MultiportOutput -> MultiportInput +ReactorBank_MultiportOutput -> ReactorBank_Input +ReactorBank_MultiportOutput -> ReactorBank_MultiportInput +ReactorBank_MultiportOutput -> Output +ReactorBank_MultiportOutput -> MultiportOutput + +*/ + +template +void display_(std::set*> &left_ports, std::set*> &right_ports) { + reactor::log::Warn() << "Left Ports:"; + for (auto *left_port : left_ports) { + reactor::log::Warn() << "\t" << left_port->fqn(); + } + reactor::log::Warn() << "Right Ports:"; + for (auto *right_port : right_ports) { + reactor::log::Warn() << "\t" << right_port->fqn(); + } +} + +template +void connect_( std::set*> &left_ports, std::set*> &right_ports, + reactor::ConnectionProperties &&property) { + if (left_ports.size() < right_ports.size()) { + reactor::log::Warn() << "There are more right ports (" << right_ports.size() << ") than left ports (" << left_ports.size() << ")"; + display_ (left_ports, right_ports); + } else if (left_ports.size() > right_ports.size()) { + reactor::log::Warn() << "There are more left ports (" << left_ports.size() << ") than right ports (" << right_ports.size() << ")"; + display_ (left_ports, right_ports); + } + + auto right_port_itr = right_ports.begin(); + for (auto *left_port : left_ports) { + if (right_port_itr == right_ports.end()) { + break; + } + left_port->environment()->draw_connection(left_port, (*right_port_itr), reactor::ConnectionProperties{}); + ++right_port_itr; + } +} + +template +void connect_fanout_(std::set*> &left_ports, std::set*> &right_ports, + reactor::ConnectionProperties &&property) { + assert (left_ports.size() == 1); + if (left_ports.size() < right_ports.size()) { + reactor::log::Warn() << "There are more right ports (" << right_ports.size() << ") than left ports (" << left_ports.size() << ")"; + display_ (left_ports, right_ports); + } else if (left_ports.size() > right_ports.size()) { + reactor::log::Warn() << "There are more left ports (" << left_ports.size() << ") than right ports (" << right_ports.size() << ")"; + display_ (left_ports, right_ports); + } + + reactor::log::Warn() << "Fanning out left port to all right ports"; + + auto left_port_itr = left_ports.begin(); + for (auto *right_port : right_ports) { + (*left_port_itr)->environment()->draw_connection((*left_port_itr), right_port, reactor::ConnectionProperties{}); + } + +} \ No newline at end of file diff --git a/include/reactor-sdk/impl/InputMultiport_wiring_impl.hh b/include/reactor-sdk/impl/InputMultiport_wiring_impl.hh new file mode 100644 index 00000000..5f59f780 --- /dev/null +++ b/include/reactor-sdk/impl/InputMultiport_wiring_impl.hh @@ -0,0 +1,39 @@ +#pragma once + +namespace sdk +{ +template +void MultiportInput::connect(Input& input) { + if (n_inputs > 1) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + output_port.environment()->draw_connection(output_port, input, reactor::ConnectionProperties{}); + break; + } +} + +template +void MultiportInput::connect(MultiportInput& input) { + auto input_itr = input.begin(); + + if (n_inputs < input.get_nports()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (n_inputs > input.get_nports()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + for (auto& output_port : *this) { + output_port.environment()->draw_connection(output_port, *input_itr, reactor::ConnectionProperties{}); + ++input_itr; + if (input_itr == input.end()) + { + break; + } + } +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/InputPort_wiring_impl.hh b/include/reactor-sdk/impl/InputPort_wiring_impl.hh new file mode 100644 index 00000000..3e6147f4 --- /dev/null +++ b/include/reactor-sdk/impl/InputPort_wiring_impl.hh @@ -0,0 +1,58 @@ +#pragma once + +#include "Connection.hh" + +namespace sdk +{ + +template +void Input::connect(Input& input) { + std::set*> left_ports; + std::set*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + result = right_ports.insert(&input).second; + reactor_assert(result); + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +template +void Input::connect(MultiportInput& input) { + auto input_itr = input.begin(); + + if (1 < input.get_nports()) { + reactor::log::Warn() << "There are more right ports than left ports, not all ports would be connected"; + } + + if (input_itr != input.end()) + { + this->environment()->draw_connection(*this, *input_itr, reactor::ConnectionProperties{}); + } +} + +template +void Input::connect_fanout(MultiportInput& input) { + // auto input_itr = input.begin(); + + // if (1 < input.get_nports()) { + // reactor::log::Warn() << "Fanning out input to all right output ports"; + // } + + // while (input_itr != input.end()) + // { + // this->environment()->draw_connection(*this, *input_itr, reactor::ConnectionProperties{}); + // ++input_itr; + // } + + std::set*> left_ports; + std::set*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_fanout_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/OutputMultiport_wiring_impl.hh b/include/reactor-sdk/impl/OutputMultiport_wiring_impl.hh new file mode 100644 index 00000000..47ef5a99 --- /dev/null +++ b/include/reactor-sdk/impl/OutputMultiport_wiring_impl.hh @@ -0,0 +1,163 @@ +#pragma once + +#include "Connection.hh" +namespace sdk +{ +template +void MultiportOutput::connect(Input& input) { + if (n_inputs > 1) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + output_port.environment()->draw_connection(output_port, input, reactor::ConnectionProperties{}); + break; + } +} + +template +void MultiportOutput::connect(Output& input) { + if (n_inputs > 1) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + output_port.environment()->draw_connection(output_port, input, reactor::ConnectionProperties{}); + break; + } +} + +template +void MultiportOutput::connect(MultiportInput& input) { + auto input_itr = input.begin(); + + if (n_inputs < input.get_nports()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (n_inputs > input.get_nports()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + for (auto& output_port : *this) { + output_port.environment()->draw_connection(output_port, *input_itr, reactor::ConnectionProperties{}); + ++input_itr; + if (input_itr == input.end()) + { + break; + } + } +} + +template +void MultiportOutput::connect(MultiportOutput& input) { + auto input_itr = input.begin(); + + if (n_inputs < input.get_nports()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (n_inputs > input.get_nports()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + for (auto& output_port : *this) { + output_port.environment()->draw_connection(output_port, *input_itr, reactor::ConnectionProperties{}); + ++input_itr; + if (input_itr == input.end()) + { + break; + } + } +} + +template +template +void MultiportOutput::connect(std::vector>* reactors, Input ReactorType::*member) { + auto reactor_itr = reactors->begin(); + + if (n_inputs < reactors->size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (n_inputs > reactors->size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + auto *reactor = (*reactor_itr).get(); + output_port.environment()->draw_connection(output_port, reactor->*member, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors->end()) + { + break; + } + } +} + +template +template +void MultiportOutput::connect(ReactorBankInputPort &&other_bank_ports) { + auto reactor_itr = other_bank_ports.begin(); + + if (n_inputs < other_bank_ports.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (n_inputs > other_bank_ports.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& output_port : *this) { + auto *reactor = (*reactor_itr).get(); + output_port.environment()->draw_connection(output_port, reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == other_bank_ports.end()) + { + break; + } + } +} + +template +template +void MultiportOutput::connect(ReactorBankInputPortOffset &&other_bank_ports) { + // auto reactor_itr = other_bank_ports.begin(); + + // if (n_inputs < other_bank_ports.size()) { + // reactor::log::Warn() << "There are more right ports than left ports. " + // << "Not all ports will be connected!"; + // } else if (n_inputs > other_bank_ports.size()) { + // reactor::log::Warn() << "There are more left ports than right ports. " + // << "Not all ports will be connected!"; + // } + // for (auto& output_port : *this) { + // auto *reactor = (*reactor_itr).get(); + // char* reactor_base = reinterpret_cast(reactor); + // Input* port = reinterpret_cast*>(reactor_base + other_bank_ports.get_offset()); + // output_port.environment()->draw_connection(output_port, *port, reactor::ConnectionProperties{}); + // ++reactor_itr; + // if (reactor_itr == other_bank_ports.end()) + // { + // break; + // } + // } + + std::set*> left_ports; + std::set*> right_ports; + bool result; + + for (auto& left_port : *this) { + result = left_ports.insert(&left_port).second; + reactor_assert(result); + } + + for (auto &reactor : other_bank_ports) { + char* reactor_base = reinterpret_cast(reactor.get()); + Input* port = reinterpret_cast*>(reactor_base + other_bank_ports.get_offset()); + result = right_ports.insert(port).second; + reactor_assert(result); + } + + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/OutputPort_wiring_impl.hh b/include/reactor-sdk/impl/OutputPort_wiring_impl.hh new file mode 100644 index 00000000..56330466 --- /dev/null +++ b/include/reactor-sdk/impl/OutputPort_wiring_impl.hh @@ -0,0 +1,161 @@ +#pragma once + +#include "Connection.hh" + +namespace sdk +{ + +template +void Output::connect(Input& input) { + this->environment()->draw_connection(*this, input, reactor::ConnectionProperties{}); +} + +template +void Output::connect(Output& input) { + this->environment()->draw_connection(*this, input, reactor::ConnectionProperties{}); +} + +template +void Output::connect(MultiportInput& input) { + if (is_accumulated) { + auto input_itr = input.begin(); + + if (accumulated.size() < input.get_nports()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (accumulated.size() > input.get_nports()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + this->environment()->draw_connection(*this, *input_itr, reactor::ConnectionProperties{}); + ++input_itr; + + for (auto *l_port : accumulated) { + if (input_itr != input.end()) { + break; + } + this->environment()->draw_connection(*l_port, *input_itr, reactor::ConnectionProperties{}); + ++input_itr; + } + is_accumulated = false; + accumulated.clear(); + } else { + // auto input_itr = input.begin(); + + // if (1 < input.get_nports()) { + // reactor::log::Warn() << "There are more right ports than left ports. " + // << "Not all ports will be connected!"; + // } + + // if (input_itr != input.end()) + // { + // this->environment()->draw_connection(*this, *input_itr, reactor::ConnectionProperties{}); + // } + std::set*> left_ports; + std::set*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_ (left_ports, right_ports, reactor::ConnectionProperties{}); + } +} + +template +void Output::connect_fanout(MultiportInput& input) { + if (is_accumulated) { + auto input_itr = input.begin(); + + if (accumulated.size() < input.get_nports()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (accumulated.size() > input.get_nports()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + this->environment()->draw_connection(*this, *input_itr, reactor::ConnectionProperties{}); + ++input_itr; + + for (auto *l_port : accumulated) { + if (input_itr != input.end()) { + break; + } + this->environment()->draw_connection(*l_port, *input_itr, reactor::ConnectionProperties{}); + ++input_itr; + } + is_accumulated = false; + accumulated.clear(); + } else { + // auto input_itr = input.begin(); + + // if (1 < input.get_nports()) { + // reactor::log::Warn() << "Fanning out input to all right output ports"; + // } + + // while (input_itr != input.end()) + // { + // this->environment()->draw_connection(*this, *input_itr, reactor::ConnectionProperties{}); + // ++input_itr; + // } + + std::set*> left_ports; + std::set*> right_ports; + bool result = left_ports.insert(this).second; + reactor_assert(result); + for (auto &right_port : input) { + result = right_ports.insert(&right_port).second; + reactor_assert(result); + } + connect_fanout_ (left_ports, right_ports, reactor::ConnectionProperties{}); + } +} + +template +template +void Output::connect(std::vector>* reactors, Input ReactorType::*member) { + + if (1 < reactors->size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } + for (auto &p_reactor : *reactors) { + auto *reactor = p_reactor.get(); + this->environment()->draw_connection(*this, reactor->*member, reactor::ConnectionProperties{}); + } +} + +template +template +void Output::connect(ReactorBankInputPort &&other_bank_ports) { + + if (1 < other_bank_ports.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } + for (auto &p_reactor : other_bank_ports) { + auto *reactor = p_reactor.get(); + this->environment()->draw_connection(*this, reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + } +} + +template +template +void Output::connect(ReactorBankInputPortOffset &&other_bank_ports) { + + if (1 < other_bank_ports.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } + for (auto &p_reactor : other_bank_ports) { + auto *reactor = p_reactor.get(); + char* reactor_base = reinterpret_cast(reactor); + Input* port = reinterpret_cast*>(reactor_base + other_bank_ports.get_offset()); + this->environment()->draw_connection(*this, *port, reactor::ConnectionProperties{}); + } +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/ReactorBankOutputMultiport_wiring_impl.hh b/include/reactor-sdk/impl/ReactorBankOutputMultiport_wiring_impl.hh new file mode 100644 index 00000000..02e82100 --- /dev/null +++ b/include/reactor-sdk/impl/ReactorBankOutputMultiport_wiring_impl.hh @@ -0,0 +1,389 @@ +#pragma once + +namespace sdk +{ +template +void ReactorBankOutputMultiPort::connect(MultiportInput& input) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = input.get_nports(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + left_ports += (left_reactor->*member).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_port_itr = input.begin(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_port_itr == input.end()) { + break; + } + + for (auto &l_port : left_reactor->*member) { + l_port.environment()->draw_connection(l_port, *right_port_itr, reactor::ConnectionProperties{}); + ++right_port_itr; + if (right_port_itr == input.end()) { + break; + } + } + } +} + +template +template +void ReactorBankOutputMultiPort::connect(ReactorBankInputPort &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = other_bank_ports.size(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + left_ports += (left_reactor->*member).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + + for (auto &l_port : left_reactor->*member) { + auto *right_reactor = (*right_reactor_itr).get(); + l_port.environment()->draw_connection(l_port, right_reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++right_reactor_itr; + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + } + } +} + +template +template +void ReactorBankOutputMultiPort::connect(ReactorBankInputMultiPort &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + left_ports += (left_reactor->*member).get_nports(); + } + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + right_ports += (right_reactor->*(other_bank_ports.get_member())).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + auto right_port_itr = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).begin(); + auto right_port_itr_end = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).end(); + size_t right_port_count = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_port_count == right_ports) { + break; + } + + for (auto &l_port : left_reactor->*member) { + l_port.environment()->draw_connection(l_port, *right_port_itr, reactor::ConnectionProperties{}); + ++right_port_count; + if (right_port_count == right_ports) { + break; + } + ++right_port_itr; + if (right_port_itr == right_port_itr_end) { + ++right_reactor_itr; + right_port_itr = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).begin(); + right_port_itr_end = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).end(); + } + } + } +} + + +template +void ReactorBankOutputMultiPortOffset::connect(MultiportInput& input) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = input.get_nports(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_port = reinterpret_cast*>(l_reactor_base + offset); + left_ports += (*l_port).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_port_itr = input.begin(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_port_itr == input.end()) { + break; + } + + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + l_port.environment()->draw_connection(l_port, *right_port_itr, reactor::ConnectionProperties{}); + ++right_port_itr; + if (right_port_itr == input.end()) { + break; + } + } + } +} + +template +template +void ReactorBankOutputMultiPortOffset::connect(ReactorBankInputPort &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = other_bank_ports.size(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + left_ports += (*l_ports).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + auto *right_reactor = (*right_reactor_itr).get(); + l_port.environment()->draw_connection(l_port, right_reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++right_reactor_itr; + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + } + } +} + +template +template +void ReactorBankOutputMultiPortOffset::connect(ReactorBankInputMultiPort &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + left_ports += (*l_ports).get_nports(); + } + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + right_ports += (right_reactor->*(other_bank_ports.get_member())).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + auto right_port_itr = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).begin(); + auto right_port_itr_end = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).end(); + size_t right_port_count = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_port_count == right_ports) { + break; + } + + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + l_port.environment()->draw_connection(l_port, *right_port_itr, reactor::ConnectionProperties{}); + ++right_port_count; + if (right_port_count == right_ports) { + break; + } + ++right_port_itr; + if (right_port_itr == right_port_itr_end) { + ++right_reactor_itr; + right_port_itr = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).begin(); + right_port_itr_end = (*right_reactor_itr).get()->*(other_bank_ports.get_member()).end(); + } + } + } +} + +template +template +void ReactorBankOutputMultiPortOffset::connect(ReactorBankInputPortOffset &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = other_bank_ports.size(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + left_ports += (*l_ports).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + auto *right_reactor = (*right_reactor_itr).get(); + char* r_reactor_base = reinterpret_cast(right_reactor); + Input* r_port= reinterpret_cast*>(r_reactor_base + other_bank_ports.get_offset()); + l_port.environment()->draw_connection(l_port, *r_port, reactor::ConnectionProperties{}); + ++right_reactor_itr; + if (right_reactor_itr == other_bank_ports.end()) { + break; + } + } + } +} + +template +template +void ReactorBankOutputMultiPortOffset::connect(ReactorBankInputMultiPortOffset &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = 0; + size_t right_ports = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + left_ports += (*l_ports).get_nports(); + } + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + char* r_reactor_base = reinterpret_cast(right_reactor); + MultiportInput* r_ports = reinterpret_cast*>(r_reactor_base + other_bank_ports.get_offset()); + right_ports += (*r_ports).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto right_reactor_itr = other_bank_ports.begin(); + char* r_reactor_base = reinterpret_cast(*right_reactor_itr); + MultiportInput* r_ports = reinterpret_cast*>(r_reactor_base + other_bank_ports.get_offset()); + auto right_port_itr = (*r_ports).begin(); + auto right_port_itr_end = (*r_ports).end(); + size_t right_port_count = 0; + + for (auto& p_left_reactor : reactors) { + auto *left_reactor = p_left_reactor.get(); + + if (right_port_count == right_ports) { + break; + } + + char* l_reactor_base = reinterpret_cast(left_reactor); + MultiportOutput* l_ports= reinterpret_cast*>(l_reactor_base + offset); + for (auto &l_port : *l_ports) { + l_port.environment()->draw_connection(l_port, *right_port_itr, reactor::ConnectionProperties{}); + ++right_port_count; + if (right_port_count == right_ports) { + break; + } + ++right_port_itr; + if (right_port_itr == right_port_itr_end) { + ++right_reactor_itr; + r_reactor_base = reinterpret_cast(*right_reactor_itr); + r_ports = reinterpret_cast*>(r_reactor_base + other_bank_ports.get_offset()); + right_port_itr = (*r_ports).begin(); + right_port_itr_end = (*r_ports).end(); + } + } + } +} + +} // namespace sdk diff --git a/include/reactor-sdk/impl/ReactorBankOutputPort_wiring_impl.hh b/include/reactor-sdk/impl/ReactorBankOutputPort_wiring_impl.hh new file mode 100644 index 00000000..33d4779d --- /dev/null +++ b/include/reactor-sdk/impl/ReactorBankOutputPort_wiring_impl.hh @@ -0,0 +1,313 @@ +#pragma once + +namespace sdk +{ + +template +void ReactorBankOutputPort::connect(Input& input) { + auto reactor_itr = reactors.begin(); + + if (1 < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto *reactor = (*reactor_itr).get(); + input.environment()->draw_connection(reactor->*member, input, reactor::ConnectionProperties{}); +} + +template +void ReactorBankOutputPort::connect(MultiportInput& input) { + auto reactor_itr = reactors.begin(); + + if (input.get_nports() > reactors.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (input.get_nports() < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& input_port : input) { + auto *reactor = (*reactor_itr).get(); + input_port.environment()->draw_connection(reactor->*member, input_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template +template +void ReactorBankOutputPort::connect(ReactorBankInputPort &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = other_bank_ports.size(); + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *reactor = (*reactor_itr).get(); + auto *right_reactor = p_right_reactor.get(); + (reactor->*member).environment()->draw_connection(reactor->*member, right_reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template +template +void ReactorBankOutputPort::connect(ReactorBankInputMultiPort &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = 0; + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + right_ports += (right_reactor->*(other_bank_ports.get_member())).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + if (reactor_itr == reactors.end()) + { + break; + } + + for (auto& right_port : right_reactor->*(other_bank_ports.get_member())) { + auto *reactor = (*reactor_itr).get(); + (reactor->*member).environment()->draw_connection(reactor->*member, right_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } + } +} + + +// ReactorBankOutputPortOffset +template +void ReactorBankOutputPortOffset::connect(Input& input) { + auto reactor_itr = reactors.begin(); + + if (1 < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + + auto *reactor = (*reactor_itr).get(); + char* reactor_base = reinterpret_cast(reactor); + Output* port = reinterpret_cast*>(reactor_base + offset); + input.environment()->draw_connection(*port, input, reactor::ConnectionProperties{}); +} + +template +void ReactorBankOutputPortOffset::connect(MultiportInput& input) { + auto reactor_itr = reactors.begin(); + + if (input.get_nports() > reactors.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (input.get_nports() < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& input_port : input) { + auto *reactor = (*reactor_itr).get(); + char* reactor_base = reinterpret_cast(reactor); + Output* port = reinterpret_cast*>(reactor_base + offset); + input_port.environment()->draw_connection(*port, input_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template +void ReactorBankOutputPortOffset::connect_fanout(MultiportInput& input) { + auto reactor_itr = reactors.begin(); + + if (input.get_nports() > reactors.size()) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Fanning Out!"; + } else if (input.get_nports() < reactors.size()) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& input_port : input) { + auto *reactor = (*reactor_itr).get(); + char* reactor_base = reinterpret_cast(reactor); + Output* port = reinterpret_cast*>(reactor_base + offset); + input_port.environment()->draw_connection(*port, input_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + reactor_itr = reactors.begin(); + } + } +} + +template +template +void ReactorBankOutputPortOffset::connect(ReactorBankInputPort &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = other_bank_ports.size(); + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *reactor = (*reactor_itr).get(); + char* l_reactor_base = reinterpret_cast(reactor); + Output* l_port = reinterpret_cast*>(l_reactor_base + offset); + auto *right_reactor = p_right_reactor.get(); + (*l_port).environment()->draw_connection(*l_port, right_reactor->*(other_bank_ports.get_member()), reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template +template +void ReactorBankOutputPortOffset::connect(ReactorBankInputMultiPort &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = 0; + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + right_ports += (right_reactor->*(other_bank_ports.get_member())).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + if (reactor_itr == reactors.end()) + { + break; + } + + for (auto& right_port : right_reactor->*(other_bank_ports.get_member())) { + auto *reactor = (*reactor_itr).get(); + char* l_reactor_base = reinterpret_cast(reactor); + Output* l_port = reinterpret_cast*>(l_reactor_base + offset); + (*l_port).environment()->draw_connection(*l_port, right_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } + } +} + +template +template +void ReactorBankOutputPortOffset::connect(ReactorBankInputPortOffset &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = other_bank_ports.size(); + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *reactor = (*reactor_itr).get(); + char* l_reactor_base = reinterpret_cast(reactor); + Output* l_port = reinterpret_cast*>(l_reactor_base + offset); + auto *right_reactor = p_right_reactor.get(); + char* r_reactor_base = reinterpret_cast(right_reactor); + Input* r_port = reinterpret_cast*>(r_reactor_base + other_bank_ports.get_offset()); + (*l_port).environment()->draw_connection(*l_port, *r_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } +} + +template +template +void ReactorBankOutputPortOffset::connect(ReactorBankInputMultiPortOffset &&other_bank_ports) { + auto reactor_itr = reactors.begin(); + size_t left_ports = reactors.size(); + size_t right_ports = 0; + + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + char* r_reactor_base = reinterpret_cast(right_reactor); + MultiportInput* r_port = reinterpret_cast*>(r_reactor_base + other_bank_ports.get_offset()); + right_ports += (*r_port).get_nports(); + } + + if (left_ports < right_ports) { + reactor::log::Warn() << "There are more right ports than left ports. " + << "Not all ports will be connected!"; + } else if (left_ports > right_ports) { + reactor::log::Warn() << "There are more left ports than right ports. " + << "Not all ports will be connected!"; + } + for (auto& p_right_reactor : other_bank_ports) { + auto *right_reactor = p_right_reactor.get(); + if (reactor_itr == reactors.end()) + { + break; + } + + char* r_reactor_base = reinterpret_cast(right_reactor); + MultiportInput* r_port = reinterpret_cast*>(r_reactor_base + other_bank_ports.get_offset()); + for (auto& right_port : *r_port) { + auto *reactor = (*reactor_itr).get(); + char* l_reactor_base = reinterpret_cast(reactor); + Output* l_port = reinterpret_cast*>(l_reactor_base + offset); + (*l_port).environment()->draw_connection(*l_port, right_port, reactor::ConnectionProperties{}); + ++reactor_itr; + if (reactor_itr == reactors.end()) + { + break; + } + } + } +} + +} // namespace sdk \ No newline at end of file diff --git a/include/reactor-sdk/reactor-sdk.hh b/include/reactor-sdk/reactor-sdk.hh new file mode 100644 index 00000000..08303ac3 --- /dev/null +++ b/include/reactor-sdk/reactor-sdk.hh @@ -0,0 +1,16 @@ +#pragma once + +using namespace std; + +#include "reactor-cpp/reactor-cpp.hh" +#include "Misc.hh" +#include "ReactorBank.hh" +#include "MultiportInput.hh" +#include "MultiportOutput.hh" +#include "OutputPort.hh" +#include "InputPort.hh" +#include "Environment.hh" +#include "Reaction.hh" +#include "Reactor.hh" +#include "ConfigParameters.hh" +#include "SystemParameters.hh" \ No newline at end of file diff --git a/lib/reactor.cc b/lib/reactor.cc index dc73c60e..ae9bdb57 100644 --- a/lib/reactor.cc +++ b/lib/reactor.cc @@ -55,8 +55,9 @@ void Reactor::register_output(BasePort* port) { void Reactor::register_reaction([[maybe_unused]] Reaction* reaction) { reactor_assert(reaction != nullptr); - validate(this->environment()->phase() == Phase::Construction, - "Reactions can only be registered during construction phase!"); + validate((this->environment()->phase() == Phase::Construction) || + (this->environment()->phase() == Phase::Assembly), + "Reactions can only be registered during construction or assembly phase!"); [[maybe_unused]] bool result = reactions_.insert(reaction).second; reactor_assert(result); Statistics::increment_reactions(); diff --git a/lib/reactor_element.cc b/lib/reactor_element.cc index 32db319f..b0faae83 100644 --- a/lib/reactor_element.cc +++ b/lib/reactor_element.cc @@ -25,7 +25,8 @@ ReactorElement::ReactorElement(const std::string& name, ReactorElement::Type typ this->environment_ = container->environment(); reactor_assert(this->environment_ != nullptr); validate(this->environment_->phase() == Phase::Construction || - (type == Type::Action && this->environment_->phase() == Phase::Assembly), + (type == Type::Action && this->environment_->phase() == Phase::Assembly) || + (type == Type::Reaction && this->environment_->phase() == Phase::Assembly), "Reactor elements can only be created during construction phase!"); // We need a reinterpret_cast here as the derived class is not yet created // when this constructor is executed. dynamic_cast only works for diff --git a/reactor-sdk/CMakeLists.txt b/reactor-sdk/CMakeLists.txt new file mode 100644 index 00000000..bf3f9021 --- /dev/null +++ b/reactor-sdk/CMakeLists.txt @@ -0,0 +1,20 @@ +set (ReactorSDK_LIB reactor-sdk) + +include(GNUInstallDirs) + +find_package(reactor-cpp PATHS ) + +set(REACTOR_CPP_SDK_INCLUDE "include") + +add_library(${ReactorSDK_LIB} SHARED Environment.cc Reactor.cc) + +target_link_libraries(${ReactorSDK_LIB} PUBLIC reactor-cpp) + +install(TARGETS ${ReactorSDK_LIB} EXPORT ${ReactorSDK_LIB}Config + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) + +install(EXPORT ${ReactorSDK_LIB}Config DESTINATION share/${ReactorSDK_LIB}/cmake) + +export(TARGETS ${ReactorSDK_LIB} FILE ${ReactorSDK_LIB}Config.cmake) \ No newline at end of file diff --git a/reactor-sdk/Environment.cc b/reactor-sdk/Environment.cc new file mode 100644 index 00000000..e4962adb --- /dev/null +++ b/reactor-sdk/Environment.cc @@ -0,0 +1,56 @@ +#include +#include +#include +#include "reactor-sdk/Environment.hh" + +using namespace std; + +namespace sdk +{ + +std::map type_convert = { + {"i", "int"}, + {"j", "uint32_t"}, + {"b", "bool"}, + {"f", "float"}, + {"d", "double"}, + {"c", "char"}, + {"s", "std::string"}, + {"ll", "long long"}, + {"ul", "unsigned long"}, + {"uc", "unsigned char"}, + {"ld", "long double"}, + {"uint", "unsigned int"} +}; + +Environment::Environment( ConfigParameterBase *cfg_param, unsigned int num_workers, bool fast_fwd_execution, + const reactor::Duration& timeout, bool visualize) + : reactor::Environment (num_workers, fast_fwd_execution, timeout), config_parameters(cfg_param), visualize(visualize) { +} + + +void Environment::run() +{ + if (this->config_parameters) { + this->config_parameters->pull_config(); + // instance->config_parameters->display(); + } + + this->construct(); + this->assemble(); + + if (this->config_parameters) { + if (this->config_parameters->validate() != 0) { + reactor::log::Error() << "INVALID CONFIGURATION!"; + return; + } + } + + if (visualize) { + this->export_dependency_graph("graph.dot"); + } + auto thread = this->startup(); + thread.join(); +} + +} // namespace sdk \ No newline at end of file diff --git a/reactor-sdk/Reactor.cc b/reactor-sdk/Reactor.cc new file mode 100644 index 00000000..438c5f4e --- /dev/null +++ b/reactor-sdk/Reactor.cc @@ -0,0 +1,45 @@ +#include +#include +#include +#include "reactor-sdk/Reactor.hh" +#include "reactor-sdk/Environment.hh" + +using namespace std; + +namespace sdk +{ + +Reactor::Reactor(const std::string &name, Environment *env) + : reactor::Reactor(name, (reactor::Environment*)env), env(env) { +} + +Reactor::Reactor(const std::string &name, Reactor *container) + : reactor::Reactor(name, container), env(container->env) { + container->add_child (this); +} + +void Reactor::add_child(Reactor* reactor) { + [[maybe_unused]] bool result = child_reactors.insert(reactor).second; + reactor_assert(result); +} + +void Reactor::add_to_reaction_map (std::string &name, std::shared_ptr reaction) { + reaction_map[name] = reaction; +} + +Reactor &Reactor::reaction (const std::string name) { + current_reaction_name = name; + return *this; +} + +void Reactor::construct() { + if (p_param) { + p_param->fetch_config(); + } + construction(); +} +void Reactor::assemble() { + assembling(); +} + +} // namespace sdk \ No newline at end of file diff --git a/test/ActionDelay/CMakeLists.txt b/test/ActionDelay/CMakeLists.txt new file mode 100644 index 00000000..31bb9c9a --- /dev/null +++ b/test/ActionDelay/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(ActionDelay VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET ActionDelay) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/test/ActionDelay/main.cc b/test/ActionDelay/main.cc new file mode 100644 index 00000000..a65b1387 --- /dev/null +++ b/test/ActionDelay/main.cc @@ -0,0 +1,141 @@ + +#include + +using namespace std; +using namespace sdk; + +class GeneratedDelay : public Reactor { + int y_state = 0; + LogicalAction act{"act", this, 100ms}; +public: + GeneratedDelay(const std::string &name, Environment *env) + : Reactor(name, env) {} + GeneratedDelay(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Input y_in{"y_in", this}; + Output y_out{"y_out", this}; + + void construction() {} + + void assembling() { + reaction ("reaction_1"). + triggers(&y_in). + effects(&act). + function ( + [&](Input &y_in, LogicalAction &act) { + y_state = *y_in.get(); + act.schedule(); + } + ); + + reaction ("reaction_2"). + triggers(&act). + effects(&y_out). + function ( + [&](LogicalAction &act, Output &y_out) { + y_out.set(y_state); + } + ); + } +}; + +class Source : public Reactor { +public: + Source(const std::string &name, Environment *env) + : Reactor(name, env) {} + Source(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Output out{"out", this}; + + void construction() {} + + void assembling() { + reaction ("reaction_1"). + triggers(&startup). + effects(&out). + function ( + [&](Startup &startup, Output &out) { + out.set(1); + } + ); + } +}; + +class Sink : public Reactor { +public: + Sink(const std::string &name, Environment *env) + : Reactor(name, env) {} + Sink(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Input in{"in", this}; + + void construction() {} + + void assembling() { + reaction ("reaction_1"). + triggers(&in). + effects(). + function ( + [&](Input &in) { + auto elapsed_logical = get_elapsed_logical_time(); + auto logical = get_logical_time(); + auto physical = get_physical_time(); + std::cout << "logical time: " << logical << '\n'; + std::cout << "physical time: " << physical << '\n'; + std::cout << "elapsed logical time: " << elapsed_logical << '\n'; + if (elapsed_logical != 100ms) { + std::cerr << "ERROR: Expected 100 msecs but got " << elapsed_logical << '\n'; + exit(1); + } else { + std::cout << "SUCCESS. Elapsed logical time is 100 msec.\n"; + } + } + ); + } +}; + +class ActionDelay : public Reactor { + std::unique_ptr source; + std::unique_ptr sink; + std::unique_ptr g; +public: + ActionDelay(const std::string &name, Environment *env) + : Reactor(name, env) {} + ActionDelay(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() { + source = std::make_unique("Source", this); + sink = std::make_unique("Sink", this); + g = std::make_unique("GeneratedDelay", this); + } + + void assembling() { + source->out --> g->y_in; + g->y_out --> sink->in; + } +}; + +int main(int argc, char **argv) { + unsigned workers = 1; + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + + bool visualize = false; + + if (argc > 1) { + string v_str = argv[1]; + visualize = (v_str == "true") ? true : false; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << visualize << std::endl; + + Environment sim {nullptr, workers, fast, timeout, visualize}; + auto action_delay = new ActionDelay("ActionDelay", &sim); + + sim.run(); + return 0; +} diff --git a/test/ActionIsPresent/CMakeLists.txt b/test/ActionIsPresent/CMakeLists.txt new file mode 100644 index 00000000..0adc43de --- /dev/null +++ b/test/ActionIsPresent/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(ActionIsPresent VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET ActionIsPresent) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/test/ActionIsPresent/main.cc b/test/ActionIsPresent/main.cc new file mode 100644 index 00000000..7bed8901 --- /dev/null +++ b/test/ActionIsPresent/main.cc @@ -0,0 +1,97 @@ + +#include + +using namespace std; +using namespace sdk; + +class ActionIsPresent : public Reactor { +struct Parameters : public SystemParameter { + ParameterMetadata offset = ParameterMetadata { + .name = "offset", + .description = "offset", + .min_value = 1ns, + .max_value = 10ns, + .value = 1ns + }; + + ParameterMetadata period = ParameterMetadata { + .name = "period", + .description = "period", + .min_value = 500ms, + .max_value = 1s, + .value = 500ms + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (offset, period); + } + }; +private: + Parameters parameters{this}; + LogicalAction a{"a", this}; + bool success = false; + Duration zero = 0ns; +public: + ActionIsPresent(const std::string &name, Environment *env) + : Reactor(name, env) {} + ActionIsPresent(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() { + } + + void assembling() { + reaction ("reaction_1"). + triggers(&startup, &a). + effects(). + function ( + [&](Startup &startup, LogicalAction &a) { + if (!a.is_present()) { + if (parameters.offset.value == zero) { + std::cout << "Hello World!" << '\n'; + success = true; + } else { + a.schedule(parameters.offset.value); + } + } else { + std::cout << "Hello World 2!" << '\n'; + success = true; + } + } + ); + + reaction ("reaction_2"). + triggers(&shutdown). + effects(). + function ( + [&](Shutdown &shutdown) { + if (!success) { + std::cerr << "Failed to print 'Hello World!'" << '\n'; + exit(1); + } + } + ); + } +}; + +int main(int argc, char **argv) { + unsigned workers = 1; + bool fast{false}; + reactor::Duration timeout = reactor::Duration::max(); + + bool visualize = false; + + if (argc > 1) { + string v_str = argv[1]; + visualize = (v_str == "true") ? true : false; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << visualize << std::endl; + + Environment sim {nullptr, workers, fast, timeout, visualize}; + auto action_delay = new ActionIsPresent("ActionIsPresent", &sim); + + sim.run(); + return 0; +} diff --git a/test/Deadlines/CMakeLists.txt b/test/Deadlines/CMakeLists.txt new file mode 100644 index 00000000..aef0a597 --- /dev/null +++ b/test/Deadlines/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) +project(Deadlines VERSION 0.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard is cached for visibility in external tools." FORCE) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) + +set(LF_MAIN_TARGET Deadlines) + +find_package(reactor-cpp PATHS ) +find_package(reactor-sdk PATHS ) + +add_executable(${LF_MAIN_TARGET} + main.cc +) + +include_directories(${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(${LF_MAIN_TARGET} reactor-cpp) +target_link_libraries(${LF_MAIN_TARGET} reactor-sdk) + +target_compile_options(${LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) \ No newline at end of file diff --git a/test/Deadlines/main.cc b/test/Deadlines/main.cc new file mode 100644 index 00000000..5d6697d3 --- /dev/null +++ b/test/Deadlines/main.cc @@ -0,0 +1,152 @@ + +#include + +using namespace std; +using namespace sdk; + +class Source : public Reactor { + struct Parameters : public SystemParameter { + ParameterMetadata period = ParameterMetadata { + .name = "period", + .description = "period", + .min_value = 1s, + .max_value = 5s, + .value = 2s + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (period); + } + }; + Parameters parameters{this}; + Timer t{"t", this}; + int count = 0; +public: + Source(const std::string &name, Environment *env) + : Reactor(name, env) {} + Source(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Output y{"y", this}; + + void construction() { + t.set_timer (parameters.period.value, 0ns); + } + + void assembling() { + reaction ("reaction_1"). + triggers(&t). + effects(&y). + function ( + [&](Timer &t, Output &y) { + if (count % 2 == 1) { + // The count variable is odd. + // Take time to cause a deadline violation. + std::this_thread::sleep_for(400ms); + } + std::cout << "Source sends: " << count << std::endl; + y.set(count); + count++; + } + ); + } +}; + +class Destination : public Reactor { + struct Parameters : public SystemParameter { + ParameterMetadata timeout = ParameterMetadata { + .name = "timeout", + .description = "timeout", + .min_value = 100ms, + .max_value = 1s, + .value = 200ms + }; + + Parameters(Reactor *container) + : SystemParameter(container) { + register_parameters (timeout); + } + }; + Parameters parameters{this}; + int count = 0; +public: + Destination(const std::string &name, Environment *env) + : Reactor(name, env) {} + Destination(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + Input x{"x", this}; + + void construction() {} + + void assembling() { + reaction ("reaction_1"). + triggers(&x). + effects(). + function ( + [&](Input &x) { + std::cout << "Destination receives: " << *x.get() << std::endl; + if (count % 2 == 1) { + // The count variable is odd, so the deadline should have been + // violated + std::cerr << "ERROR: Failed to detect deadline." << std::endl; + exit(1); + } + count++; + } + ).deadline (parameters.timeout.value, + [&](Input &x) { + std::cout << "Destination deadline handler receives: " + << *x.get() << std::endl; + if (count % 2 == 0) { + // The count variable is even, so the deadline should not have + // been violated. + std::cerr << "ERROR: Deadline handler invoked without deadline " + << "violation." << std::endl; + exit(2); + } + count++; + }); + } +}; + +class Deadline : public Reactor { + std::unique_ptr s; + std::unique_ptr d; +public: + Deadline(const std::string &name, Environment *env) + : Reactor(name, env) {} + Deadline(const std::string &name, Reactor *container) + : Reactor(name, container) {} + + void construction() { + s = std::make_unique("Source", this); + d = std::make_unique("Destination", this); + } + + void assembling() { + s->y --> d->x; + } +}; + +int main(int argc, char **argv) { + unsigned workers = 1; + bool fast{false}; + reactor::Duration timeout = 4s; + + bool visualize = false; + + if (argc > 1) { + string v_str = argv[1]; + visualize = (v_str == "true") ? true : false; + } + + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << visualize << std::endl; + + Environment sim {nullptr, workers, fast, timeout, visualize}; + auto dl = new Deadline("Deadline", &sim); + + sim.run(); + return 0; +} From fb31d005f23f2c23de143ef05841239d47bdc69e Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Thu, 20 Feb 2025 05:20:35 +0500 Subject: [PATCH 03/22] reverted back info logs --- lib/environment.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/environment.cc b/lib/environment.cc index a2ab4ee7..cc9261d9 100644 --- a/lib/environment.cc +++ b/lib/environment.cc @@ -75,9 +75,7 @@ void recursive_construct(Reactor* container) { } void Environment::construct() { - // phase_ = Phase::Assembly; - - log::Info() << "Start Contruction of reactors"; + log::Debug() << "Start Contruction of reactors"; for (auto* reactor : top_level_reactors_) { recursive_construct(reactor); } @@ -100,7 +98,7 @@ void Environment::assemble() { // NOLINT(readability-function-cognitive-complexi // constructing all the reactors // this mainly tell the reactors that they should connect their ports and actions not ports and ports - log::Info() << "start assembly of reactors"; + log::Debug() << "start assembly of reactors"; for (auto* reactor : top_level_reactors_) { recursive_assemble(reactor); } @@ -361,7 +359,7 @@ auto Environment::startup(const TimePoint& start_time) -> std::thread { validate(this->phase() == Phase::Indexing, "startup() may only be called during Indexing phase!"); - log_.info() << "Starting the execution"; + log_.debug() << "Starting the execution"; phase_ = Phase::Startup; this->start_tag_ = Tag::from_physical_time(start_time); From 9be28073991e866938aa3fcf0e622189516c05a3 Mon Sep 17 00:00:00 2001 From: Omer Majeed Date: Thu, 20 Feb 2025 19:50:15 +0500 Subject: [PATCH 04/22] command line arguments support added to SDK, copied files used by LF --- examples/sdk-SrcSink-Fanout/main.cc | 36 +- examples/sdk-SrcSink/Config-a/Config-a.cc | 4 +- examples/sdk-SrcSink/Main/MainReactor.cc | 10 - examples/sdk-SrcSink/main.cc | 36 +- examples/sdk-deadlines/main.cc | 36 +- include/reactor-sdk/cxxopts.hpp | 2610 +++++++++++++++++++++ include/reactor-sdk/reactor-sdk.hh | 1 + include/reactor-sdk/time_parser.hh | 164 ++ test/ActionDelay/main.cc | 36 +- test/ActionIsPresent/main.cc | 34 +- test/Deadlines/main.cc | 38 +- 11 files changed, 2951 insertions(+), 54 deletions(-) create mode 100644 include/reactor-sdk/cxxopts.hpp create mode 100644 include/reactor-sdk/time_parser.hh diff --git a/examples/sdk-SrcSink-Fanout/main.cc b/examples/sdk-SrcSink-Fanout/main.cc index 32b7d893..6d01b509 100644 --- a/examples/sdk-SrcSink-Fanout/main.cc +++ b/examples/sdk-SrcSink-Fanout/main.cc @@ -8,18 +8,40 @@ using namespace std; using namespace sdk; int main(int argc, char **argv) { - unsigned workers = 1; + 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 visualize{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")) + ("v,visualize", "Generate graph.dot file of the topology.", cxxopts::value(visualize)->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; + } - bool visualize = false; - - if (argc > 1) { - string v_str = argv[1]; - visualize = (v_str == "true") ? true : false; + // 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 << " visualize:" << visualize << std::endl; + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << (visualize ? "True" : "False") << std::endl; Environment sim {&cfg_parameters, workers, fast, timeout, visualize}; auto main = new MainReactor("Main", &sim); diff --git a/examples/sdk-SrcSink/Config-a/Config-a.cc b/examples/sdk-SrcSink/Config-a/Config-a.cc index c93bef8d..bdc94ce9 100644 --- a/examples/sdk-SrcSink/Config-a/Config-a.cc +++ b/examples/sdk-SrcSink/Config-a/Config-a.cc @@ -11,8 +11,8 @@ ConfigParameter::ParametersMap UserParameters::homogeneous_config ConfigParameter::ParametersMap UserParameters::heterogeneous_config() { return { {"Main.Source.iterations", ConfigParameterMetadata { 20 } }, - {"Main.Source.n_ports", ConfigParameterMetadata { 2 } }, - {"Main.n_sinks", ConfigParameterMetadata { 2 } }, + {"Main.Source.n_ports", ConfigParameterMetadata { 4 } }, + {"Main.n_sinks", ConfigParameterMetadata { 4 } }, }; } diff --git a/examples/sdk-SrcSink/Main/MainReactor.cc b/examples/sdk-SrcSink/Main/MainReactor.cc index 4c937cd1..43a7c167 100644 --- a/examples/sdk-SrcSink/Main/MainReactor.cc +++ b/examples/sdk-SrcSink/Main/MainReactor.cc @@ -36,14 +36,4 @@ void MainReactor::assembling() { "Starting up reaction\n" << "Bank:" << bank_index << " name:" << parameters.alias.value << " fqn:" << fqn() << " n_sinks:" << parameters.n_sinks.value << endl; } ); - - reaction("reaction_2"). - triggers(&snk[0].rsp). - effects(). - function( - [&](Output& rsp) { - cout << "(" << get_elapsed_logical_time() << ", " << get_microstep() << "), physical_time: " << get_elapsed_physical_time() << " " << - "Response\n" << "Bank:" << bank_index << " value:" << *rsp.get() << endl; - } - ); } \ No newline at end of file diff --git a/examples/sdk-SrcSink/main.cc b/examples/sdk-SrcSink/main.cc index 32b7d893..bb4ca048 100644 --- a/examples/sdk-SrcSink/main.cc +++ b/examples/sdk-SrcSink/main.cc @@ -8,18 +8,40 @@ using namespace std; using namespace sdk; int main(int argc, char **argv) { - unsigned workers = 1; + 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 visualize{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")) + ("v,visualize", "Generate graph.dot file of the topology.", cxxopts::value(visualize)->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; + } - bool visualize = false; - - if (argc > 1) { - string v_str = argv[1]; - visualize = (v_str == "true") ? true : false; + // 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 << " visualize:" << visualize << std::endl; + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << (visualize ? "True" : "False") << std::endl; Environment sim {&cfg_parameters, workers, fast, timeout, visualize}; auto main = new MainReactor("Main", &sim); diff --git a/examples/sdk-deadlines/main.cc b/examples/sdk-deadlines/main.cc index 32b7d893..15eb3247 100644 --- a/examples/sdk-deadlines/main.cc +++ b/examples/sdk-deadlines/main.cc @@ -8,18 +8,40 @@ using namespace std; using namespace sdk; int main(int argc, char **argv) { - unsigned workers = 1; + 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 visualize{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")) + ("v,visualize", "Generate graph.dot file of the topology.", cxxopts::value(visualize)->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; + } - bool visualize = false; - - if (argc > 1) { - string v_str = argv[1]; - visualize = (v_str == "true") ? true : false; + // 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 << " visualize:" << visualize << std::endl; + std::cout << "parameters - workers:" << workers << " fast:" << (fast ? "True" : "False") << " timeout:" << timeout << " visualize:" << (visualize ? "True" : "False") << std::endl; Environment sim {&cfg_parameters, workers, fast, timeout, visualize}; auto main = new MainReactor("Main", &sim); 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