├── doc ├── source │ ├── .gitignore │ ├── index.rst │ └── conf.py ├── requirements.txt ├── make.bat └── Makefile ├── tracing ├── stop_tracing.sh ├── start_tracing.sh ├── README.md └── ctf_to_json.py ├── .gitignore ├── examples ├── count │ ├── CMakeLists.txt │ └── main.cc ├── hello │ ├── CMakeLists.txt │ └── main.cc ├── ports │ ├── CMakeLists.txt │ └── main.cc ├── power_train │ ├── CMakeLists.txt │ └── main.cc └── CMakeLists.txt ├── .clang-format ├── lib ├── trace.cc ├── multiport.cc ├── assert.cc ├── time.cc ├── CMakeLists.txt ├── logical_time.cc ├── reactor_element.cc ├── action.cc ├── reaction.cc ├── reactor.cc ├── port.cc └── environment.cc ├── include └── reactor-cpp │ ├── config.hh.in │ ├── reactor-cpp.hh │ ├── fwd.hh │ ├── semaphore.hh │ ├── time.hh │ ├── connection_properties.hh │ ├── safe_vector.hh │ ├── reactor_element.hh │ ├── reactor.hh │ ├── assert.hh │ ├── reaction.hh │ ├── time_barrier.hh │ ├── logging.hh │ ├── trace.hh │ ├── statistics.hh │ ├── logical_time.hh │ ├── impl │ ├── port_impl.hh │ └── action_impl.hh │ ├── multiport.hh │ ├── graph.hh │ ├── environment.hh │ ├── action.hh │ ├── port.hh │ ├── scheduler.hh │ └── connection.hh ├── .github └── workflows │ ├── clang-format.yml │ ├── clang-tidy.yml │ ├── github-pages.yaml │ └── main.yaml ├── .clang-tidy ├── nix ├── reactor-cpp.nix ├── lfc.nix ├── test.nix ├── library.nix └── benchmark.nix ├── package.xml ├── LICENSE.txt ├── flake.nix ├── CMakeLists.txt ├── README.md ├── CONTRIBUTING.md └── flake.lock /doc/source/.gitignore: -------------------------------------------------------------------------------- 1 | api 2 | -------------------------------------------------------------------------------- /tracing/stop_tracing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lttng destroy 4 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | breathe 3 | exhale 4 | sphinx-rtd-theme 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | doxygen 3 | cmake-build-debug 4 | cmake-build-release 5 | .idea 6 | result 7 | -------------------------------------------------------------------------------- /tracing/start_tracing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lttng create reactor-cpp-session 4 | lttng enable-event --userspace "reactor_cpp:*" 5 | lttng start 6 | -------------------------------------------------------------------------------- /examples/count/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(count EXCLUDE_FROM_ALL main.cc) 2 | target_link_libraries(count reactor-cpp) 3 | add_dependencies(examples count) 4 | -------------------------------------------------------------------------------- /examples/hello/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(hello EXCLUDE_FROM_ALL main.cc) 2 | target_link_libraries(hello reactor-cpp) 3 | add_dependencies(examples hello) 4 | -------------------------------------------------------------------------------- /examples/ports/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ports EXCLUDE_FROM_ALL main.cc) 2 | target_link_libraries(ports reactor-cpp) 3 | add_dependencies(examples ports) 4 | -------------------------------------------------------------------------------- /examples/power_train/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(power_train EXCLUDE_FROM_ALL main.cc) 2 | target_link_libraries(power_train reactor-cpp) 3 | add_dependencies(examples power_train) 4 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | Language: Cpp 4 | IndentWidth: 2 5 | BreakConstructorInitializersBeforeComma: 'true' 6 | AllowShortFunctionsOnASingleLine: All 7 | PointerAlignment: Left 8 | ColumnLimit: 120 -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories( 2 | "${PROJECT_SOURCE_DIR}/include" 3 | ) 4 | 5 | add_custom_target(examples) 6 | add_subdirectory(count) 7 | add_subdirectory(ports) 8 | add_subdirectory(hello) 9 | add_subdirectory(power_train) 10 | -------------------------------------------------------------------------------- /lib/trace.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #define TRACEPOINT_CREATE_PROBES 10 | #define TRACEPOINT_DEFINE 11 | 12 | #include "reactor-cpp/trace.hh" 13 | -------------------------------------------------------------------------------- /include/reactor-cpp/config.hh.in: -------------------------------------------------------------------------------- 1 | #ifndef REACTOR_CPP_CONFIG_HH 2 | #define REACTOR_CPP_CONFIG_HH 3 | 4 | // NOLINTNEXTLINE 5 | #cmakedefine REACTOR_CPP_PRINT_STATISTICS 6 | // NOLINTNEXTLINE 7 | #cmakedefine REACTOR_CPP_TRACE 8 | // NOLINTNEXTLINE 9 | #cmakedefine REACTOR_CPP_VALIDATE 10 | // NOLINTNEXTLINE 11 | #cmakedefine REACTOR_CPP_LOG_LEVEL @REACTOR_CPP_LOG_LEVEL@ 12 | 13 | #cmakedefine REACTOR_CPP_USE_BACKTRACE 14 | #define REACTOR_CPP_BACKTRACE_HEADER <@Backtrace_HEADER@> 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /.github/workflows/clang-format.yml: -------------------------------------------------------------------------------- 1 | name: check clang-format 2 | 3 | # You can be more specific, but it currently only works on pull requests 4 | on: [pull_request] 5 | 6 | jobs: 7 | clang-format: 8 | runs-on: ubuntu-24.04 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Analyze 12 | run: | 13 | clang-format --dry-run --Werror -style=file $(find ./ -name '*.cc' -print) 14 | clang-format --dry-run --Werror -style=file $(find ./ -name '*.hh' -print) 15 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: ' 2 | clang-diagnostic-*, 3 | boost-*, 4 | Google-*, 5 | clang-analyzer-*, 6 | modernize-*, 7 | performance-*, 8 | portability-*, 9 | readability-*, 10 | cppcoreguidelines-*, 11 | llvm-*, 12 | cert-*, 13 | -clang-analyzer-core.CallAndMessage, 14 | -readability-redundant-member-init' 15 | WarningsAsErrors: true 16 | HeaderFilterRegex: '' 17 | FormatStyle: none 18 | CheckOptions: 19 | - key: readability-identifier-length.MinimumParameterNameLength 20 | value: 2 21 | -------------------------------------------------------------------------------- /.github/workflows/clang-tidy.yml: -------------------------------------------------------------------------------- 1 | name: check clang-tidy 2 | 3 | # You can be more specific, but it currently only works on pull requests 4 | on: [pull_request] 5 | 6 | jobs: 7 | clang-tidy: 8 | runs-on: ubuntu-24.04 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: configure 12 | run: | 13 | mkdir build 14 | cd build 15 | cmake .. 16 | - name: build the library 17 | run: cmake --build build 18 | - name: build examples 19 | run: 20 | cmake --build build --target examples 21 | -------------------------------------------------------------------------------- /include/reactor-cpp/reactor-cpp.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_REACTOR_CPP_HH 10 | #define REACTOR_CPP_REACTOR_CPP_HH 11 | 12 | // include everything that is needed to use reactor-cpp 13 | #include "action.hh" 14 | #include "connection.hh" 15 | #include "connection_properties.hh" 16 | #include "environment.hh" 17 | #include "logging.hh" 18 | #include "logical_time.hh" 19 | #include "multiport.hh" 20 | #include "port.hh" 21 | #include "reaction.hh" 22 | #include "reactor.hh" 23 | #include "time.hh" 24 | 25 | #endif // REACTOR_CPP_REACTOR_CPP_HH 26 | -------------------------------------------------------------------------------- /nix/reactor-cpp.nix: -------------------------------------------------------------------------------- 1 | { pkgs, mkDerivation, cmake, gcc, reactor-cpp-src, debug }: 2 | let 3 | 4 | buildMode = if debug then "Debug" else "Release"; 5 | 6 | in 7 | mkDerivation { 8 | name = "cpp-lingua-franca-runtime"; 9 | src = reactor-cpp-src; 10 | 11 | nativeBuildInputs = with pkgs; [ cmake gcc ]; 12 | 13 | configurePhase = '' 14 | echo "Configuration" 15 | ''; 16 | 17 | #TODO: remove debug build here 18 | buildPhase = '' 19 | mkdir -p build && cd build 20 | cmake -DCMAKE_BUILD_TYPE=${buildMode} -DCMAKE_INSTALL_PREFIX=./ ../ 21 | make install 22 | ''; 23 | 24 | installPhase = '' 25 | cp -r ./ $out/ 26 | ''; 27 | 28 | fixupPhase = '' 29 | echo "FIXUP PHASE SKIP" 30 | ''; 31 | } 32 | -------------------------------------------------------------------------------- /include/reactor-cpp/fwd.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_FWD_HH 10 | #define REACTOR_CPP_FWD_HH 11 | 12 | #include 13 | #include 14 | 15 | namespace reactor { 16 | 17 | class BaseAction; 18 | class BasePort; 19 | class Environment; 20 | enum class Phase : std::uint8_t; 21 | class Reaction; 22 | class Reactor; 23 | class ReactorElement; 24 | class Scheduler; 25 | class Tag; 26 | 27 | template class Action; 28 | template class Port; 29 | 30 | using PortCallback = std::function; 31 | 32 | } // namespace reactor 33 | 34 | #endif // REACTOR_CPP_FWD_HH 35 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | reactor-cpp 5 | 0.0.0 6 | A C++ reactor runtime 7 | user 8 | ISC License 9 | 10 | cmake 11 | 12 | 13 | 14 | reactor-cpp-foo 15 | 0.0.0 16 | A C++ reactor runtime 17 | user 18 | ISC License 19 | 20 | cmake 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2024, Xronos, Inc. 4 | Copyright (c) 2019, TU Dresden 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yaml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Install doxygen 14 | run: sudo apt-get install doxygen 15 | 16 | - name: Install sphinx and plugins 17 | run: sudo pip3 install -r doc/requirements.txt 18 | 19 | - name: build documentation 20 | run: make html 21 | working-directory: doc 22 | 23 | - name: ls doc/doxygen 24 | run: ls -la doc 25 | 26 | - name: ls doc/doxygen 27 | run: ls -la doc/doxygen 28 | 29 | - name: Deploy 30 | uses: peaceiris/actions-gh-pages@v3 31 | with: 32 | github_token: ${{ secrets.GITHUB_TOKEN }} 33 | publish_dir: ./doc/build/html 34 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. reactor-cpp documentation master file, created by 2 | sphinx-quickstart on Thu Jun 25 10:24:11 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | reactor-cpp 7 | =========== 8 | 9 | reactor-cpp is a framework for writing reactor-oriented programs. While 10 | reactor-cpp can be used as a standalone framework, it is designed to work in 11 | conjunction with `Lingua Franca `_, a 12 | polyglot coordination language. Have a look at the Lingua Franca 13 | `wiki `_ to get an overview of the 14 | reactor model. 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | 19 | api/library_root 20 | 21 | 22 | 23 | Indices and tables 24 | ================== 25 | 26 | * :ref:`genindex` 27 | * :ref:`modindex` 28 | * :ref:`search` 29 | 30 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile html 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | 22 | clean: Makefile 23 | rm -rf doxgen/ api/ 24 | @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 25 | 26 | html: Makefile 27 | doxygen reactor-cpp.doxyfile 28 | @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 29 | -------------------------------------------------------------------------------- /include/reactor-cpp/semaphore.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_SEMAPHORE_HH 10 | #define REACTOR_CPP_SEMAPHORE_HH 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace reactor { 17 | 18 | class Semaphore { 19 | private: 20 | int count_; 21 | std::mutex mutex_{}; 22 | std::condition_variable cv_{}; 23 | 24 | public: 25 | explicit Semaphore(int count) 26 | : count_(count) {} 27 | 28 | void release(int increment) { 29 | { 30 | std::lock_guard lock_guard(mutex_); 31 | count_ += increment; 32 | } 33 | cv_.notify_all(); 34 | } 35 | 36 | void acquire() { 37 | std::unique_lock lock_guard(mutex_); 38 | cv_.wait(lock_guard, [&]() { return count_ != 0; }); 39 | count_--; 40 | } 41 | }; 42 | 43 | } // namespace reactor 44 | 45 | #endif // REACTOR_CPP_SEMAPHORE_HH 46 | -------------------------------------------------------------------------------- /include/reactor-cpp/time.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_TIME_HH 10 | #define REACTOR_CPP_TIME_HH 11 | 12 | #include 13 | #include 14 | 15 | namespace reactor { 16 | 17 | using TimePoint = std::chrono::time_point; 18 | using Duration = std::chrono::nanoseconds; 19 | 20 | auto inline get_physical_time() -> TimePoint { return std::chrono::system_clock::now(); } 21 | 22 | inline namespace operators { 23 | 24 | auto operator<<(std::ostream& os, TimePoint tp) -> std::ostream&; 25 | 26 | auto operator<<(std::ostream& os, std::chrono::seconds dur) -> std::ostream&; 27 | auto operator<<(std::ostream& os, std::chrono::milliseconds dur) -> std::ostream&; 28 | auto operator<<(std::ostream& os, std::chrono::microseconds dur) -> std::ostream&; 29 | auto operator<<(std::ostream& os, std::chrono::nanoseconds dur) -> std::ostream&; 30 | 31 | } // namespace operators 32 | 33 | } // namespace reactor 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /examples/hello/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "reactor-cpp/action.hh" 4 | #include "reactor-cpp/reactor-cpp.hh" 5 | 6 | using namespace reactor; 7 | using namespace std::chrono_literals; 8 | 9 | class Hello : public Reactor { 10 | private: 11 | // actions 12 | Timer timer{"timer", this, 1s, 2s}; 13 | ShutdownTrigger sa{"terminate", this}; 14 | 15 | // reactions 16 | Reaction r_hello{"r_hello", 1, this, [this]() { hello(); }}; 17 | Reaction r_terminate{"r_terminate", 2, this, [this]() { terminate(); }}; 18 | 19 | public: 20 | explicit Hello(Environment* env) 21 | : Reactor("Hello", env) {} 22 | 23 | void assemble() override { 24 | r_hello.declare_trigger(&timer); 25 | r_terminate.declare_trigger(&sa); 26 | } 27 | 28 | static void hello() { std::cout << "Hello World!\n"; } 29 | 30 | static void terminate() { std::cout << "Good Bye!\n"; } 31 | }; 32 | 33 | auto main() -> int { 34 | Environment env{4, false, 5s}; 35 | 36 | Hello hello{&env}; 37 | env.assemble(); 38 | 39 | auto thread = env.startup(); 40 | thread.join(); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /include/reactor-cpp/connection_properties.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Tassilo Tanneberger 7 | */ 8 | 9 | #ifndef REACTOR_CPP_CONNECTION_PROPERTIES_HH 10 | #define REACTOR_CPP_CONNECTION_PROPERTIES_HH 11 | 12 | #include "fwd.hh" 13 | #include "logical_time.hh" 14 | #include 15 | 16 | namespace reactor { 17 | 18 | enum ConnectionType : std::uint8_t { 19 | Normal, 20 | Delayed, 21 | Enclaved, 22 | Physical, 23 | DelayedEnclaved, 24 | PhysicalEnclaved, 25 | Plugin, 26 | Invalid 27 | }; 28 | struct ConnectionProperties { 29 | ConnectionType type_ = ConnectionType::Normal; 30 | Duration delay_{0}; 31 | Environment* enclave_{nullptr}; 32 | 33 | auto operator<(const ConnectionProperties& elem2) const noexcept -> bool { 34 | return (this->type_ < elem2.type_) || (this->type_ == elem2.type_ && this->delay_ < elem2.delay_); 35 | } 36 | 37 | auto operator==(const ConnectionProperties& elem2) const noexcept -> bool { 38 | return this->type_ == elem2.type_ && this->delay_ == elem2.delay_; 39 | } 40 | }; 41 | 42 | } // namespace reactor 43 | 44 | #endif // REACTOR_CPP_CONNECTION_PROPERTIES_HH 45 | -------------------------------------------------------------------------------- /examples/count/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "reactor-cpp/reactor-cpp.hh" 4 | 5 | using namespace reactor; 6 | using namespace std::chrono_literals; 7 | 8 | class Count : public Reactor { 9 | private: 10 | // actions 11 | Timer timer{"timer", this}; 12 | LogicalAction counter{"counter", this}; 13 | 14 | // reactions_ 15 | Reaction r_init{"r_init", 1, this, [this]() { init(); }}; 16 | Reaction r_counter{"r_counter", 2, this, [this]() { print_count(); }}; 17 | 18 | public: 19 | explicit Count(Environment* env) 20 | : Reactor("Count", env) {} 21 | 22 | void assemble() override { 23 | r_init.declare_trigger(&timer); 24 | r_counter.declare_trigger(&counter); 25 | } 26 | 27 | void print_count() { 28 | const auto& value = *(counter.get()); 29 | std::cout << "Count: " << value << '\n'; 30 | counter.schedule(value + 1, 1s); 31 | } 32 | 33 | void init() { 34 | std::cout << "Hello World!\n"; 35 | counter.schedule(0, 1s); 36 | } 37 | }; 38 | 39 | auto main() -> int { 40 | Environment env{4}; 41 | 42 | Count count{&env}; 43 | env.assemble(); 44 | 45 | auto thread = env.startup(); 46 | thread.join(); 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /lib/multiport.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Tassilo Tanneberger 7 | * Christian Menard 8 | */ 9 | 10 | #include "reactor-cpp/multiport.hh" 11 | #include "reactor-cpp/port.hh" 12 | 13 | auto reactor::BaseMultiport::get_set_callback(std::size_t index) noexcept -> reactor::PortCallback { 14 | // tells the parent multiport that this port has been set. 15 | return [this, index](const BasePort& port) { 16 | // if the port is present, the callback was already invoked before 17 | if (!port.is_present()) { 18 | this->set_present(index); 19 | } 20 | }; 21 | } 22 | 23 | void reactor::BaseMultiport::set_present(std::size_t index) { 24 | auto calculated_index = size_.fetch_add(1, std::memory_order_relaxed); 25 | 26 | reactor_assert(calculated_index < present_ports_.size()); 27 | 28 | present_ports_[calculated_index] = index; 29 | } 30 | 31 | void reactor::BaseMultiport::register_port(BasePort& port, size_t idx) { 32 | // need to add one new slot t the present list 33 | reactor_assert(this->present_ports_.size() == idx); 34 | this->present_ports_.emplace_back(0); 35 | 36 | // and we need to register callbacks on the port 37 | port.register_set_callback(this->get_set_callback(idx)); 38 | port.register_clean_callback(this->get_clean_callback()); 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | name: Try compilation on ${{ matrix.os }} 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | os: [ubuntu-24.04, windows-latest, macos-latest] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: configure 22 | run: | 23 | mkdir build 24 | cd build 25 | cmake .. -DREACTOR_CPP_CLANG_TIDY=Off 26 | - name: build the library 27 | run: cmake --build build 28 | - name: build examples 29 | run: 30 | cmake --build build --target examples 31 | - name: build with tracing enabled 32 | run: | 33 | sudo apt-get install -y liblttng-ust-dev 34 | cd build 35 | cmake -DREACTOR_CPP_TRACE=ON .. 36 | cd .. 37 | cmake --build build --target examples 38 | if: matrix.os == 'ubuntu-24.04' 39 | 40 | lf-tests-pull-request: 41 | uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master 42 | with: 43 | runtime-ref: ${{github.ref}} 44 | if: ${{ github.event_name == 'pull_request' }} 45 | lf-tests-push: 46 | uses: lf-lang/lingua-franca/.github/workflows/cpp-tests.yml@master 47 | with: 48 | runtime-ref: ${{github.ref_name}} 49 | if: ${{ github.event_name == 'push' }} 50 | -------------------------------------------------------------------------------- /nix/lfc.nix: -------------------------------------------------------------------------------- 1 | { pkgs 2 | , config 3 | , lib 4 | , mkDerivation 5 | , jdk17_headless 6 | , lingua-franca-src 7 | }: 8 | let 9 | # takes the first file in the jars repo which should be the relavant jar file 10 | jar_path = (builtins.head (builtins.attrNames (builtins.readDir "${lingua-franca-src}/lib/jars"))); 11 | 12 | # filter out name 13 | extracted_name = (builtins.head (lib.reverseList (builtins.split "/" jar_path))); 14 | 15 | in 16 | mkDerivation { 17 | pname = "lfc"; 18 | version = "0.1.0"; 19 | 20 | src = lingua-franca-src; 21 | 22 | buildInputs = [ jdk17_headless ]; 23 | 24 | _JAVA_HOME = "${jdk17_headless}/"; 25 | 26 | postPatch = '' 27 | substituteInPlace bin/lfc \ 28 | --replace 'base=`dirname $(dirname ''${abs_path})`' "base='$out'" \ 29 | --replace "run_lfc_with_args" "${jdk17_headless}/bin/java -jar $out/lib/jars/${extracted_name}" 30 | ''; 31 | 32 | buildPhase = '' 33 | echo "SKIPPING BUILDPHASE FOR LFC" 34 | ''; 35 | 36 | installPhase = '' 37 | cp -r ./ $out/ 38 | chmod +x $out/bin/lfc 39 | ''; 40 | 41 | meta = with lib; { 42 | description = "Polyglot coordination language"; 43 | longDescription = '' 44 | Lingua Franca (LF) is a polyglot coordination language for concurrent 45 | and possibly time-sensitive applications ranging from low-level 46 | embedded code to distributed cloud and edge applications. 47 | ''; 48 | homepage = "https://github.com/lf-lang/lingua-franca"; 49 | license = licenses.bsd2; 50 | platforms = platforms.linux; 51 | maintainers = with maintainers; [ revol-xut ]; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /lib/assert.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #include "reactor-cpp/assert.hh" 10 | #include "reactor-cpp/environment.hh" 11 | #include "reactor-cpp/reactor_element.hh" 12 | 13 | namespace reactor { 14 | 15 | auto ValidationError::build_message(const std::string_view msg) noexcept -> std::string { 16 | std::stringstream string_stream; 17 | string_stream << "Validation Error! \"" << msg << "\""; 18 | return string_stream.str(); 19 | } 20 | 21 | void assert_phase([[maybe_unused]] const ReactorElement* ptr, [[maybe_unused]] Phase phase) { 22 | if constexpr (runtime_assertion) { 23 | if (ptr->environment()->phase() != phase) { 24 | auto enum_value_to_name = [](Phase phase) -> std::string { 25 | const std::map conversation_map = { 26 | {Phase::Construction, "Construction"}, {Phase::Assembly, "Assembly"}, 27 | {Phase::Startup, "Startup"}, {Phase::Execution, "Execution"}, 28 | {Phase::Shutdown, "Shutdown"}, {Phase::Deconstruction, "Deconstruction"}}; 29 | // in C++20 use .contains() 30 | if (conversation_map.find(phase) != std::end(conversation_map)) { 31 | return conversation_map.at(phase); 32 | } 33 | return "Unknown Phase: Value: " + std::to_string(extract_value(phase)); 34 | }; 35 | print_backtrace(); 36 | 37 | // C++20 std::format 38 | throw ValidationError("Expected Phase: " + enum_value_to_name(phase) + 39 | " Current Phase: " + enum_value_to_name(ptr->environment()->phase())); 40 | } 41 | } 42 | } 43 | 44 | } // namespace reactor 45 | -------------------------------------------------------------------------------- /include/reactor-cpp/safe_vector.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_SAFE_VECTOR_HH 10 | #define REACTOR_CPP_SAFE_VECTOR_HH 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace reactor { 19 | 20 | template class SafeVector { 21 | private: 22 | static constexpr std::size_t size_increment_{100}; 23 | std::atomic write_pos_{0}; 24 | std::size_t vector_size_{size_increment_}; 25 | std::vector vector_{size_increment_}; 26 | std::shared_mutex mutex_; 27 | 28 | public: 29 | void push_back(const T& value) { 30 | auto pos = write_pos_.fetch_add(1, std::memory_order_release); 31 | 32 | { 33 | std::shared_lock shared_lock(mutex_); 34 | if (pos < vector_size_) { 35 | vector_[pos] = value; 36 | } else { 37 | shared_lock.unlock(); 38 | { 39 | std::unique_lock unique_lock(mutex_); 40 | while (pos >= vector_size_) { 41 | vector_size_ += size_increment_; 42 | vector_.resize(vector_size_); 43 | } 44 | vector_[pos] = value; 45 | } 46 | } 47 | } 48 | } 49 | 50 | // careful! Using the iterators is only safe if no more elements are added to the vector! 51 | auto begin() -> auto { return vector_.begin(); } 52 | auto end() -> auto { return vector_.begin() + write_pos_.load(std::memory_order_acquire); } 53 | 54 | auto size() -> std::size_t { return write_pos_.load(std::memory_order_acquire); } 55 | auto empty() -> bool { return size() == 0; } 56 | 57 | void clear() { write_pos_.store(0, std::memory_order_release); } 58 | }; 59 | 60 | } // namespace reactor 61 | 62 | #endif // REACTOR_CPP_SAFE_VECTOR_HH 63 | -------------------------------------------------------------------------------- /include/reactor-cpp/reactor_element.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Tassilo Tanneberger 7 | * Christian Menard 8 | */ 9 | 10 | #ifndef REACTOR_CPP_REACTOR_ELEMENT_HH 11 | #define REACTOR_CPP_REACTOR_ELEMENT_HH 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "fwd.hh" 20 | 21 | namespace reactor { 22 | class ReactorElement { // NOLINT(cppcoreguidelines-special-member-functions) 23 | private: 24 | std::string name_{}; 25 | std::string fqn_{}; 26 | 27 | // The reactor owning this element 28 | Reactor* container_{nullptr}; 29 | Environment* environment_{}; 30 | 31 | auto fqn_detail(std::stringstream& string_stream) const noexcept -> std::stringstream&; 32 | 33 | public: 34 | enum class Type : std::uint8_t { Action, Port, Reaction, Reactor, Input, Output }; 35 | 36 | ReactorElement(const std::string& name, Type type, Reactor* container); 37 | ReactorElement(const std::string& name, Type type, Environment* environment); 38 | virtual ~ReactorElement() = default; 39 | 40 | // not copyable, but movable 41 | ReactorElement(const ReactorElement&) = delete; 42 | ReactorElement(ReactorElement&&) = default; 43 | 44 | [[nodiscard]] auto container() const noexcept -> Reactor* { return container_; } 45 | 46 | [[nodiscard]] auto name() const noexcept -> const std::string& { return name_; } 47 | [[nodiscard]] auto fqn() const noexcept -> const std::string& { return fqn_; } 48 | [[nodiscard]] auto environment() noexcept -> Environment* { return environment_; } 49 | [[nodiscard]] auto environment() const noexcept -> const Environment* { return environment_; } 50 | 51 | [[nodiscard]] auto is_top_level() const noexcept -> bool { return this->container() == nullptr; } 52 | 53 | virtual void startup() = 0; 54 | virtual void shutdown() = 0; 55 | }; 56 | } // namespace reactor 57 | 58 | #endif // REACTOR_CPP_REACTOR_ELEMENT_HH 59 | -------------------------------------------------------------------------------- /lib/time.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | // tell MSCV not to worry about the potential unsafe use of localtime 10 | #ifdef _MSC_VER 11 | #pragma warning(disable : 4996) 12 | #endif 13 | 14 | #include "reactor-cpp/time.hh" 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace reactor { 21 | 22 | constexpr std::size_t TIME_TO_STR_BUFFER_SIZE{20}; 23 | constexpr std::size_t NANOSECONDS_IN_ONE_SECOND{1'000'000'000UL}; 24 | constexpr std::size_t NANOSECOND_DIGITS{9}; 25 | 26 | inline namespace operators { 27 | 28 | auto operator<<(std::ostream& os, TimePoint tp) -> std::ostream& { 29 | std::array buf{}; 30 | time_t time = 31 | std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(tp)); 32 | auto res = std::strftime(buf.data(), sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&time)); 33 | auto epoch = std::chrono::duration_cast(tp.time_since_epoch()); 34 | 35 | if (res != 0) { 36 | os << buf.data() << '.' << std::setw(NANOSECOND_DIGITS) << std::setfill('0') 37 | << epoch.count() % NANOSECONDS_IN_ONE_SECOND; 38 | } else { 39 | os << "[INVALID TIME]"; 40 | } 41 | 42 | return os; 43 | } 44 | 45 | auto operator<<(std::ostream& os, std::chrono::seconds dur) -> std::ostream& { 46 | os << dur.count() << " secs"; 47 | return os; 48 | } 49 | auto operator<<(std::ostream& os, std::chrono::milliseconds dur) -> std::ostream& { 50 | os << dur.count() << " msecs"; 51 | return os; 52 | } 53 | auto operator<<(std::ostream& os, std::chrono::microseconds dur) -> std::ostream& { 54 | os << dur.count() << " usecs"; 55 | return os; 56 | } 57 | auto operator<<(std::ostream& os, std::chrono::nanoseconds dur) -> std::ostream& { 58 | os << dur.count() << " nsecs"; 59 | return os; 60 | } 61 | 62 | } // namespace operators 63 | 64 | } // namespace reactor 65 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "test and build script for the reactor-cpp runtime"; 3 | 4 | inputs = { 5 | utils.url = "github:numtide/flake-utils"; 6 | nixpkgs.url = "github:NixOs/nixpkgs/nixos-unstable"; 7 | lf-benchmark-runner = { 8 | url = "github:revol-xut/lf-benchmark-runner"; 9 | }; 10 | 11 | # input for the reactor-cpp 12 | reactor-cpp = { 13 | url = "github:lf-lang/reactor-cpp"; 14 | flake = false; 15 | }; 16 | 17 | # source for the lingu franca compiler 18 | lingua-franca-src = { 19 | url = "https://github.com/lf-lang/lingua-franca/releases/download/v0.3.0/lfc_0.3.0.tar.gz"; 20 | flake = false; 21 | }; 22 | 23 | # determines the the lingua-franca version from which the test are taken from 24 | lingua-franca-tests = { 25 | url = "github:lf-lang/lingua-franca"; 26 | flake = false; 27 | }; 28 | 29 | # repo that contains the benchmarks 30 | lingua-franca-benchmarks = { 31 | url = "github:lf-lang/benchmarks-lingua-franca"; 32 | flake = false; 33 | }; 34 | }; 35 | 36 | outputs = { utils, nixpkgs, lf-benchmark-runner, reactor-cpp, lingua-franca-src, lingua-franca-tests, lingua-franca-benchmarks, ... }@inputs: 37 | utils.lib.eachDefaultSystem (system: 38 | let 39 | pkgs = nixpkgs.legacyPackages.${system}; 40 | allTests = (pkgs.callPackage ./nix/test.nix { 41 | reactor-cpp-src = reactor-cpp; 42 | lingua-franca-src = lingua-franca-src; 43 | lingua-franca-tests = lingua-franca-tests; 44 | }); 45 | allBenchmarks = pkgs.callPackage ./nix/benchmark.nix { 46 | reactor-cpp-src = reactor-cpp; 47 | lingua-franca-src = lingua-franca-src; 48 | lingua-franca-benchmarks = lingua-franca-benchmarks; 49 | lf-benchmark-runner = lf-benchmark-runner.packages."${system}".lf-benchmark-runner; 50 | rev-reactor = reactor-cpp.narHash; 51 | rev-lingua-franca = lingua-franca-src.narHash; 52 | }; 53 | customPackages = allBenchmarks // allTests // rec { 54 | reactor-cpp = pkgs.callPackage ./nix/reactor-cpp.nix { 55 | mkDerivation = pkgs.stdenv.mkDerivation; 56 | debug = false; 57 | reactor-cpp-src = ./.; 58 | }; 59 | default = reactor-cpp; 60 | }; 61 | in 62 | rec { 63 | checks = packages; 64 | packages = customPackages; 65 | } 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCE_FILES 2 | action.cc 3 | assert.cc 4 | environment.cc 5 | logical_time.cc 6 | port.cc 7 | reaction.cc 8 | reactor.cc 9 | scheduler.cc 10 | time.cc 11 | multiport.cc 12 | reactor_element.cc 13 | ) 14 | 15 | if(REACTOR_CPP_TRACE) 16 | set(SOURCE_FILES ${SOURCE_FILES} trace.cc ) 17 | endif() 18 | 19 | if (DEFINED LF_REACTOR_CPP_SUFFIX) 20 | set(REACTOR_CPP_INCLUDE "include/${LIB_TARGET}") 21 | else() 22 | set(REACTOR_CPP_INCLUDE "include") 23 | endif() 24 | 25 | add_library(${LIB_TARGET} SHARED ${SOURCE_FILES}) 26 | target_include_directories(${LIB_TARGET} PUBLIC 27 | "$" 28 | "$" 29 | "$" 30 | "$" 31 | PRIVATE src) 32 | 33 | if(MSVC) 34 | target_compile_options(${LIB_TARGET} PRIVATE /W4 /WX) 35 | else() 36 | target_compile_options(${LIB_TARGET} PRIVATE -Wall -Wextra -pedantic -Werror) 37 | endif() 38 | 39 | if(${Backtrace_FOUND}) 40 | target_include_directories(${LIB_TARGET} PRIVATE ${Backtrace_INCLUDE_DIRS}) 41 | target_link_libraries(${LIB_TARGET} ${Backtrace_LIBRARY}) 42 | endif() 43 | 44 | target_link_libraries(${LIB_TARGET} ${CMAKE_THREAD_LIBS_INIT}) 45 | if(REACTOR_CPP_TRACE) 46 | target_link_libraries(${LIB_TARGET} LTTng::UST) 47 | endif() 48 | 49 | # Generates Compilation Database for clang-tidy 50 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 51 | 52 | set_target_properties(${LIB_TARGET} PROPERTIES 53 | VERSION ${PROJECT_VERSION} 54 | CXX_CLANG_TIDY "${CMAKE_CXX_CLANG_TIDY}" 55 | SOVERSION 1) 56 | 57 | if(REACTOR_CPP_INSTALL) 58 | if(DEFINED LF_REACTOR_CPP_SUFFIX) 59 | install(FILES "${PROJECT_BINARY_DIR}/include/reactor-cpp/config.hh" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${LIB_TARGET}/reactor-cpp") 60 | else() 61 | install(FILES "${PROJECT_BINARY_DIR}/include/reactor-cpp/config.hh" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/reactor-cpp") 62 | endif() 63 | 64 | install(TARGETS ${LIB_TARGET} EXPORT ${LIB_TARGET}Config 65 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL 66 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL 67 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL) 68 | 69 | install(EXPORT ${LIB_TARGET}Config DESTINATION share/${LIB_TARGET}/cmake) 70 | 71 | export(TARGETS ${PROJECT_NAME} FILE ${LIB_TARGET}Config.cmake) 72 | endif() 73 | -------------------------------------------------------------------------------- /tracing/README.md: -------------------------------------------------------------------------------- 1 | Tracing reactor programms 2 | --- 3 | 4 | Optionally reactor-cpp can be build with tracing support. This provides a 5 | powerful tool for analyzing and debugging reactor applications. 6 | 7 | ## Required Dependencies 8 | 9 | - [LTTng-ust](https://lttng.org) for recording traces 10 | - [Babeltrace2](https://babeltrace.org/) with python bindings for converting traces 11 | - Google Chrome or Chromium for viewing traces 12 | 13 | ## Build 14 | 15 | 1. Install LTTng as described [here](https://lttng.org/docs/#doc-installing-lttng) 16 | 2. Build and install Babeltrace2 including the python bindings as described [here](https://github.com/efficios/babeltrace/blob/stable-2.0/README.adoc) 17 | 3. Build reactor-cpp with tracing enabled: 18 | ```sh 19 | mkdir build 20 | cd build 21 | cmake -DREACTOR_CPP_TRACE=ON.. 22 | make 23 | ``` 24 | 4. Build your reactor application using reactor-cpp as normal (e.g. `make examples`) 25 | 26 | ## Usage 27 | 28 | This directory contains a set of scripts that help in recording and converting 29 | traces. Run `start_tracing.sh` to start and configure a lttng user space session. Then 30 | start the application that you would like to trace 31 | (e.g. `examples/hello/hello`). Use `stop_tracing.sh` to end the lttng session 32 | after your application finished or when you want to abort tracing. This ensures 33 | that all trace data is properly written to the files. 34 | 35 | In order to view the trace, you have to convert it to a json file using 36 | `ctf_to_json.py `. `` is the output 37 | directory reported by `start_tracing.sh`. This produces a file 38 | `trace.json`. Optionally, the default output file can be overridden using `-o` 39 | or `--output`. If the conversion script fails, make sure that the generated 40 | Babeltrace2 python bindings are within the `PYTHONPATH` and that `libabeltrace2.so` can be found 41 | within `LD_LIBRARY_PATH`. 42 | 43 | The trace can be viewed by opening `about://tracing` in Chrome (or 44 | Chromium). There you can load the previously generated json file which produces 45 | a visualization of the trace. In this visualization, the top part (labeled *Execution*), 46 | shows the physical time at which reactions execute. The bottom part shows individual reactors and logical times at which 47 | actions are scheduled and reactions are triggered. 48 | 49 | ![Screenshot_20200512_165849](https://user-images.githubusercontent.com/6460123/81709144-fcb29a00-9471-11ea-9032-95cb6a368e98.png) 50 | 51 | -------------------------------------------------------------------------------- /include/reactor-cpp/reactor.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_REACTOR_HH 10 | #define REACTOR_CPP_REACTOR_HH 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "action.hh" 17 | #include "environment.hh" 18 | #include "logical_time.hh" 19 | #include "reactor_element.hh" 20 | 21 | namespace reactor { 22 | class Reactor : public ReactorElement { // NOLINT(cppcoreguidelines-special-member-functions) 23 | 24 | private: 25 | std::set actions_{}; 26 | std::set inputs_{}; 27 | std::set outputs_{}; 28 | std::set reactions_{}; 29 | std::set reactors_{}; 30 | std::set> connections_{}; 31 | 32 | void register_action(BaseAction* action); 33 | void register_input(BasePort* port); 34 | void register_output(BasePort* port); 35 | void register_reaction(Reaction* reaction); 36 | void register_reactor(Reactor* reactor); 37 | 38 | public: 39 | Reactor(const std::string& name, Reactor* container); 40 | Reactor(const std::string& name, Environment* environment); 41 | ~Reactor() override = default; 42 | 43 | void register_connection(std::unique_ptr&& connection); 44 | [[nodiscard]] auto actions() const noexcept -> const auto& { return actions_; } 45 | [[nodiscard]] auto inputs() const noexcept -> const auto& { return inputs_; } 46 | [[nodiscard]] auto outputs() const noexcept -> const auto& { return outputs_; } 47 | [[nodiscard]] auto reactions() const noexcept -> const auto& { return reactions_; } 48 | [[nodiscard]] auto reactors() const noexcept -> const auto& { return reactors_; } 49 | [[nodiscard]] auto number_of_connections() const noexcept -> std::size_t { return connections_.size(); } 50 | 51 | void startup() final; 52 | void shutdown() final; 53 | 54 | virtual void assemble() = 0; 55 | 56 | [[nodiscard]] static auto get_physical_time() noexcept -> TimePoint; 57 | [[nodiscard]] auto get_logical_time() const noexcept -> TimePoint; 58 | [[nodiscard]] auto get_microstep() const noexcept -> mstep_t; 59 | [[nodiscard]] auto get_tag() const noexcept -> Tag; 60 | [[nodiscard]] auto get_elapsed_logical_time() const noexcept -> Duration; 61 | [[nodiscard]] auto get_elapsed_physical_time() const noexcept -> Duration; 62 | 63 | friend ReactorElement; 64 | }; 65 | 66 | } // namespace reactor 67 | 68 | #endif // REACTOR_CPP_REACTOR_HH 69 | -------------------------------------------------------------------------------- /include/reactor-cpp/assert.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_ASSERT_HH 10 | #define REACTOR_CPP_ASSERT_HH 11 | 12 | #include "reactor-cpp/config.hh" 13 | #include "reactor-cpp/fwd.hh" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #ifdef REACTOR_CPP_VALIDATE 21 | constexpr bool runtime_validation = true; 22 | #else 23 | constexpr bool runtime_validation = false; 24 | #endif 25 | 26 | #ifdef NDEBUG 27 | constexpr bool runtime_assertion = false; 28 | #else 29 | constexpr bool runtime_assertion = true; 30 | #endif 31 | 32 | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) 33 | #define reactor_assert(x) assert(x) 34 | 35 | #ifdef REACTOR_CPP_USE_BACKTRACE 36 | 37 | // NOLINTNEXTLINE(llvm-include-order) 38 | #include REACTOR_CPP_BACKTRACE_HEADER 39 | #include 40 | #include 41 | 42 | namespace reactor { 43 | 44 | constexpr std::size_t MAX_TRACE_SIZE{16}; 45 | 46 | inline void print_backtrace() { 47 | std::array trace{nullptr}; 48 | int size = backtrace(trace.data(), MAX_TRACE_SIZE); 49 | char** messages = backtrace_symbols(trace.data(), size); 50 | for (int i{0}; i < size; i++) { 51 | // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 52 | std::cerr << "[backtrace] " << messages[i] << '\n'; 53 | } 54 | } 55 | 56 | } // namespace reactor 57 | #else 58 | namespace reactor { 59 | inline void print_backtrace() {} 60 | } // namespace reactor 61 | #endif // REACTOR_CPP_BACKTRACE_SUPPORT 62 | 63 | namespace reactor { 64 | 65 | class ValidationError : public std::runtime_error { 66 | private: 67 | static auto build_message(std::string_view msg) noexcept -> std::string; 68 | 69 | public: 70 | explicit ValidationError(const std::string_view msg) 71 | : std::runtime_error(build_message(msg)) {} 72 | }; 73 | 74 | constexpr void validate([[maybe_unused]] bool condition, [[maybe_unused]] const std::string_view message) { 75 | if constexpr (runtime_validation) { 76 | if (!condition) { 77 | print_backtrace(); 78 | throw ValidationError(message); 79 | } 80 | } 81 | } 82 | 83 | template constexpr auto extract_value(E enum_value) -> typename std::underlying_type_t { 84 | return static_cast>(enum_value); 85 | } 86 | 87 | void assert_phase([[maybe_unused]] const ReactorElement* ptr, [[maybe_unused]] Phase phase); 88 | 89 | } // namespace reactor 90 | 91 | #endif // REACTOR_CPP_ASSERT_HH 92 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | 3 | if (DEFINED LF_REACTOR_CPP_SUFFIX) 4 | # the build is triggered from Lingua Franca 5 | set(LIB_TARGET "reactor-cpp-${LF_REACTOR_CPP_SUFFIX}") 6 | else() 7 | set(LIB_TARGET "reactor-cpp") 8 | endif() 9 | 10 | project(${LIB_TARGET} LANGUAGES CXX VERSION 0.0.1) 11 | 12 | # require C++17 13 | set(CMAKE_CXX_STANDARD 17) 14 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 15 | set(CMAKE_CXX_EXTENSIONS OFF) 16 | 17 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) 18 | 19 | # Use thw new CMP0068 policy. This needs to be set explicitly to avoid a warning message 20 | cmake_policy(SET CMP0068 NEW) 21 | set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) 22 | set(BUILD_WITH_INSTALL_NAME_DIR ON) 23 | 24 | option(REACTOR_CPP_PRINT_STATISTICS "Print statistics after execution" OFF) 25 | option(REACTOR_CPP_TRACE "Enable tracing" OFF) 26 | option(REACTOR_CPP_VALIDATE "Enable runtime validation" ON) 27 | option(REACTOR_CPP_INSTALL "Install the reactor-cpp target" On) 28 | option(REACTOR_CPP_CLANG_TIDY "Enable building with clang-tidy " On) 29 | 30 | if (REACTOR_CPP_CLANG_TIDY AND NOT DEFINED LF_REACTOR_CPP_SUFFIX) 31 | find_program(CLANG_TIDY clang-tidy) 32 | if (CLANG_TIDY) 33 | set(CMAKE_CXX_CLANG_TIDY clang-tidy; -header-filter=reactor-cpp/\(.*\)\\.hh; -warnings-as-errors=*;) 34 | else () 35 | message(WARNING "Please install clang-tidy!") 36 | endif() 37 | endif() 38 | 39 | find_package(Threads) 40 | 41 | find_package(Backtrace) 42 | set(REACTOR_CPP_USE_BACKTRACE ${Backtrace_FOUND}) 43 | 44 | set(DEFAULT_BUILD_TYPE "Release") 45 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 46 | message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.") 47 | set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "Choose the type of build." FORCE) 48 | # Set the possible values of build type for cmake-gui 49 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") 50 | endif() 51 | 52 | if (NOT DEFINED REACTOR_CPP_LOG_LEVEL) 53 | set(REACTOR_CPP_LOG_LEVEL 3) 54 | endif() 55 | 56 | if(REACTOR_CPP_TRACE) 57 | find_package(LTTngUST REQUIRED) 58 | endif() 59 | 60 | configure_file(include/reactor-cpp/config.hh.in include/reactor-cpp/config.hh @ONLY) 61 | 62 | include(GNUInstallDirs) 63 | 64 | add_subdirectory(lib) 65 | if(NOT DEFINED LF_REACTOR_CPP_SUFFIX) 66 | add_subdirectory(examples) 67 | endif() 68 | 69 | if (DEFINED LF_REACTOR_CPP_SUFFIX) 70 | install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${LIB_TARGET}") 71 | else() 72 | install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") 73 | endif() 74 | -------------------------------------------------------------------------------- /include/reactor-cpp/reaction.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_REACTION_HH 10 | #define REACTOR_CPP_REACTION_HH 11 | 12 | #include 13 | #include 14 | 15 | #include "fwd.hh" 16 | #include "logical_time.hh" 17 | #include "reactor_element.hh" 18 | 19 | namespace reactor { 20 | 21 | class Reaction : public ReactorElement { // NOLINT(cppcoreguidelines-special-member-functions) 22 | private: 23 | std::set action_triggers_; 24 | std::set scheduable_actions_; 25 | std::set port_trigger_; 26 | std::set antidependencies_; 27 | std::set dependencies_; 28 | 29 | const int priority_; 30 | unsigned int index_{}; 31 | 32 | std::function body_{nullptr}; 33 | 34 | Duration deadline_{Duration::zero()}; 35 | std::function deadline_handler_{nullptr}; 36 | 37 | void set_deadline_impl(Duration deadline, const std::function& handler); 38 | 39 | public: 40 | Reaction(const std::string& name, int priority, Reactor* container, std::function body); 41 | 42 | ~Reaction() override = default; 43 | 44 | void declare_trigger(BaseAction* action); 45 | void declare_trigger(BasePort* port); 46 | void declare_schedulable_action(BaseAction* action); 47 | void declare_antidependency(BasePort* port); 48 | void declare_dependency(BasePort* port); 49 | 50 | [[nodiscard]] auto action_triggers() const noexcept -> const auto& { return action_triggers_; } 51 | 52 | [[nodiscard]] auto port_triggers() const noexcept -> const auto& { return port_trigger_; } 53 | 54 | [[maybe_unused]] [[nodiscard]] auto antidependencies() const noexcept -> const auto& { return antidependencies_; } 55 | 56 | [[nodiscard]] auto dependencies() const noexcept -> const auto& { return dependencies_; } 57 | 58 | [[maybe_unused]] [[nodiscard]] auto scheduable_actions() const noexcept -> const auto& { return scheduable_actions_; } 59 | 60 | [[nodiscard]] auto priority() const noexcept -> int { return priority_; } 61 | 62 | void startup() final {} 63 | void shutdown() final {} 64 | void trigger(); 65 | void set_index(unsigned index); 66 | 67 | template void set_deadline(Dur deadline, const std::function& handler) { 68 | set_deadline_impl(std::chrono::duration_cast(deadline), handler); 69 | } 70 | 71 | [[nodiscard]] auto has_deadline() const noexcept -> bool { return deadline_ != Duration::zero(); } 72 | 73 | [[nodiscard]] auto index() const noexcept -> unsigned int { return index_; } 74 | }; 75 | 76 | } // namespace reactor 77 | 78 | #endif // REACTOR_CPP_REACTION_HH 79 | -------------------------------------------------------------------------------- /lib/logical_time.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #include "reactor-cpp/logical_time.hh" 10 | 11 | #include "reactor-cpp/assert.hh" 12 | #include 13 | 14 | namespace reactor { 15 | 16 | auto operator==(const Tag& lhs, const Tag& rhs) noexcept -> bool { 17 | return lhs.time_point() == rhs.time_point() && lhs.micro_step() == rhs.micro_step(); 18 | } 19 | 20 | auto operator<(const Tag& lhs, const Tag& rhs) noexcept -> bool { 21 | return lhs.time_point() < rhs.time_point() || 22 | (lhs.time_point() == rhs.time_point() && lhs.micro_step() < rhs.micro_step()); 23 | } 24 | 25 | auto Tag::from_physical_time(TimePoint time_point) noexcept -> Tag { return {time_point, 0}; } 26 | 27 | auto Tag::from_logical_time(const LogicalTime& logical_time) noexcept -> Tag { 28 | return {logical_time.time_point(), logical_time.micro_step()}; 29 | } 30 | 31 | auto Tag::delay(Duration offset) const noexcept -> Tag { 32 | if (offset == Duration::zero()) { 33 | validate(this->micro_step_ != std::numeric_limits::max(), "Microstep overflow detected!"); 34 | return {this->time_point_, this->micro_step_ + 1}; 35 | } 36 | return {this->time_point_ + offset, 0}; 37 | } 38 | 39 | auto Tag::subtract(Duration offset) const noexcept -> Tag { 40 | if (offset == Duration::zero()) { 41 | return decrement(); 42 | } 43 | return {time_point_ - offset, std::numeric_limits::max()}; 44 | } 45 | 46 | auto Tag::decrement() const noexcept -> Tag { 47 | if (micro_step_ == 0) { 48 | return {time_point_ - Duration{1}, std::numeric_limits::max()}; 49 | } 50 | return {time_point_, micro_step_ - 1}; 51 | } 52 | 53 | void LogicalTime::advance_to(const Tag& tag) { 54 | reactor_assert(*this < tag); 55 | time_point_ = tag.time_point(); 56 | micro_step_ = tag.micro_step(); 57 | } 58 | 59 | void LogicalTime::advance_to(const LogicalTime& time) { 60 | reactor_assert(*this < Tag::from_logical_time(time)); 61 | time_point_ = time.time_point(); 62 | micro_step_ = time.micro_step(); 63 | } 64 | 65 | auto operator==(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { 66 | return logical_time.time_point() == tag.time_point() && logical_time.micro_step() == tag.micro_step(); 67 | } 68 | 69 | auto operator<(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { 70 | return logical_time.time_point() < tag.time_point() || 71 | (logical_time.time_point() == tag.time_point() && logical_time.micro_step() < tag.micro_step()); 72 | } 73 | 74 | auto operator>(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { 75 | return logical_time.time_point() > tag.time_point() || 76 | (logical_time.time_point() == tag.time_point() && logical_time.micro_step() > tag.micro_step()); 77 | } 78 | 79 | auto operator<<(std::ostream& os, const Tag& tag) -> std::ostream& { 80 | return os << '[' << tag.time_point() << ", " << tag.micro_step() << ']'; 81 | } 82 | auto operator<<(std::ostream& os, const LogicalTime& tag) -> std::ostream& { 83 | return os << '[' << tag.time_point() << ", " << tag.micro_step() << ']'; 84 | } 85 | 86 | } // namespace reactor 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Reactor-Oriented Programming Framework in C++ 2 | --- 3 | 4 | ![CI](https://github.com/lf-lang/reactor-cpp/workflows/CI/badge.svg) 5 | 6 | > [!IMPORTANT] 7 | > This repository is currently not actively maintained and developed as the core contributors focus on other projects. Please check out the [Xronos SDK](https://github.com/xronos-inc/xronos) which uses a fork of this runtime and [reactor-uc](https://github.com/lf-lang/reactor-uc) which applies the same underlying ideas to embedded devices. 8 | 9 | While reactor-cpp can be used as a standalone framework, it is designed to work 10 | in conjunction with [Lingua Franca](https://github.com/lf-lang/lingua-franca/), 11 | a polyglot metaprogramming language. Read the Lingua Franca [handbook](https://www.lf-lang.org/docs/) 12 | to get an overview of the reactor model. 13 | 14 | ## Build 15 | 16 | ```sh 17 | mkdir build 18 | cd build 19 | cmake .. 20 | make 21 | ``` 22 | 23 | The examples need to be built explicitly. 24 | Alternatively, take a look at the [CONTRIBUTING.md](CONTRIBUTING.md) for building with nix package manager. 25 | 26 | ## Extras 27 | reactor-cpp can be built with [tracing support](https://github.com/lf-lang/reactor-cpp/tree/master/tracing). 28 | This provides a powerful tool for analyzing and debugging reactor applications. 29 | 30 | ## Contributing 31 | 32 | For general guidelines about contributing, see [CONTRIBUTING.md](CONTRIBUTING.md). 33 | 34 | 59 | 60 | ## Publications 61 | 62 | * **Phd Thesis:** Christian Menard, ["Deterministic Reactive Programming for Cyber-physical Systems"](https://nbn-resolving.org/urn:nbn:de:bsz:14-qucosa2-916872), PhD thesis, TU Dresden, 205 pp., Jun 2024. 63 | * **TACO'23:** Christian Menard, Marten Lohstroh, Soroush Bateni, Matthew Chorlian, Arthur Deng, Peter Donovan, Clément Fournier, Shaokai Lin, Felix Suchert, Tassilo Tanneberger, Hokeun Kim, Jeronimo Castrillon, and Edward A. Lee. 2023. ["High-performance Deterministic Concurrency Using Lingua Franca"](https://doi.org/10.1145/3617687). ACM Transaction on Architecure and Code Optimization, Volume 20, Issue 4, Article 48 (December 2023), 29 pages. 64 | * **DATE'20:** Christian Menard, Andrés Goens, Marten Lohstroh, Jeronimo Castrillon, ["Achieving Determinism in Adaptive AUTOSAR"](https://doi.org/10.23919/DATE48585.2020.9116430), 2020 Design, Automation & Test in Europe Conference & Exhibition (DATE), Grenoble, France, 2020, pp. 822-827. 65 | 66 | More related publications are available on the Lingua Franca [publication page](https://www.lf-lang.org/research). 67 | -------------------------------------------------------------------------------- /nix/test.nix: -------------------------------------------------------------------------------- 1 | { fetchFromGitHub 2 | , pkgs 3 | , lib 4 | , stdenv 5 | , reactor-cpp-src 6 | , lingua-franca-src 7 | , lingua-franca-tests 8 | }: 9 | let 10 | 11 | # custom library which supplies essential functionality 12 | library = pkgs.callPackage ./library.nix { 13 | lingua-franca-src = lingua-franca-src; 14 | reactor-cpp-src = reactor-cpp-src; 15 | }; 16 | 17 | # list of special derivations which cannot be run 18 | keep_alive = [ 19 | "Keepalive.lf" 20 | "AsyncCallback.lf" 21 | "AsyncCallback2.lf" 22 | ]; 23 | 24 | mkDerivation = library.mkDerivation; 25 | 26 | 27 | # searches all lingua-franca files in that repo 28 | tests = (library.search_files "${lingua-franca-tests}/test/Cpp/src"); 29 | 30 | # checks if a given file is in the unrunable file list 31 | is_executable = file: !((lib.lists.count (x: x == file) keep_alive) > 0); 32 | 33 | # list of executable file names 34 | executables = (builtins.filter is_executable tests); 35 | 36 | list_of_derivations = (library.create_derivations tests); 37 | 38 | executables_derivations = (library.create_derivations executables); 39 | 40 | # executes all tests 41 | execute_all = (lib.strings.concatStringsSep "\n" (builtins.map (x: "${x}/bin/${x.name}") executables_derivations)); 42 | 43 | # writes the execute command to file 44 | run_all = (pkgs.writeScriptBin "all-tests" ('' 45 | #!${pkgs.runtimeShell} 46 | 47 | '' + execute_all)); 48 | 49 | # string of all tests 50 | list_of_tests = (lib.strings.concatStringsSep "\n" (builtins.map library.extract_name tests)); 51 | 52 | # script of all tests 53 | all-tests-script = (pkgs.writeScriptBin "all-tests" (list_of_tests + "\n")); 54 | 55 | # derivation which hold the script 56 | list-tests = mkDerivation { 57 | src = ./.; 58 | name = "list-tests"; 59 | installPhase = '' 60 | mkdir -p $out/bin 61 | echo "cat ${all-tests-script}/bin/all-tests" > $out/bin/list-tests 62 | chmod +x $out/bin/list-tests 63 | ''; 64 | }; 65 | 66 | # copies all the binaries 67 | install_command = (library.create_install_command (library.create_derivations tests)); 68 | 69 | # package that triggers a build of every test file 70 | all-tests = mkDerivation { 71 | src = ./.; 72 | name = "all-tests"; 73 | buildInputs = list_of_derivations; 74 | installPhase = '' 75 | ${pkgs.coreutils}/bin/mkdir -p $out/bin 76 | ${pkgs.coreutils}/bin/cp ${run_all}/bin/* $out/bin/ 77 | '' + install_command; 78 | }; 79 | 80 | # function that takes a list of attributes [ { name = "a"; value = "b";}] and transforms it into 81 | # a list with all the values which are derivations in that case: e.g. ["b"] 82 | extract_derivations = (list: lib.attrValues (lib.listToAttrs list)); 83 | 84 | # takes the cartisean product of packages and compilers 85 | attribute_set_derivations = (library.double_map tests library.compilers library.buildDerivation); 86 | 87 | # creates for every package a memtest version for debug purposes 88 | attribute_set_memory = (builtins.map library.memtest (extract_derivations attribute_set_derivations)); 89 | in 90 | lib.listToAttrs (attribute_set_derivations 91 | ++ attribute_set_memory 92 | ++ [ 93 | { name = "all-tests"; value = all-tests; } 94 | { name = "list-tests"; value = list-tests; } 95 | { name = "list-compilers"; value = library.list-compilers; } 96 | ]) 97 | 98 | -------------------------------------------------------------------------------- /lib/reactor_element.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Tassilo Tanneberger 7 | * Christian Menard 8 | */ 9 | 10 | #include "reactor-cpp/reactor.hh" 11 | 12 | #include "reactor-cpp/action.hh" 13 | #include "reactor-cpp/assert.hh" 14 | #include "reactor-cpp/environment.hh" 15 | #include "reactor-cpp/port.hh" 16 | #include "reactor-cpp/reaction.hh" 17 | #include "reactor-cpp/statistics.hh" 18 | 19 | namespace reactor { 20 | 21 | ReactorElement::ReactorElement(const std::string& name, ReactorElement::Type type, Reactor* container) 22 | : name_(name) 23 | , container_(container) { 24 | reactor_assert(container != nullptr); 25 | this->environment_ = container->environment(); 26 | reactor_assert(this->environment_ != nullptr); 27 | validate(this->environment_->phase() == Phase::Construction || 28 | (type == Type::Action && this->environment_->phase() == Phase::Assembly), 29 | "Reactor elements can only be created during construction phase!"); 30 | // We need a reinterpret_cast here as the derived class is not yet created 31 | // when this constructor is executed. dynamic_cast only works for 32 | // completely constructed objects. Technically, the casts here return 33 | // invalid pointers as the objects they point to do not yet 34 | // exists. However, we are good as long as we only store the pointer and do 35 | // not dereference it before construction is completed. 36 | // It works, but maybe there is some nicer way of doing this... 37 | switch (type) { 38 | case Type::Action: 39 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 40 | container->register_action(reinterpret_cast(this)); 41 | break; 42 | case Type::Input: 43 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 44 | container->register_input(reinterpret_cast(this)); 45 | break; 46 | case Type::Output: 47 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 48 | container->register_output(reinterpret_cast(this)); 49 | break; 50 | case Type::Reaction: 51 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 52 | container->register_reaction(reinterpret_cast(this)); 53 | break; 54 | case Type::Reactor: 55 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 56 | container->register_reactor(reinterpret_cast(this)); 57 | break; 58 | default: 59 | throw std::runtime_error("unexpected type"); 60 | } 61 | 62 | std::stringstream string_stream; 63 | string_stream << container_->fqn() << '.' << name; 64 | fqn_ = string_stream.str(); 65 | } 66 | 67 | ReactorElement::ReactorElement(const std::string& name, ReactorElement::Type type, Environment* environment) 68 | : name_(name) 69 | , fqn_(name) 70 | , environment_(environment) { 71 | reactor_assert(environment != nullptr); 72 | 73 | validate(type == Type::Reactor || type == Type::Action, "Only reactors and actions can be owned by the environment!"); 74 | validate(this->environment_->phase() == Phase::Construction || 75 | (type == Type::Action && this->environment_->phase() == Phase::Assembly), 76 | "Reactor elements can only be created during construction phase!"); 77 | 78 | switch (type) { 79 | case Type::Action: 80 | Statistics::increment_actions(); 81 | break; 82 | case Type::Reactor: 83 | Statistics::increment_reactor_instances(); 84 | break; 85 | default: 86 | break; 87 | } 88 | } 89 | } // namespace reactor 90 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | **Contact:** or 4 | 5 | ## Programming Style 6 | 7 | The project is currently implemented in C++17 and follows primarily the [C++ Core Guidelines](https://github.com/isocpp/CppCoreGuidelines) 8 | Please make sure your submitted code follows the .clang-tidy and .clang-format files. 9 | 10 | ### Testing 11 | There are CI Tests that automatically check your code. If you want to perform the tests manually take a look 12 | at this [guide](https://www.lf-lang.org/docs/developer/regression-tests). 13 | 14 | ## Building and Testing with Nix 15 | 16 | **List Compilers** 17 | ``` 18 | $ nix run .#packages.x86_64-linux.list-compilers 19 | ``` 20 | 21 | **List all available Tests** 22 | ``` 23 | $ nix run .#packages.x86_64-linux.list-tests 24 | ``` 25 | 26 | **List Benchmarks** 27 | ``` 28 | $ nix run .#packages.x86_64-linux.list-benchmarks 29 | ``` 30 | 31 | **Building a special Package** 32 | ``` 33 | $ nix build .#packages.x86_64-linux.ActionDelay-gcc-wrapper-10-3-0 --override-input reactor-cpp github:lf-lang/reactor-cpp/ 34 | ``` 35 | 36 | The important thing to note is that the `--override-input` flag can take literally any source. In this example, it takes the `cpp-core-guidleines` branch 37 | but you maybe also want to use your fork then the argument would look like this `--override-input reactor-cpp github:revol-xut/reactor-cpp`. 38 | 39 | **Building and Running all Packages** 40 | ``` 41 | $ nix run .#packages.x86_64-linux.all-tests 42 | ``` 43 | 44 | This will build and run every tests. 45 | 46 | **Building and Running all Benchmarks** 47 | ``` 48 | $ nix run .#packages.x86_64-linux.all-benchmarks 49 | ``` 50 | 51 | **Local integration testing** 52 | Let's assume you have the following folder structure: 53 | - reactor-cpp/ 54 | - lingua-franca/ 55 | - build/your_lfc_build.tar.gz folder that contains your local build of lfc 56 | 57 | ``` 58 | $ nix run .#packages.x86_64-linux.all-tests --override-input reactor-cpp "./." lingua-franca-src "../lingua-franca/build/your_lfc_build.tar.gz" 59 | ``` 60 | 61 | Building lfc with nix is a work in progress until then you have to sadly build lfc yourself and specify it this way. 62 | 63 | 64 | **Running all Benchmarks and collect results** 65 | 66 | ``` 67 | $ nix build .\#packages.x86_64-linux.make-benchmark 68 | ``` 69 | 70 | This will generate a data/result.csv with all the measurements of all benchmarks. 71 | 72 | **Analysing Benchmarks and Tests for Memory Leaks** 73 | 74 | This will run valgrind on that test and create $out/data/Test-memtest.out which contains the requested debug information. 75 | ``` 76 | nix build .\#packages.x86_64-linux.MLeaks-FullyConnected-gcc-wrapper-10-3-0 77 | ``` 78 | 79 | **Cachegrind** 80 | Analyse your benchmark for cache misses. 81 | ``` 82 | nix build .\#packages.x86_64-linux.cachegrind-SleepingBarber-gcc-wrapper-10-3-0 83 | ``` 84 | 85 | **Callgrind** 86 | Profile and analyze your benchmarks call chain. 87 | ``` 88 | nix build .\#packages.x86_64-linux.callgrind-SleepingBarber-gcc-wrapper-10-3-0 89 | ``` 90 | 91 | 92 | 93 | ### Benchmarking 94 | If your changes are performance-critical it is advised to run the test from [here](https://www.lf-lang.org/docs/developer/running-benchmarks) 95 | 96 | 97 | ## Git 98 | 99 | There are no strict conventions on how your git messages need to be formatted just know they should be informative and summersing on your changes. 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /include/reactor-cpp/time_barrier.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_TIME_BARRIER_HH 10 | #define REACTOR_CPP_TIME_BARRIER_HH 11 | 12 | #include "fwd.hh" 13 | #include "logical_time.hh" 14 | #include "scheduler.hh" 15 | #include "time.hh" 16 | #include 17 | 18 | namespace reactor { 19 | 20 | class PhysicalTimeBarrier { 21 | // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) 22 | inline static std::atomic last_observed_physical_time_{Duration::zero()}; 23 | 24 | public: 25 | static auto try_acquire_tag(const Tag& tag) -> bool { 26 | // First, we compare against the last observed physical time. This variable 27 | // serves as a cache for reading the physical clock. Reading from the physical 28 | // clock can be slow and, thus, this is an optimization that ensures that we 29 | // only read the clock when it is needed. 30 | if (tag.time_point().time_since_epoch() < last_observed_physical_time_.load(std::memory_order_acquire)) { 31 | return true; 32 | } 33 | 34 | auto physical_time = get_physical_time(); 35 | last_observed_physical_time_.store(physical_time.time_since_epoch(), std::memory_order_release); 36 | 37 | return tag.time_point() < physical_time; 38 | } 39 | 40 | static auto acquire_tag(const Tag& tag, std::unique_lock& lock, Scheduler* scheduler, 41 | const std::function& abort_waiting) -> bool { 42 | if (try_acquire_tag(tag)) { 43 | return true; 44 | } 45 | return !scheduler->wait_until(lock, tag.time_point(), abort_waiting); 46 | } 47 | }; 48 | 49 | class LogicalTimeBarrier { 50 | private: 51 | LogicalTime released_time_; 52 | Scheduler* scheduler_; 53 | 54 | /* 55 | * Note that we use the main scheduler lock to protect our `released_time_` 56 | * variable. We cannot use a local mutex here that only protects our local 57 | * state. This is, because we also need to use a lock to wait on a condition 58 | * variable. This lock must be the same lock as the one that is used when 59 | * updating the wait condition. (See "An atomic predicate" in 60 | * https://www.modernescpp.com/index.php/c-core-guidelines-be-aware-of-the-traps-of-condition-variables) 61 | * 62 | * Here we have two conditions in which we want to stop the wait: 63 | * 1. When the tag that we are waiting for was released. 64 | * 2. When the given function `abort_waiting` returns true. In a typical usage 65 | * scenario, this would be the case when a new event was scheduled. 66 | * 67 | * We need to make sure that both conditions are updated while holding the 68 | * lock that we use for waiting on the condition variable. Our only option for 69 | * this is to use the global scheduler lock. 70 | */ 71 | public: 72 | LogicalTimeBarrier(Scheduler* scheduler) 73 | : scheduler_(scheduler) {} 74 | 75 | void release_tag(const LogicalTime& tag) { 76 | auto lock = scheduler_->lock(); 77 | released_time_.advance_to(tag); 78 | } 79 | 80 | // The caller must hold a lock on the scheduler mutex 81 | auto try_acquire_tag(const Tag& tag) { return tag <= released_time_; } 82 | 83 | auto acquire_tag(const Tag& tag, std::unique_lock& lock, 84 | const std::function& abort_waiting) -> bool { 85 | if (try_acquire_tag(tag)) { 86 | return true; 87 | } 88 | scheduler_->wait(lock, [this, &tag, &abort_waiting]() { return try_acquire_tag(tag) || abort_waiting(); }); 89 | return !abort_waiting(); 90 | } 91 | }; 92 | 93 | } // namespace reactor 94 | 95 | #endif // REACTOR_CPP_TIME_BARRIER_HH 96 | -------------------------------------------------------------------------------- /include/reactor-cpp/logging.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_LOGGING_HH 10 | #define REACTOR_CPP_LOGGING_HH 11 | 12 | #include "reactor-cpp/config.hh" 13 | #include "reactor-cpp/time.hh" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace reactor::log { 22 | 23 | template class BaseLogger {}; 24 | 25 | template <> class BaseLogger { 26 | private: 27 | using Lock = std::unique_lock; 28 | 29 | const std::string log_prefix_{}; 30 | 31 | // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) 32 | inline static std::mutex mutex_{}; 33 | 34 | Lock lock_{}; 35 | 36 | public: 37 | explicit BaseLogger(const std::string& log_prefix) 38 | : log_prefix_(log_prefix) 39 | , lock_(mutex_) { 40 | std::cerr << log_prefix; 41 | } 42 | BaseLogger(const BaseLogger&) = delete; 43 | auto operator=(const BaseLogger&) -> BaseLogger& = delete; 44 | BaseLogger(BaseLogger&&) = delete; 45 | auto operator=(BaseLogger&&) -> BaseLogger& = delete; 46 | 47 | template auto operator<<(const T& msg) -> BaseLogger& { 48 | // FIXME 49 | std::cerr << msg; // NOLINT 50 | return *this; 51 | } 52 | 53 | ~BaseLogger() { std::cerr << '\n'; } 54 | }; 55 | 56 | template <> class BaseLogger { 57 | public: 58 | explicit BaseLogger([[maybe_unused]] const std::string& /*unused*/) {} 59 | BaseLogger(const BaseLogger&) = delete; 60 | auto operator=(const BaseLogger&) -> BaseLogger& = delete; 61 | BaseLogger(BaseLogger&&) = delete; 62 | auto operator=(BaseLogger&&) -> BaseLogger& = delete; 63 | 64 | template auto operator<<([[maybe_unused]] const T& /*unused*/) const -> const BaseLogger& { return *this; } 65 | 66 | ~BaseLogger() = default; 67 | }; 68 | 69 | constexpr bool debug_enabled = 4 <= REACTOR_CPP_LOG_LEVEL; 70 | constexpr bool info_enabled = 3 <= REACTOR_CPP_LOG_LEVEL; 71 | constexpr bool warn_enabled = 2 <= REACTOR_CPP_LOG_LEVEL; 72 | constexpr bool error_enabled = 1 <= REACTOR_CPP_LOG_LEVEL; 73 | 74 | struct Debug : BaseLogger { 75 | Debug() 76 | : BaseLogger("[DEBUG] ") {} 77 | }; 78 | struct Info : BaseLogger { 79 | Info() 80 | : BaseLogger("[INFO] ") {} 81 | }; 82 | struct Warn : BaseLogger { 83 | Warn() 84 | : BaseLogger("[WARN] ") {} 85 | }; 86 | struct Error : BaseLogger { 87 | Error() 88 | : BaseLogger("[ERROR] ") {} 89 | }; 90 | 91 | class NamedLogger { 92 | private: 93 | std::string debug_prefix_{}; 94 | std::string info_prefix_{}; 95 | std::string warn_prefix_{}; 96 | std::string error_prefix_{}; 97 | 98 | public: 99 | NamedLogger(const std::string& name) 100 | : debug_prefix_("[DEBUG] (" + name + ") ") 101 | , info_prefix_("[INFO] (" + name + ") ") 102 | , warn_prefix_("[WARN] (" + name + ") ") 103 | , error_prefix_("[ERROR] (" + name + ") ") {} 104 | 105 | [[nodiscard]] auto debug() const -> BaseLogger { return BaseLogger(debug_prefix_); } 106 | [[nodiscard]] auto info() const -> BaseLogger { return BaseLogger(info_prefix_); } 107 | [[nodiscard]] auto warn() const -> BaseLogger { return BaseLogger(warn_prefix_); } 108 | [[nodiscard]] auto error() const -> BaseLogger { return BaseLogger(error_prefix_); } 109 | }; 110 | 111 | } // namespace reactor::log 112 | 113 | #endif // REACTOR_CPP_LOGGING_HH 114 | -------------------------------------------------------------------------------- /include/reactor-cpp/trace.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | // NOTE: This file is named trace.hpp (with the hpp file extension) on purpose. 10 | // This is to exclude the file from all clang-tidy checks. This is more a hacky 11 | // workaround than an actual solution, but apparently clang-tidy does not allow 12 | // something nicer at the moment. 13 | 14 | #include 15 | 16 | #include "reactor-cpp/config.hh" 17 | #include "reactor-cpp/logical_time.hh" 18 | 19 | // Only enable tracing if REACTOR_CPP_TRACE is set. 20 | // Also, disable tracing if clang analytics are run as it produces many errors. 21 | #if defined(REACTOR_CPP_TRACE) && !defined(__clang_analyzer__) 22 | 23 | #undef LTTNG_UST_COMPAT_API_VERSION 24 | #define LTTNG_UST_COMPAT_API_VERSION 0 25 | 26 | #undef TRACEPOINT_PROVIDER 27 | #define TRACEPOINT_PROVIDER reactor_cpp 28 | 29 | #undef TRACEPOINT_INCLUDE 30 | #define TRACEPOINT_INCLUDE "reactor-cpp/trace.hh" 31 | 32 | // LTTng requires this header to be included multiple times. 33 | #ifndef REACTOR_CPP_TRACE_HH 34 | namespace reactor { 35 | constexpr bool tracing_enabled = true; 36 | } 37 | #endif 38 | 39 | #if !defined(REACTOR_CPP_TRACE_HH) || defined(TRACEPOINT_HEADER_MULTI_READ) 40 | #define REACTOR_CPP_TRACE_HH 41 | 42 | #include 43 | 44 | TRACEPOINT_EVENT( 45 | reactor_cpp, reaction_execution_starts, 46 | TP_ARGS(int, worker_id_arg, const std::string&, reaction_name_arg, const reactor::LogicalTime&, tag_arg), 47 | TP_FIELDS(ctf_string(reaction_name, reaction_name_arg.c_str()) ctf_integer(int, worker_id, worker_id_arg) 48 | ctf_integer(unsigned long long, timestamp_ns, tag_arg.time_point().time_since_epoch().count()) 49 | ctf_integer(unsigned long, timestamp_microstep, tag_arg.micro_step()))) 50 | 51 | TRACEPOINT_EVENT( 52 | reactor_cpp, reaction_execution_finishes, 53 | TP_ARGS(int, worker_id_arg, const std::string&, reaction_name_arg, const reactor::LogicalTime&, tag_arg), 54 | TP_FIELDS(ctf_string(reaction_name, reaction_name_arg.c_str()) ctf_integer(int, worker_id, worker_id_arg) 55 | ctf_integer(unsigned long long, timestamp_ns, tag_arg.time_point().time_since_epoch().count()) 56 | ctf_integer(unsigned long, timestamp_microstep, tag_arg.micro_step()))) 57 | 58 | TRACEPOINT_EVENT( 59 | reactor_cpp, schedule_action, 60 | TP_ARGS(const std::string&, reactor_name_arg, const std::string&, action_name_arg, const reactor::Tag&, tag_arg), 61 | TP_FIELDS(ctf_string(reactor_name, reactor_name_arg.c_str()) ctf_string(action_name, action_name_arg.c_str()) 62 | ctf_integer(unsigned long long, timestamp_ns, tag_arg.time_point().time_since_epoch().count()) 63 | ctf_integer(unsigned long, timestamp_microstep, tag_arg.micro_step()))) 64 | 65 | TRACEPOINT_EVENT( 66 | reactor_cpp, trigger_reaction, 67 | TP_ARGS(const std::string&, reactor_name_arg, const std::string&, reaction_name_arg, const reactor::LogicalTime&, 68 | tag_arg), 69 | TP_FIELDS(ctf_string(reactor_name, reactor_name_arg.c_str()) ctf_string(reaction_name, reaction_name_arg.c_str()) 70 | ctf_integer(unsigned long long, timestamp_ns, tag_arg.time_point().time_since_epoch().count()) 71 | ctf_integer(unsigned long, timestamp_microstep, tag_arg.micro_step()))) 72 | 73 | #endif /* REACTOR_CPP_TRACE_HH */ 74 | 75 | #include 76 | 77 | #else 78 | 79 | #ifndef REACTOR_CPP_TRACE_HH 80 | // NOLINTNEXTLINE 81 | #define REACTOR_CPP_TRACE_HH 82 | 83 | namespace reactor { 84 | constexpr bool tracing_enabled = false; 85 | } // namespace reactor 86 | 87 | // empty definition in case we compile without tracing 88 | #define tracepoint(...) 89 | 90 | #endif // REACTOR_CPP_TRACE_HH 91 | 92 | #endif // REACTOR_CPP_TRACE 93 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'reactor-cpp' 21 | copyright = '2020, Christian Menard' 22 | author = 'Christian Menard' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | "breathe", 32 | "exhale", 33 | "sphinx_rtd_theme", 34 | "sphinx.ext.todo", 35 | "sphinx.ext.extlinks", 36 | ] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # List of patterns, relative to source directory, that match files and 42 | # directories to ignore when looking for source files. 43 | # This pattern also affects html_static_path and html_extra_path. 44 | exclude_patterns = [] 45 | 46 | 47 | # -- Options for HTML output ------------------------------------------------- 48 | 49 | # The theme to use for HTML and HTML Help pages. See the documentation for 50 | # a list of builtin themes. 51 | # 52 | html_theme = 'sphinx_rtd_theme' 53 | 54 | # Add any paths that contain custom static files (such as style sheets) here, 55 | # relative to this directory. They are copied after the builtin static files, 56 | # so a file named "default.css" will overwrite the builtin "default.css". 57 | html_static_path = ['_static'] 58 | 59 | # Breathe Configuration 60 | breathe_projects = {"reactor-cpp": "../doxygen/xml"} 61 | breathe_default_project = "reactor-cpp" 62 | 63 | 64 | def specificationsForKind(kind): 65 | ''' 66 | For a given input ``kind``, return the list of reStructuredText 67 | specifications for the associated Breathe directive. 68 | ''' 69 | # Change the defaults for .. doxygenclass:: and .. doxygenstruct:: 70 | if kind == "class" or kind == "struct": 71 | return [ 72 | ":members:", 73 | ":protected-members:", 74 | ":private-members:", 75 | ":undoc-members:" 76 | ] 77 | # An empty list signals to Exhale to use the defaults 78 | else: 79 | return [] 80 | 81 | 82 | # Exhale Configuration 83 | from exhale import utils 84 | exhale_args = { 85 | # These arguments are required 86 | "containmentFolder": "./api", 87 | "rootFileName": "library_root.rst", 88 | "rootFileTitle": "Library API", 89 | "doxygenStripFromPath": "..", 90 | # Suggested optional arguments 91 | "createTreeView": True, 92 | # override breathe defaults 93 | "customSpecificationsMapping": utils.makeCustomSpecificationsMapping( 94 | specificationsForKind), 95 | } 96 | 97 | # Tell sphinx what the primary language being documented is. 98 | primary_domain = 'cpp' 99 | 100 | # Tell sphinx what the pygments highlight language should be. 101 | highlight_language = 'cpp' 102 | 103 | # configure theme 104 | html_theme_options = { 105 | # Toc options 106 | 'collapse_navigation': False, 107 | 'sticky_navigation': True, 108 | 'navigation_depth': 4, 109 | 'includehidden': True, 110 | 'titles_only': False 111 | } 112 | 113 | # show todo notes 114 | todo_include_todos = True 115 | 116 | # configure external links 117 | extlinks = { 118 | 'std-memory': ('https://en.cppreference.com/w/cpp/memory/%s', 'std::'), 119 | } 120 | -------------------------------------------------------------------------------- /examples/ports/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace reactor; 6 | using namespace std::chrono_literals; 7 | 8 | class Trigger : public Reactor { 9 | private: 10 | Timer timer; 11 | Reaction r_timer{"r_timer", 1, this, [this]() { on_timer(); }}; 12 | 13 | public: 14 | Trigger(const std::string& name, Environment* env, Duration period) 15 | : Reactor(name, env) 16 | , timer{"timer", this, period, Duration::zero()} {} 17 | 18 | Output trigger{"trigger", this}; // NOLINT 19 | 20 | void assemble() override { 21 | r_timer.declare_trigger(&timer); 22 | r_timer.declare_antidependency(&trigger); 23 | } 24 | 25 | void on_timer() { trigger.set(); } 26 | }; 27 | 28 | class Counter : public Reactor { 29 | private: 30 | int value_{0}; 31 | Reaction r_trigger{"r_trigger", 1, this, [this]() { on_trigger(); }}; 32 | 33 | public: 34 | Counter(const std::string& name, Environment* env) 35 | : Reactor(name, env) {} 36 | 37 | Input trigger{"trigger", this}; // NOLINT 38 | Output count{"count", this}; // NOLINT 39 | 40 | void assemble() override { 41 | std::cout << "assemble Counter\n"; 42 | r_trigger.declare_trigger(&trigger); 43 | r_trigger.declare_antidependency(&count); 44 | } 45 | 46 | void on_trigger() { 47 | value_ += 1; 48 | count.set(value_); 49 | } 50 | }; 51 | 52 | class Printer : public Reactor { 53 | private: 54 | Reaction r_value{"r_value", 1, this, [this]() { on_value(); }}; 55 | 56 | public: 57 | Input value{"value", this}; // NOLINT 58 | Input forward{"forward", this}; // NOLINT 59 | Printer(const std::string& name, Environment* env) 60 | : Reactor(name, env) {} 61 | 62 | void assemble() override { 63 | std::cout << "assemble Printer\n"; 64 | r_value.declare_trigger(&value); 65 | } 66 | 67 | void on_value() { std::cout << this->name() << ": " << *value.get() << '\n'; } 68 | }; 69 | 70 | class Adder : public Reactor { 71 | private: 72 | Reaction r_add{"r_add", 1, this, [this]() { add(); }}; 73 | 74 | public: 75 | Input i1{"i1", this}; // NOLINT 76 | Input i2{"i1", this}; // NOLINT 77 | Output sum{"sum", this}; // NOLINT 78 | 79 | Adder(const std::string& name, Environment* env) 80 | : Reactor(name, env) {} 81 | 82 | void assemble() override { 83 | r_add.declare_trigger(&i1); 84 | r_add.declare_trigger(&i2); 85 | r_add.declare_antidependency(&sum); 86 | } 87 | 88 | void add() { 89 | if (i1.is_present() && i2.is_present()) { 90 | sum.set(*i1.get() + *i2.get()); 91 | std::cout << "setting sum\n"; 92 | } 93 | } 94 | }; 95 | 96 | auto main() -> int { 97 | Environment env{4}; 98 | 99 | Trigger trigger1{"t1", &env, 1s}; 100 | Counter counter1{"c1", &env}; 101 | Printer printer1{"p1", &env}; 102 | 103 | env.draw_connection(trigger1.trigger, counter1.trigger, ConnectionProperties{}); 104 | env.draw_connection(counter1.count, printer1.value, ConnectionProperties{}); 105 | 106 | Trigger trigger2{"t2", &env, 2s}; 107 | Counter counter2{"c2", &env}; 108 | Printer printer2{"p2", &env}; 109 | 110 | // trigger2.trigger.set_inward_binding(&counter2.trigger); 111 | // counter2.count.set_inward_binding(&printer2.value); 112 | env.draw_connection(trigger2.trigger, counter2.trigger, ConnectionProperties{}); 113 | env.draw_connection(counter2.count, printer2.value, ConnectionProperties{}); 114 | 115 | Adder add{"add", &env}; 116 | Printer p_add{"p_add", &env}; 117 | 118 | env.draw_connection(counter1.count, add.i1, ConnectionProperties{}); 119 | env.draw_connection(counter2.count, add.i2, ConnectionProperties{}); 120 | env.draw_connection(add.sum, p_add.forward, ConnectionProperties{ConnectionType::Delayed, 10s, nullptr}); 121 | env.draw_connection(p_add.forward, p_add.value, ConnectionProperties{ConnectionType::Delayed, 5s, nullptr}); 122 | 123 | std::cout << "assemble\n"; 124 | env.assemble(); 125 | 126 | std::cout << "optimize\n"; 127 | auto thread = env.startup(); 128 | thread.join(); 129 | 130 | return 0; 131 | } 132 | -------------------------------------------------------------------------------- /lib/action.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #include "reactor-cpp/action.hh" 10 | 11 | #include "reactor-cpp/assert.hh" 12 | #include "reactor-cpp/environment.hh" 13 | #include "reactor-cpp/reaction.hh" 14 | #include "reactor-cpp/time.hh" 15 | 16 | namespace reactor { 17 | 18 | BaseAction::BaseAction(const std::string& name, Environment* environment, bool logical, Duration min_delay) 19 | : ReactorElement(name, ReactorElement::Type::Action, environment) 20 | , min_delay_(min_delay) 21 | , logical_(logical) { 22 | environment->register_input_action(this); 23 | } 24 | 25 | void BaseAction::register_trigger(Reaction* reaction) { 26 | reactor_assert(reaction != nullptr); 27 | reactor_assert(this->environment() == reaction->environment()); 28 | assert_phase(this, Phase::Assembly); 29 | validate(this->container() == reaction->container(), 30 | "Action triggers must belong to the same reactor as the triggered " 31 | "reaction"); 32 | [[maybe_unused]] bool result = triggers_.insert(reaction).second; 33 | reactor_assert(result); 34 | } 35 | 36 | void BaseAction::register_scheduler(Reaction* reaction) { 37 | reactor_assert(reaction != nullptr); 38 | reactor_assert(this->environment() == reaction->environment()); 39 | assert_phase(this, Phase::Assembly); 40 | // the reaction must belong to the same reactor as this action 41 | validate(this->container() == reaction->container(), "Scheduable actions must belong to the same reactor as the " 42 | "triggered reaction"); 43 | [[maybe_unused]] bool result = schedulers_.insert(reaction).second; 44 | reactor_assert(result); 45 | } 46 | 47 | void Timer::startup() { 48 | // abort if the offset is the maximum duration 49 | if (offset_ == Duration::max()) { 50 | return; 51 | } 52 | 53 | const Tag& start_tag = environment()->start_tag(); 54 | if (offset_ != Duration::zero()) { 55 | environment()->scheduler()->schedule_sync(this, start_tag.delay(offset_)); 56 | } else { 57 | environment()->scheduler()->schedule_sync(this, start_tag); 58 | } 59 | } 60 | 61 | void Timer::cleanup() noexcept { 62 | BaseAction::cleanup(); 63 | // schedule the timer again 64 | if (period_ != Duration::zero()) { 65 | Tag now = Tag::from_logical_time(environment()->logical_time()); 66 | Tag next = now.delay(period_); 67 | environment()->scheduler()->schedule_sync(this, next); 68 | } 69 | } 70 | 71 | ShutdownTrigger::ShutdownTrigger(const std::string& name, Reactor* container) 72 | : Timer(name, container, Duration::zero(), container->environment()->timeout()) {} 73 | 74 | void ShutdownTrigger::setup() noexcept { BaseAction::setup(); } 75 | 76 | void ShutdownTrigger::shutdown() { 77 | Tag tag = Tag::from_logical_time(environment()->logical_time()).delay(); 78 | environment()->scheduler()->schedule_sync(this, tag); 79 | } 80 | 81 | auto Action::schedule_at(const Tag& tag) -> bool { 82 | auto* scheduler = environment()->scheduler(); 83 | if (is_logical()) { 84 | if (tag <= scheduler->logical_time()) { 85 | return false; 86 | } 87 | scheduler->schedule_sync(this, tag); 88 | } else { 89 | // We must call schedule_async while holding the mutex, because otherwise 90 | // the scheduler could already start processing the event that we schedule 91 | // and call setup() on this action before we insert the value in events_. 92 | // Holding both the local mutex mutex_events_ and the scheduler mutex (in 93 | // schedule async) should not lead to a deadlock as the scheduler will 94 | // only hold one of the two mutexes at once. 95 | return scheduler->schedule_async_at(this, tag); 96 | } 97 | return true; 98 | } 99 | 100 | auto BaseAction::acquire_tag(const Tag& tag, std::unique_lock& lock, 101 | const std::function& abort_waiting) -> bool { 102 | reactor_assert(!logical_); 103 | reactor_assert(lock.owns_lock()); 104 | return PhysicalTimeBarrier::acquire_tag(tag, lock, environment()->scheduler(), abort_waiting); 105 | } 106 | 107 | } // namespace reactor 108 | -------------------------------------------------------------------------------- /include/reactor-cpp/statistics.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_STATISTICS_HH 10 | #define REACTOR_CPP_STATISTICS_HH 11 | 12 | #include 13 | 14 | #include "reactor-cpp/config.hh" 15 | #include "reactor-cpp/logging.hh" 16 | 17 | namespace reactor { 18 | 19 | class Statistics { 20 | private: 21 | #ifdef REACTOR_CPP_PRINT_STATISTICS 22 | constexpr static bool enabled_{true}; 23 | #else 24 | constexpr static bool enabled_{false}; 25 | #endif 26 | // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) 27 | inline static std::atomic_size_t reactor_instances_{0}; 28 | inline static std::atomic_size_t connections_{0}; 29 | inline static std::atomic_size_t reactions_{0}; 30 | inline static std::atomic_size_t actions_{0}; 31 | inline static std::atomic_size_t ports_{0}; 32 | inline static std::atomic_size_t processed_events_{0}; 33 | inline static std::atomic_size_t processed_reactions_{0}; 34 | inline static std::atomic_size_t triggered_actions_{0}; 35 | inline static std::atomic_size_t set_ports_{0}; 36 | inline static std::atomic_size_t scheduled_actions_{0}; 37 | // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) 38 | 39 | static void increment(std::atomic_size_t& counter) { 40 | if constexpr (enabled_) { 41 | counter.fetch_add(1, std::memory_order_release); 42 | } 43 | } 44 | 45 | public: 46 | static void increment_reactor_instances() { increment(reactor_instances_); } 47 | static void increment_connections() { increment(connections_); } 48 | static void increment_reactions() { increment(reactions_); } 49 | static void increment_actions() { increment(actions_); } 50 | static void increment_ports() { increment(ports_); } 51 | static void increment_processed_events() { increment(processed_events_); } 52 | static void increment_processed_reactions() { increment(processed_reactions_); } 53 | static void increment_triggered_actions() { increment(triggered_actions_); } 54 | static void increment_set_ports() { increment(set_ports_); } 55 | static void increment_scheduled_actions() { increment(scheduled_actions_); } 56 | 57 | static auto reactor_instances() { return reactor_instances_.load(std::memory_order_acquire); } 58 | static auto connections() { return connections_.load(std::memory_order_acquire); } 59 | static auto reactions() { return reactions_.load(std::memory_order_acquire); } 60 | static auto actions() { return actions_.load(std::memory_order_acquire); } 61 | static auto ports() { return ports_.load(std::memory_order_acquire); } 62 | static auto processed_events() { return processed_events_.load(std::memory_order_acquire); } 63 | static auto processed_reactions() { return processed_reactions_.load(std::memory_order_acquire); } 64 | static auto triggered_actions() { return triggered_actions_.load(std::memory_order_acquire); } 65 | static auto set_ports() { return set_ports_.load(std::memory_order_acquire); } 66 | static auto scheduled_actions() { return scheduled_actions_.load(std::memory_order_acquire); } 67 | 68 | static void print() { 69 | if constexpr (enabled_) { 70 | reactor::log::Info() << "-----------------------------------------------------------"; 71 | reactor::log::Info() << "Program statistics:"; 72 | reactor::log::Info() << " - number of reactors: " << reactor_instances(); 73 | reactor::log::Info() << " - number of connections: " << connections(); 74 | reactor::log::Info() << " - number of reactions " << reactions(); 75 | reactor::log::Info() << " - number of actions: " << actions(); 76 | reactor::log::Info() << " - number of ports: " << ports(); 77 | reactor::log::Info() << "Execution statistics:"; 78 | reactor::log::Info() << " - processed events: " << processed_events(); 79 | reactor::log::Info() << " - triggered actions: " << triggered_actions(); 80 | reactor::log::Info() << " - processed reactions: " << processed_reactions(); 81 | reactor::log::Info() << " - set ports: " << set_ports(); 82 | reactor::log::Info() << " - scheduled actions: " << scheduled_actions(); 83 | reactor::log::Info() << "-----------------------------------------------------------"; 84 | } 85 | } 86 | }; 87 | 88 | } // namespace reactor 89 | 90 | #endif // REACTOR_CPP_STATISTICS_HH 91 | -------------------------------------------------------------------------------- /include/reactor-cpp/logical_time.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_LOGICAL_TIME_HH 10 | #define REACTOR_CPP_LOGICAL_TIME_HH 11 | 12 | #include "time.hh" 13 | 14 | namespace reactor { 15 | using mstep_t = unsigned long; // at least 32 bit 16 | class LogicalTime; 17 | 18 | class Tag { 19 | private: 20 | TimePoint time_point_{}; 21 | mstep_t micro_step_{0}; 22 | 23 | Tag(const TimePoint& time_point, const mstep_t& micro_step) 24 | : time_point_{time_point} 25 | , micro_step_{micro_step} {} 26 | 27 | public: 28 | Tag() = default; 29 | Tag(Tag&&) = default; 30 | Tag(const Tag&) = default; 31 | auto operator=(const Tag&) -> Tag& = default; 32 | auto operator=(Tag&&) -> Tag& = default; 33 | ~Tag() = default; 34 | 35 | [[nodiscard]] auto time_point() const noexcept -> const TimePoint& { return time_point_; } 36 | [[nodiscard]] auto micro_step() const noexcept -> const mstep_t& { return micro_step_; } 37 | 38 | [[nodiscard]] static auto from_physical_time(TimePoint time_point) noexcept -> Tag; 39 | [[nodiscard]] static auto from_logical_time(const LogicalTime& logical_time) noexcept -> Tag; 40 | 41 | [[nodiscard]] static auto max_for_timepoint(TimePoint time_point) noexcept -> Tag; 42 | 43 | [[nodiscard]] auto delay(Duration offset = Duration::zero()) const noexcept -> Tag; 44 | [[nodiscard]] auto subtract(Duration offset = Duration::zero()) const noexcept -> Tag; 45 | [[nodiscard]] auto decrement() const noexcept -> Tag; 46 | 47 | [[nodiscard]] static auto max() noexcept -> Tag { return {TimePoint::max(), std::numeric_limits::max()}; } 48 | }; 49 | 50 | // define all the comparison operators 51 | auto operator==(const Tag& lhs, const Tag& rhs) noexcept -> bool; 52 | auto inline operator!=(const Tag& lhs, const Tag& rhs) noexcept -> bool { return !(lhs == rhs); } 53 | auto operator<(const Tag& lhs, const Tag& rhs) noexcept -> bool; 54 | auto inline operator>(const Tag& lhs, const Tag& rhs) noexcept -> bool { return rhs < lhs; } 55 | auto inline operator<=(const Tag& lhs, const Tag& rhs) noexcept -> bool { return !(lhs > rhs); } 56 | auto inline operator>=(const Tag& lhs, const Tag& rhs) noexcept -> bool { return !(lhs < rhs); } 57 | 58 | class LogicalTime { 59 | private: 60 | TimePoint time_point_{}; 61 | mstep_t micro_step_{0}; 62 | 63 | public: 64 | void advance_to(const Tag& tag); 65 | void advance_to(const LogicalTime& time); 66 | 67 | [[nodiscard]] auto time_point() const noexcept -> TimePoint { return time_point_; } 68 | [[nodiscard]] auto micro_step() const noexcept -> mstep_t { return micro_step_; } 69 | }; 70 | 71 | // c++20 starship operator will save us this mess 72 | auto operator==(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool; 73 | auto inline operator!=(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { 74 | return !(logical_time == tag); 75 | } 76 | auto operator<(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool; 77 | auto operator>(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool; 78 | auto inline operator<=(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { 79 | return !(logical_time > tag); 80 | } 81 | auto inline operator>=(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { 82 | return !(logical_time < tag); 83 | } 84 | auto inline operator==(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { return logical_time == tag; } 85 | auto inline operator!=(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { 86 | return !(logical_time == tag); 87 | } 88 | auto inline operator<(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { return logical_time > tag; } 89 | auto inline operator>(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { return logical_time < tag; } 90 | auto inline operator<=(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { 91 | return !(tag > logical_time); 92 | } 93 | auto inline operator>=(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { 94 | return !(tag < logical_time); 95 | } 96 | 97 | auto operator<<(std::ostream& os, const Tag& tag) -> std::ostream&; 98 | auto operator<<(std::ostream& os, const LogicalTime& tag) -> std::ostream&; 99 | 100 | } // namespace reactor 101 | 102 | #endif // REACTOR_CPP_LOGICAL_TIME_HH 103 | -------------------------------------------------------------------------------- /include/reactor-cpp/impl/port_impl.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_IMPL_PORT_IMPL_HH 10 | #define REACTOR_CPP_IMPL_PORT_IMPL_HH 11 | 12 | #include "reactor-cpp/assert.hh" 13 | #include "reactor-cpp/environment.hh" 14 | #include "reactor-cpp/port.hh" 15 | #include "reactor-cpp/reactor.hh" 16 | 17 | namespace reactor { 18 | 19 | // connection fwd 20 | 21 | template class Connection; 22 | template class DelayedConnection; 23 | template class PhysicalConnection; 24 | template class EnclaveConnection; 25 | template class DelayedEnclaveConnection; 26 | template class PhysicalEnclaveConnection; 27 | 28 | template [[maybe_unused]] auto Port::typed_outward_bindings() const noexcept -> const std::set*>& { 29 | // HACK this cast is ugly but should be safe as long as we only allow to 30 | // bind with Port*. The alternative would be to copy the entire set and 31 | // cast each element individually, which is also ugly... 32 | // TODO USE C++20 std::bit_cast 33 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 34 | return reinterpret_cast*>&>(outward_bindings()); 35 | } 36 | 37 | template auto Port::typed_inward_binding() const noexcept -> Port* { 38 | // we can use a static cast here since we know that this port is always 39 | // connected with another Port. 40 | return static_cast*>(inward_binding()); 41 | } 42 | 43 | template void Port::set(const ImmutableValuePtr& value_ptr) { 44 | reactor::validate(!has_inward_binding(), "set() may only be called on ports that do not have an inward " 45 | "binding!"); 46 | reactor::validate(value_ptr != nullptr, "Ports may not be set to nullptr!"); 47 | 48 | auto scheduler = environment()->scheduler(); 49 | this->value_ptr_ = std::move(value_ptr); 50 | scheduler->set_port(this); 51 | this->present_ = true; 52 | } 53 | 54 | template auto Port::get() const noexcept -> const ImmutableValuePtr& { 55 | if (has_inward_binding()) { 56 | return typed_inward_binding()->get(); 57 | } 58 | return value_ptr_; 59 | } 60 | template 61 | void Port::instantiate_connection_to(const ConnectionProperties& properties, 62 | const std::vector& downstream) { 63 | std::unique_ptr> connection = nullptr; 64 | if (downstream.empty()) { 65 | return; 66 | } 67 | 68 | // normal connections should be handled by environment 69 | reactor_assert(properties.type_ != ConnectionType::Normal); 70 | 71 | Environment* enclave = downstream[0]->environment(); 72 | auto index = this->container()->number_of_connections(); 73 | 74 | if (properties.type_ == ConnectionType::Delayed) { 75 | connection = std::make_unique>(this->name() + "_delayed_connection_" + std::to_string(index), 76 | this->container(), properties.delay_); 77 | } 78 | if (properties.type_ == ConnectionType::Physical) { 79 | connection = std::make_unique>(this->name() + "_physical_connection_" + std::to_string(index), 80 | this->container(), properties.delay_); 81 | } 82 | if (properties.type_ == ConnectionType::Enclaved) { 83 | connection = 84 | std::make_unique>(this->name() + "_enclave_connection_" + std::to_string(index), enclave); 85 | } 86 | if (properties.type_ == ConnectionType::DelayedEnclaved) { 87 | connection = std::make_unique>( 88 | this->name() + "_delayed_enclave_connection_" + std::to_string(index), enclave, properties.delay_); 89 | } 90 | if (properties.type_ == ConnectionType::PhysicalEnclaved) { 91 | connection = std::make_unique>( 92 | this->name() + "_physical_enclave_connection_" + std::to_string(index), enclave); 93 | } 94 | 95 | // if the connection here is null we have a vaulty enum value 96 | reactor_assert(connection != nullptr); 97 | connection->bind_downstream_ports(downstream); 98 | connection->bind_upstream_port(this); 99 | this->register_set_callback(connection->upstream_set_callback()); 100 | this->container()->register_connection(std::move(connection)); 101 | } 102 | 103 | } // namespace reactor 104 | 105 | #endif // REACTOR_CPP_IMPL_PORT_IMPL_HH 106 | -------------------------------------------------------------------------------- /include/reactor-cpp/multiport.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Tassilo Tanneberger 7 | */ 8 | 9 | #ifndef REACTOR_CPP_MULTIPORT_HH 10 | #define REACTOR_CPP_MULTIPORT_HH 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "assert.hh" 20 | #include "fwd.hh" 21 | 22 | namespace reactor { 23 | 24 | class BaseMultiport { // NOLINT cppcoreguidelines-special-member-functions,-warnings-as-errors 25 | private: 26 | std::atomic size_{0}; 27 | std::vector present_ports_{}; 28 | 29 | // record that the port with the given index has been set 30 | void set_present(std::size_t index); 31 | 32 | // reset the list of set port indexes 33 | void reset() noexcept { size_.store(0, std::memory_order_relaxed); } 34 | 35 | [[nodiscard]] auto get_set_callback(std::size_t index) noexcept -> PortCallback; 36 | const PortCallback clean_callback_{[this]([[maybe_unused]] const BasePort& port) { this->reset(); }}; 37 | 38 | [[nodiscard]] auto get_clean_callback() const noexcept -> const PortCallback& { return clean_callback_; } 39 | 40 | protected: 41 | [[nodiscard]] auto present_ports() const -> const auto& { return present_ports_; } 42 | [[nodiscard]] auto present_ports_size() const -> auto { return size_.load(); } 43 | 44 | void present_ports_reserve(size_t n) { present_ports_.reserve(n); } 45 | 46 | void register_port(BasePort& port, size_t idx); 47 | 48 | public: 49 | BaseMultiport() = default; 50 | ~BaseMultiport() = default; 51 | }; 52 | 53 | template > 54 | class Multiport : public BaseMultiport { // NOLINT cppcoreguidelines-special-member-functions 55 | protected: 56 | std::vector ports_{}; // NOLINT cppcoreguidelines-non-private-member-variables-in-classes 57 | 58 | public: 59 | using value_type = typename A::value_type; 60 | using size_type = typename A::size_type; 61 | 62 | using iterator = typename std::vector::iterator; 63 | using const_iterator = typename std::vector::const_iterator; 64 | 65 | Multiport() noexcept = default; 66 | ~Multiport() noexcept = default; 67 | 68 | auto operator==(const Multiport& other) const noexcept -> bool { 69 | return std::equal(std::begin(ports_), std::end(ports_), std::begin(other.ports_), std::end(other.ports_)); 70 | } 71 | auto operator!=(const Multiport& other) const noexcept -> bool { return !(*this == other); }; 72 | auto operator[](std::size_t index) noexcept -> T& { return ports_[index]; } 73 | auto operator[](std::size_t index) const noexcept -> const T& { return ports_[index]; } 74 | 75 | auto begin() noexcept -> iterator { return ports_.begin(); }; 76 | auto begin() const noexcept -> const_iterator { return ports_.begin(); }; 77 | auto cbegin() const noexcept -> const_iterator { return ports_.cbegin(); }; 78 | auto end() noexcept -> iterator { return ports_.end(); }; 79 | auto end() const noexcept -> const_iterator { return ports_.end(); }; 80 | auto cend() const noexcept -> const_iterator { return ports_.cend(); }; 81 | 82 | auto size() const noexcept -> size_type { return ports_.size(); }; 83 | [[nodiscard]] auto empty() const noexcept -> bool { return ports_.empty(); }; 84 | 85 | [[nodiscard]] auto present_indices_unsorted() const noexcept -> std::vector { 86 | return std::vector(std::begin(present_ports()), std::begin(present_ports()) + present_ports_size()); 87 | } 88 | 89 | [[nodiscard]] auto present_indices_sorted() const noexcept -> std::vector { 90 | std::vector indices(std::begin(present_ports()), std::begin(present_ports()) + present_ports_size()); 91 | std::sort(std::begin(indices), std::end(indices)); 92 | return indices; 93 | } 94 | }; 95 | 96 | template > class ModifableMultiport : public Multiport { 97 | public: 98 | void reserve(std::size_t size) noexcept { 99 | this->ports_.reserve(size); 100 | this->present_ports_reserve(size); 101 | } 102 | 103 | void push_back(const T& elem) noexcept { 104 | this->ports_.push_back(elem); 105 | this->register_port(this->ports_.back(), this->ports_.size() - 1); 106 | } 107 | 108 | template void emplace_back(Args&&... args) noexcept { 109 | this->ports_.emplace_back(std::forward(args)...); 110 | this->register_port(this->ports_.back(), this->ports_.size() - 1); 111 | } 112 | }; 113 | } // namespace reactor 114 | 115 | #endif // REACTOR_CPP_MULTIPORT_HH 116 | -------------------------------------------------------------------------------- /include/reactor-cpp/impl/action_impl.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_IMPL_ACTION_IMPL_HH 10 | #define REACTOR_CPP_IMPL_ACTION_IMPL_HH 11 | 12 | #include "reactor-cpp/assert.hh" 13 | #include "reactor-cpp/environment.hh" 14 | #include "reactor-cpp/reactor.hh" 15 | 16 | #include 17 | #include 18 | 19 | namespace reactor { 20 | 21 | template template void Action::schedule(const ImmutableValuePtr& value_ptr, Dur delay) { 22 | Duration time_delay = std::chrono::duration_cast(delay); 23 | reactor::validate(time_delay >= Duration::zero(), "Schedule cannot be called with a negative delay!"); 24 | reactor::validate(value_ptr != nullptr, "Actions may not be scheduled with a nullptr value!"); 25 | 26 | auto* scheduler = environment()->scheduler(); 27 | if (is_logical()) { 28 | time_delay += this->min_delay(); 29 | auto tag = Tag::from_logical_time(scheduler->logical_time()).delay(time_delay); 30 | 31 | scheduler->schedule_sync(this, tag); 32 | events_[tag] = value_ptr; 33 | } else { 34 | std::lock_guard lock{mutex_events_}; 35 | // We must call schedule_async while holding the mutex, because otherwise 36 | // the scheduler could already start processing the event that we schedule 37 | // and call setup() on this action before we insert the value in events_. 38 | // Holding both the local mutex mutex_events_ and the scheduler mutex (in 39 | // schedule async) should not lead to a deadlock as the scheduler will 40 | // only hold one of the two mutexes at once. 41 | auto tag = scheduler->schedule_async(this, time_delay); 42 | events_[tag] = value_ptr; 43 | } 44 | } 45 | 46 | template void Action::schedule(Dur delay) { 47 | Duration time_delay = std::chrono::duration_cast(delay); 48 | reactor::validate(time_delay >= Duration::zero(), "Schedule cannot be called with a negative delay!"); 49 | auto* scheduler = environment()->scheduler(); 50 | if (is_logical()) { 51 | time_delay += this->min_delay(); 52 | auto tag = Tag::from_logical_time(scheduler->logical_time()).delay(time_delay); 53 | scheduler->schedule_sync(this, tag); 54 | } else { 55 | scheduler->schedule_async(this, time_delay); 56 | } 57 | } 58 | 59 | template auto Action::schedule_at(const ImmutableValuePtr& value_ptr, const Tag& tag) -> bool { 60 | reactor::validate(value_ptr != nullptr, "Actions may not be scheduled with a nullptr value!"); 61 | 62 | auto* scheduler = environment()->scheduler(); 63 | if (is_logical()) { 64 | if (tag <= scheduler->logical_time()) { 65 | return false; 66 | } 67 | 68 | scheduler->schedule_sync(this, tag); 69 | events_[tag] = value_ptr; 70 | } else { 71 | std::lock_guard lock{mutex_events_}; 72 | // We must call schedule_async while holding the mutex, because otherwise 73 | // the scheduler could already start processing the event that we schedule 74 | // and call setup() on this action before we insert the value in events_. 75 | // Holding both the local mutex mutex_events_ and the scheduler mutex (in 76 | // schedule async) should not lead to a deadlock as the scheduler will 77 | // only hold one of the two mutexes at once. 78 | bool result = scheduler->schedule_async_at(this, tag); 79 | if (result) { 80 | events_[tag] = value_ptr; 81 | } 82 | return result; 83 | } 84 | return true; 85 | } 86 | 87 | template void Action::setup() noexcept { 88 | BaseAction::setup(); 89 | if (value_ptr_ == nullptr) { // only do this once, even if the action was triggered multiple times 90 | // lock if this is a physical action 91 | std::unique_lock lock = 92 | is_logical() ? std::unique_lock() : std::unique_lock(mutex_events_); 93 | const auto& node = events_.extract(events_.begin()); 94 | reactor_assert(!node.empty()); 95 | reactor_assert(node.key() == environment()->scheduler()->logical_time()); 96 | value_ptr_ = std::move(node.mapped()); 97 | } 98 | reactor_assert(value_ptr_ != nullptr); 99 | } 100 | 101 | template void Action::cleanup() noexcept { 102 | BaseAction::cleanup(); 103 | value_ptr_ = nullptr; 104 | } 105 | 106 | template 107 | PhysicalAction::PhysicalAction(const std::string& name, reactor::Reactor* container) 108 | : Action(name, container, false, Duration::zero()) { 109 | // all physical actions act as input actions to the program as they can be 110 | // scheduled from external threads 111 | this->environment()->register_input_action(this); 112 | } 113 | 114 | } // namespace reactor 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /include/reactor-cpp/graph.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Tassilo Tanneberger 7 | */ 8 | 9 | #ifndef REACTOR_CPP_GRAPH_HH 10 | #define REACTOR_CPP_GRAPH_HH 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace reactor { 18 | // this graph is special, because to every edge properties are annotated 19 | template class Graph { 20 | private: 21 | std::map>> graph_; 22 | 23 | // custom compare operator this is required if u want special key values in std::map 24 | // this is required for the Graph::get_edges() method 25 | using map_key = std::pair; 26 | struct map_key_compare { 27 | auto operator()(const map_key& left_site, const map_key& right_site) const -> bool { 28 | return left_site.first < right_site.first || 29 | (left_site.second == right_site.second && left_site.second < right_site.second); 30 | } 31 | }; 32 | 33 | public: 34 | Graph() noexcept = default; 35 | Graph(const Graph& graph) noexcept 36 | : graph_(graph.graph_) {} 37 | Graph(const Graph&& graph) noexcept 38 | : graph_(std::move(graph.graph_)) {} 39 | ~Graph() noexcept = default; 40 | 41 | auto operator=(const Graph other) noexcept -> Graph& { 42 | graph_ = other.graph_; 43 | return *this; 44 | } 45 | 46 | auto operator=(Graph&& other) noexcept -> Graph& { 47 | graph_ = std::move(other.graph_); 48 | return *this; 49 | } 50 | 51 | // adds a single edge to the graph structure 52 | void add_edge(E source, E destination, P properties) noexcept { 53 | if (graph_.find(source) == std::end(graph_)) { 54 | std::vector> edges{std::make_pair(properties, destination)}; 55 | graph_[source] = edges; 56 | } else { 57 | graph_[source].emplace_back(properties, destination); 58 | } 59 | } 60 | 61 | // this groups connections by same source and properties 62 | [[nodiscard]] auto get_edges() const noexcept -> std::map, map_key_compare> { 63 | std::map, map_key_compare> all_edges{}; 64 | for (auto const& [source, sinks] : graph_) { 65 | 66 | for (const auto& sink : sinks) { 67 | auto key = std::make_pair(source, sink.first); 68 | all_edges.try_emplace(key, std::vector{}); 69 | all_edges[key].push_back(sink.second); 70 | } 71 | } 72 | 73 | return all_edges; 74 | }; 75 | 76 | // deletes the content of the graph 77 | void clear() noexcept { graph_.clear(); } 78 | 79 | // returns all the sources from the graph 80 | [[nodiscard]] auto keys() const noexcept -> std::vector { 81 | std::vector keys{}; 82 | for (auto it = graph_.begin(); it != graph_.end(); ++it) { 83 | keys.push_back(it->first); 84 | } 85 | return keys; 86 | } 87 | 88 | // returns the spanning tree of a given source including properties 89 | [[nodiscard]] auto spanning_tree(E source) noexcept -> std::map>> { 90 | std::map>> tree{}; 91 | std::vector work_nodes{source}; 92 | 93 | while (!work_nodes.empty()) { 94 | auto parent = *work_nodes.begin(); 95 | 96 | for (auto child : graph_[parent]) { 97 | // figuring out the properties until this node 98 | std::vector> parent_properties{}; 99 | if (tree.find(parent) != std::end(tree)) { 100 | // this if should always be the case except the first time when tree is empty 101 | parent_properties = tree[parent]; // TODO: make sure this is a copy otherwise we change this properties as 102 | // well 103 | } 104 | 105 | // appending the new property and inserting into the tree 106 | parent_properties.push_back(child); 107 | work_nodes.push_back(child.second); 108 | tree[child.second] = parent_properties; 109 | } 110 | 111 | work_nodes.erase(std::begin(work_nodes)); 112 | } 113 | 114 | return tree; 115 | } 116 | 117 | [[nodiscard]] auto get_destinations(E source) const noexcept -> std::vector> { 118 | return graph_[source]; 119 | } 120 | 121 | [[nodiscard]] auto get_upstream(E vertex) const noexcept -> std::optional { 122 | for (const auto& [source, sinks] : graph_) { 123 | if (sinks.second.contains(vertex)) { 124 | return source; 125 | } 126 | } 127 | } 128 | 129 | friend auto operator<<(std::ostream& outstream, const Graph& graph) -> std::ostream& { 130 | for (auto const& [source, destinations] : graph.graph_) { 131 | for (auto destination : destinations) { 132 | outstream << source << " --> " << destination.second << std::endl; 133 | } 134 | } 135 | return outstream; 136 | } 137 | }; 138 | } // namespace reactor 139 | #endif // REACTOR_CPP_GRAPH_HH -------------------------------------------------------------------------------- /examples/power_train/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "reactor-cpp/reactor-cpp.hh" 4 | 5 | using namespace reactor; 6 | 7 | class LeftPedal : public Reactor { 8 | public: 9 | // ports 10 | Output angle{"angle", this}; // NOLINT 11 | Output on_off{"on_off", this}; // NOLINT 12 | 13 | private: 14 | // actions 15 | PhysicalAction req{"req", this}; 16 | 17 | // reactions 18 | Reaction r1{"1", 1, this, [this]() { reaction_1(); }}; 19 | 20 | void reaction_1() {} 21 | 22 | public: 23 | explicit LeftPedal(Environment* env) 24 | : Reactor("LP", env) {} 25 | 26 | void assemble() override { 27 | r1.declare_trigger(&req); 28 | r1.declare_antidependency(&angle); 29 | r1.declare_antidependency(&on_off); 30 | } 31 | }; 32 | 33 | class RightPedal : public Reactor { 34 | public: 35 | // ports 36 | Output angle{"angle", this}; // NOLINT 37 | Input check{"check", this}; // NOLINT 38 | 39 | private: 40 | // actions 41 | PhysicalAction pol{"pol", this}; 42 | 43 | // reactions 44 | Reaction r1{"1", 1, this, [this]() { reaction_1(); }}; 45 | Reaction r2{"2", 2, this, [this]() { reaction_2(); }}; 46 | 47 | void reaction_1() {} 48 | void reaction_2() {} 49 | 50 | public: 51 | explicit RightPedal(Environment* env) 52 | : Reactor("RP", env) {} 53 | 54 | void assemble() override { 55 | r1.declare_trigger(&pol); 56 | r1.declare_antidependency(&angle); 57 | 58 | r2.declare_trigger(&check); 59 | r2.declare_schedulable_action(&pol); 60 | } 61 | }; 62 | 63 | class BrakeControl : public Reactor { 64 | public: 65 | // ports 66 | Input angle{"angle", this}; // NOLINT 67 | Output force{"force", this}; // NOLINT 68 | 69 | private: 70 | Reaction r1{"1", 1, this, [this]() { reaction_1(); }}; 71 | 72 | void reaction_1() {} 73 | 74 | public: 75 | explicit BrakeControl(Environment* env) 76 | : Reactor("BC", env) {} 77 | 78 | void assemble() override { 79 | r1.declare_trigger(&angle); 80 | r1.declare_antidependency(&force); 81 | } 82 | }; 83 | 84 | class EngineControl : public Reactor { 85 | public: 86 | // ports 87 | Input angle{"angle", this}; // NOLINT 88 | Input on_off{"on_off", this}; // NOLINT 89 | Output check{"check", this}; // NOLINT 90 | Output torque{"torque", this}; // NOLINT 91 | 92 | private: 93 | // actions 94 | PhysicalAction rev{"rev", this}; 95 | 96 | // reactions 97 | Reaction r1{"1", 1, this, [this]() { reaction_1(); }}; 98 | Reaction r2{"2", 2, this, [this]() { reaction_2(); }}; 99 | Reaction r3{"3", 3, this, [this]() { reaction_3(); }}; 100 | 101 | void reaction_1() {} 102 | void reaction_2() {} 103 | void reaction_3() {} 104 | 105 | public: 106 | explicit EngineControl(Environment* env) 107 | : Reactor("EC", env) {} 108 | 109 | void assemble() override { 110 | r1.declare_trigger(&on_off); 111 | r1.declare_antidependency(&torque); 112 | 113 | r2.declare_trigger(&angle); 114 | r2.declare_antidependency(&torque); 115 | 116 | r3.declare_trigger(&rev); 117 | r3.declare_antidependency(&check); 118 | } 119 | }; 120 | 121 | class Brake : public Reactor { 122 | public: 123 | // ports 124 | Input force{"force", this}; // NOLINT 125 | 126 | private: 127 | // reactions_ 128 | Reaction r1{"1", 1, this, [this]() { reaction_1(); }}; 129 | 130 | void reaction_1() {} 131 | 132 | public: 133 | explicit Brake(Environment* env) 134 | : Reactor("B", env) {} 135 | 136 | void assemble() override { r1.declare_trigger(&force); } 137 | }; 138 | 139 | class Engine : public Reactor { 140 | public: 141 | // ports 142 | Input torque{"torque", this}; // NOLINT 143 | 144 | private: 145 | // reactions_ 146 | Reaction r1{"1", 1, this, [this]() { reaction_1(); }}; 147 | 148 | void reaction_1() {} 149 | 150 | public: 151 | explicit Engine(Environment* env) 152 | : Reactor("E", env) {} 153 | 154 | void assemble() override { r1.declare_trigger(&torque); } 155 | }; 156 | 157 | auto main() -> int { 158 | Environment env{4}; 159 | 160 | LeftPedal left_pedal{&env}; 161 | RightPedal right_pedal{&env}; 162 | BrakeControl brake_control{&env}; 163 | EngineControl engine_control{&env}; 164 | Brake brakes{&env}; 165 | Engine engine{&env}; 166 | 167 | env.draw_connection(left_pedal.angle, brake_control.angle, ConnectionProperties{}); 168 | env.draw_connection(left_pedal.angle, engine_control.on_off, ConnectionProperties{}); 169 | env.draw_connection(brake_control.force, brakes.force, ConnectionProperties{}); 170 | env.draw_connection(right_pedal.angle, engine_control.angle, ConnectionProperties{}); 171 | env.draw_connection(engine_control.check, right_pedal.check, ConnectionProperties{}); 172 | env.draw_connection(engine_control.torque, engine.torque, ConnectionProperties{}); 173 | 174 | env.assemble(); 175 | 176 | env.export_dependency_graph("graph.dot"); 177 | 178 | auto thread = env.startup(); 179 | thread.join(); 180 | 181 | return 0; 182 | } 183 | -------------------------------------------------------------------------------- /lib/reaction.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #include 10 | 11 | #include "reactor-cpp/reaction.hh" 12 | 13 | #include "reactor-cpp/action.hh" 14 | #include "reactor-cpp/assert.hh" 15 | #include "reactor-cpp/environment.hh" 16 | #include "reactor-cpp/port.hh" 17 | 18 | namespace reactor { 19 | 20 | Reaction::Reaction(const std::string& name, int priority, Reactor* container, std::function body) 21 | : ReactorElement(name, ReactorElement::Type::Reaction, container) 22 | , priority_(priority) 23 | , body_(std::move(std::move(body))) { 24 | reactor_assert(priority != 0); 25 | } 26 | 27 | void Reaction::declare_trigger(BaseAction* action) { 28 | reactor_assert(action != nullptr); 29 | reactor_assert(this->environment() == action->environment()); 30 | assert_phase(this, Phase::Assembly); 31 | validate(this->container() == action->container(), "Action triggers must belong to the same reactor as the triggered " 32 | "reaction"); 33 | 34 | [[maybe_unused]] bool result = action_triggers_.insert(action).second; 35 | reactor_assert(result); 36 | action->register_trigger(this); 37 | } 38 | 39 | void Reaction::declare_schedulable_action(BaseAction* action) { 40 | reactor_assert(action != nullptr); 41 | reactor_assert(this->environment() == action->environment()); 42 | assert_phase(this, Phase::Assembly); 43 | validate(this->container() == action->container(), "Scheduable actions must belong to the same reactor as the " 44 | "triggered reaction"); 45 | 46 | [[maybe_unused]] bool result = scheduable_actions_.insert(action).second; 47 | reactor_assert(result); 48 | action->register_scheduler(this); 49 | } 50 | 51 | void Reaction::declare_trigger(BasePort* port) { 52 | reactor_assert(port != nullptr); 53 | reactor_assert(this->environment() == port->environment()); 54 | assert_phase(this, Phase::Assembly); 55 | 56 | if (port->is_input()) { 57 | validate(this->container() == port->container(), 58 | "Input port triggers must belong to the same reactor as the triggered " 59 | "reaction"); 60 | } else { 61 | validate(this->container() == port->container()->container(), 62 | "Output port triggers must belong to a contained reactor"); 63 | } 64 | 65 | [[maybe_unused]] bool result = port_trigger_.insert(port).second; 66 | reactor_assert(result); 67 | result = dependencies_.insert(port).second; 68 | reactor_assert(result); 69 | port->register_dependency(this, true); 70 | } 71 | 72 | void Reaction::declare_dependency(BasePort* port) { 73 | reactor_assert(port != nullptr); 74 | reactor_assert(this->environment() == port->environment()); 75 | assert_phase(this, Phase::Assembly); 76 | 77 | if (port->is_input()) { 78 | validate(this->container() == port->container(), "Dependent input ports must belong to the same reactor as the " 79 | "reaction"); 80 | } else { 81 | validate(this->container() == port->container()->container(), 82 | "Dependent output ports must belong to a contained reactor"); 83 | } 84 | 85 | [[maybe_unused]] bool result = dependencies_.insert(port).second; 86 | reactor_assert(result); 87 | port->register_dependency(this, false); 88 | } 89 | 90 | void Reaction::declare_antidependency(BasePort* port) { 91 | reactor_assert(port != nullptr); 92 | reactor_assert(this->environment() == port->environment()); 93 | assert_phase(this, Phase::Assembly); 94 | 95 | if (port->is_output()) { 96 | validate(this->container() == port->container(), "Antidependent output ports must belong to the same reactor as " 97 | "the reaction"); 98 | } else { 99 | validate(this->container() == port->container()->container(), 100 | "Antidependent input ports must belong to a contained reactor"); 101 | } 102 | 103 | [[maybe_unused]] bool result = antidependencies_.insert(port).second; 104 | reactor_assert(result); 105 | port->register_antidependency(this); 106 | } 107 | 108 | void Reaction::trigger() { 109 | if (has_deadline()) { 110 | reactor_assert(deadline_handler_ != nullptr); 111 | auto lag = Reactor::get_physical_time() - container()->get_logical_time(); 112 | if (lag > deadline_) { 113 | deadline_handler_(); 114 | return; 115 | } 116 | } 117 | 118 | body_(); 119 | } 120 | 121 | void Reaction::set_deadline_impl(Duration deadline, const std::function& handler) { 122 | reactor_assert(!has_deadline()); 123 | reactor_assert(handler != nullptr); 124 | this->deadline_ = deadline; 125 | this->deadline_handler_ = handler; 126 | } 127 | 128 | void Reaction::set_index(unsigned index) { 129 | validate(this->environment()->phase() == Phase::Assembly, "Reaction indexes may only be set during assembly phase!"); 130 | this->index_ = index; 131 | } 132 | 133 | } // namespace reactor 134 | -------------------------------------------------------------------------------- /lib/reactor.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #include "reactor-cpp/reactor.hh" 10 | 11 | #include "reactor-cpp/action.hh" 12 | #include "reactor-cpp/assert.hh" 13 | #include "reactor-cpp/environment.hh" 14 | #include "reactor-cpp/logging.hh" 15 | #include "reactor-cpp/port.hh" 16 | #include "reactor-cpp/reaction.hh" 17 | #include "reactor-cpp/statistics.hh" 18 | 19 | namespace reactor { 20 | Reactor::Reactor(const std::string& name, Reactor* container) 21 | : ReactorElement(name, ReactorElement::Type::Reactor, container) {} 22 | Reactor::Reactor(const std::string& name, Environment* environment) 23 | : ReactorElement(name, ReactorElement::Type::Reactor, environment) { 24 | environment->register_reactor(this); 25 | } 26 | 27 | void Reactor::register_action([[maybe_unused]] BaseAction* action) { 28 | reactor_assert(action != nullptr); 29 | reactor::validate(this->environment()->phase() == Phase::Construction || 30 | this->environment()->phase() == Phase::Assembly, 31 | "Actions can only be registered during construction phase!"); 32 | [[maybe_unused]] bool result = actions_.insert(action).second; 33 | reactor_assert(result); 34 | Statistics::increment_actions(); 35 | } 36 | 37 | void Reactor::register_input(BasePort* port) { 38 | reactor_assert(port != nullptr); 39 | reactor::validate(this->environment()->phase() == Phase::Construction, 40 | "Ports can only be registered during construction phase!"); 41 | [[maybe_unused]] bool result = inputs_.insert(port).second; 42 | reactor_assert(result); 43 | Statistics::increment_ports(); 44 | } 45 | 46 | void Reactor::register_output(BasePort* port) { 47 | reactor_assert(port != nullptr); 48 | reactor::validate(this->environment()->phase() == Phase::Construction, 49 | "Ports can only be registered during construction phase!"); 50 | [[maybe_unused]] bool result = inputs_.insert(port).second; 51 | reactor_assert(result); 52 | Statistics::increment_ports(); 53 | } 54 | 55 | void Reactor::register_reaction([[maybe_unused]] Reaction* reaction) { 56 | reactor_assert(reaction != nullptr); 57 | 58 | validate(this->environment()->phase() == Phase::Construction, 59 | "Reactions can only be registered during construction phase!"); 60 | [[maybe_unused]] bool result = reactions_.insert(reaction).second; 61 | reactor_assert(result); 62 | Statistics::increment_reactions(); 63 | } 64 | 65 | void Reactor::register_reactor([[maybe_unused]] Reactor* reactor) { 66 | reactor_assert(reactor != nullptr); 67 | validate(this->environment()->phase() == Phase::Construction, 68 | "Reactions can only be registered during construction phase!"); 69 | [[maybe_unused]] bool result = reactors_.insert(reactor).second; 70 | reactor_assert(result); 71 | Statistics::increment_reactor_instances(); 72 | } 73 | 74 | void Reactor::register_connection([[maybe_unused]] std::unique_ptr&& connection) { 75 | reactor_assert(connection != nullptr); 76 | [[maybe_unused]] auto result = connections_.insert(std::move(connection)).second; 77 | reactor_assert(result); 78 | } 79 | 80 | void Reactor::startup() { 81 | reactor_assert(environment()->phase() == Phase::Startup); 82 | log::Debug() << "Starting up reactor " << fqn(); 83 | // call startup on all contained objects 84 | for (auto* base_action : actions_) { 85 | base_action->startup(); 86 | } 87 | for (auto* base_port : inputs_) { 88 | base_port->startup(); 89 | } 90 | for (auto* base_port : outputs_) { 91 | base_port->startup(); 92 | } 93 | for (auto* reaction : reactions_) { 94 | reaction->startup(); 95 | } 96 | for (auto* reactor : reactors_) { 97 | reactor->startup(); 98 | } 99 | } 100 | 101 | void Reactor::shutdown() { 102 | reactor_assert(environment()->phase() == Phase::Shutdown); 103 | log::Debug() << "Terminating reactor " << fqn(); 104 | // call shutdown on all contained objects 105 | for (auto* action : actions_) { 106 | action->shutdown(); 107 | } 108 | for (auto* base_port : inputs_) { 109 | base_port->shutdown(); 110 | } 111 | for (auto* base_port : outputs_) { 112 | base_port->shutdown(); 113 | } 114 | for (auto* reaction : reactions_) { 115 | reaction->shutdown(); 116 | } 117 | for (auto* reactor : reactors_) { 118 | reactor->shutdown(); 119 | } 120 | } 121 | 122 | auto Reactor::get_physical_time() noexcept -> TimePoint { return reactor::get_physical_time(); } 123 | 124 | auto Reactor::get_logical_time() const noexcept -> TimePoint { 125 | return environment()->scheduler()->logical_time().time_point(); 126 | } 127 | 128 | auto Reactor::get_microstep() const noexcept -> mstep_t { 129 | return environment()->scheduler()->logical_time().micro_step(); 130 | } 131 | 132 | auto Reactor::get_tag() const noexcept -> Tag { 133 | return Tag::from_logical_time(environment()->scheduler()->logical_time()); 134 | } 135 | 136 | auto Reactor::get_elapsed_logical_time() const noexcept -> Duration { 137 | return get_logical_time() - environment()->start_tag().time_point(); 138 | } 139 | 140 | auto Reactor::get_elapsed_physical_time() const noexcept -> Duration { 141 | return get_physical_time() - environment()->start_tag().time_point(); 142 | } 143 | 144 | } // namespace reactor 145 | -------------------------------------------------------------------------------- /include/reactor-cpp/environment.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_ENVIRONMENT_HH 10 | #define REACTOR_CPP_ENVIRONMENT_HH 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "connection_properties.hh" 17 | #include "fwd.hh" 18 | #include "graph.hh" 19 | #include "reactor-cpp/logging.hh" 20 | #include "reactor-cpp/time.hh" 21 | #include "scheduler.hh" 22 | 23 | namespace reactor { 24 | 25 | constexpr unsigned int default_number_worker = 1; 26 | constexpr unsigned int default_max_reaction_index = 0; 27 | constexpr bool default_run_forever = false; 28 | constexpr bool default_fast_fwd_execution = false; 29 | 30 | enum class Phase : std::uint8_t { 31 | Construction = 0, 32 | Assembly = 1, 33 | Startup = 2, 34 | Execution = 3, 35 | Shutdown = 4, 36 | Deconstruction = 5 37 | }; 38 | 39 | class Environment { 40 | private: 41 | using Dependency = std::pair; 42 | 43 | std::string name_{}; 44 | log::NamedLogger log_; 45 | unsigned int num_workers_{default_number_worker}; 46 | unsigned int max_reaction_index_{default_max_reaction_index}; 47 | bool run_forever_{default_run_forever}; 48 | const bool fast_fwd_execution_{default_fast_fwd_execution}; 49 | 50 | std::set top_level_reactors_{}; 51 | /// Set of actions that act as an input to the reactor program in this environment 52 | std::set input_actions_{}; 53 | std::set reactions_{}; 54 | std::vector dependencies_{}; 55 | 56 | /// The environment containing this environment. nullptr if this is the top environment 57 | Environment* containing_environment_{nullptr}; 58 | /// Set of all environments contained by this environment 59 | std::set contained_environments_{}; 60 | /// Pointer to the top level environment 61 | Environment* top_environment_{nullptr}; 62 | 63 | Scheduler scheduler_; 64 | Phase phase_{Phase::Construction}; 65 | 66 | /// Timeout as given in the constructor 67 | const Duration timeout_{}; 68 | 69 | /// The start tag as determined during startup() 70 | Tag start_tag_{}; 71 | /// The timeout tag as determined during startup() 72 | Tag timeout_tag_{}; 73 | 74 | Graph graph_{}; 75 | Graph optimized_graph_{}; 76 | 77 | void build_dependency_graph(Reactor* reactor); 78 | void calculate_indexes(); 79 | 80 | std::mutex shutdown_mutex_{}; 81 | 82 | auto startup(const TimePoint& start_time) -> std::thread; 83 | 84 | public: 85 | explicit Environment(unsigned int num_workers, bool fast_fwd_execution = default_fast_fwd_execution, 86 | const Duration& timeout = Duration::max()); 87 | explicit Environment(const std::string& name, Environment* containing_environment); 88 | 89 | auto name() -> const std::string& { return name_; } 90 | 91 | // this method draw a connection between two graph elements with some properties 92 | template void draw_connection(Port& source, Port& sink, ConnectionProperties properties) { 93 | this->draw_connection(&source, &sink, properties); 94 | } 95 | 96 | template void draw_connection(Port* source, Port* sink, ConnectionProperties properties) { 97 | if (top_environment_ == nullptr || top_environment_ == this) { 98 | log::Debug() << "drawing connection: " << source->fqn() << " --> " << sink->fqn(); 99 | graph_.add_edge(source, sink, properties); 100 | } else { 101 | top_environment_->draw_connection(source, sink, properties); 102 | } 103 | } 104 | 105 | void optimize(); 106 | 107 | void register_reactor(Reactor* reactor); 108 | void register_port(BasePort* port) noexcept; 109 | void register_input_action(BaseAction* action); 110 | void assemble(); 111 | auto startup() -> std::thread; 112 | void sync_shutdown(); 113 | void async_shutdown(); 114 | 115 | void export_dependency_graph(const std::string& path); 116 | 117 | [[nodiscard]] auto top_level_reactors() const noexcept -> const auto& { return top_level_reactors_; } 118 | [[nodiscard]] auto phase() const noexcept -> Phase { return phase_; } 119 | [[nodiscard]] auto scheduler() const noexcept -> const Scheduler* { return &scheduler_; } 120 | 121 | auto scheduler() noexcept -> Scheduler* { return &scheduler_; } 122 | 123 | [[nodiscard]] auto logical_time() const noexcept -> const LogicalTime& { return scheduler_.logical_time(); } 124 | [[nodiscard]] auto start_tag() const noexcept -> const Tag& { return start_tag_; } 125 | [[nodiscard]] auto timeout() const noexcept -> const Duration& { return timeout_; } 126 | [[nodiscard]] auto timeout_tag() const noexcept -> const Tag& { return timeout_tag_; } 127 | 128 | static auto physical_time() noexcept -> TimePoint { return get_physical_time(); } 129 | 130 | [[nodiscard]] auto num_workers() const noexcept -> unsigned int { return num_workers_; } 131 | [[nodiscard]] auto fast_fwd_execution() const noexcept -> bool { return fast_fwd_execution_; } 132 | [[nodiscard]] auto run_forever() const noexcept -> bool { return run_forever_; } 133 | [[nodiscard]] auto max_reaction_index() const noexcept -> unsigned int { return max_reaction_index_; } 134 | 135 | friend Scheduler; 136 | }; 137 | } // namespace reactor 138 | 139 | #endif // REACTOR_CPP_ENVIRONMENT_HH 140 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "lf-benchmark-runner": { 4 | "inputs": { 5 | "naersk": "naersk", 6 | "nixpkgs": "nixpkgs", 7 | "utils": "utils" 8 | }, 9 | "locked": { 10 | "lastModified": 1659456277, 11 | "narHash": "sha256-V8aOHKj/+tYvo5dCkIiVkMX0+0wIWI/i1t7CwYjZS4c=", 12 | "owner": "revol-xut", 13 | "repo": "lf-benchmark-runner", 14 | "rev": "754fecc48abfaa0334d757cba1d2ce79afde4c78", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "revol-xut", 19 | "repo": "lf-benchmark-runner", 20 | "type": "github" 21 | } 22 | }, 23 | "lingua-franca-benchmarks": { 24 | "flake": false, 25 | "locked": { 26 | "lastModified": 1667410143, 27 | "narHash": "sha256-8U5BlxWABZhVIk3DKz0L6bExYINNL9xDhAo7VXkDCQQ=", 28 | "owner": "lf-lang", 29 | "repo": "benchmarks-lingua-franca", 30 | "rev": "6e6d445110365dd3af8ed69fbb4d42202bb00aa3", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "lf-lang", 35 | "repo": "benchmarks-lingua-franca", 36 | "type": "github" 37 | } 38 | }, 39 | "lingua-franca-src": { 40 | "flake": false, 41 | "locked": { 42 | "narHash": "sha256-jSINlwHfSOPbti3LJTXpSk6lcUtwKfz7CMLtq2OuNns=", 43 | "type": "tarball", 44 | "url": "https://github.com/lf-lang/lingua-franca/releases/download/v0.3.0/lfc_0.3.0.tar.gz" 45 | }, 46 | "original": { 47 | "type": "tarball", 48 | "url": "https://github.com/lf-lang/lingua-franca/releases/download/v0.3.0/lfc_0.3.0.tar.gz" 49 | } 50 | }, 51 | "lingua-franca-tests": { 52 | "flake": false, 53 | "locked": { 54 | "lastModified": 1668669130, 55 | "narHash": "sha256-j5fEXiJZOH/ZpCgt+Qy6gvrtHeVAmMl2+2+vCcEAJQo=", 56 | "owner": "lf-lang", 57 | "repo": "lingua-franca", 58 | "rev": "e0156ba43a0ac6d1a7662ccb8ccc8d0bacb298d2", 59 | "type": "github" 60 | }, 61 | "original": { 62 | "owner": "lf-lang", 63 | "repo": "lingua-franca", 64 | "type": "github" 65 | } 66 | }, 67 | "naersk": { 68 | "inputs": { 69 | "nixpkgs": [ 70 | "lf-benchmark-runner", 71 | "nixpkgs" 72 | ] 73 | }, 74 | "locked": { 75 | "lastModified": 1649096192, 76 | "narHash": "sha256-7O8e+eZEYeU+ET98u/zW5epuoN/xYx9G+CIh4DjZVzY=", 77 | "owner": "nix-community", 78 | "repo": "naersk", 79 | "rev": "d626f73332a8f587b613b0afe7293dd0777be07d", 80 | "type": "github" 81 | }, 82 | "original": { 83 | "owner": "nix-community", 84 | "repo": "naersk", 85 | "type": "github" 86 | } 87 | }, 88 | "nixpkgs": { 89 | "locked": { 90 | "lastModified": 1649368816, 91 | "narHash": "sha256-lVzCpg2xfTUrfcankjlym/mrh/7F/gpWQ7CYQM6BcPY=", 92 | "owner": "NixOS", 93 | "repo": "nixpkgs", 94 | "rev": "29abf698b384258b540e39a86b53ea980495ac4c", 95 | "type": "github" 96 | }, 97 | "original": { 98 | "owner": "NixOS", 99 | "ref": "nixos-21.11", 100 | "repo": "nixpkgs", 101 | "type": "github" 102 | } 103 | }, 104 | "nixpkgs_2": { 105 | "locked": { 106 | "lastModified": 1668905981, 107 | "narHash": "sha256-RBQa/+9Uk1eFTqIOXBSBezlEbA3v5OkgP+qptQs1OxY=", 108 | "owner": "NixOs", 109 | "repo": "nixpkgs", 110 | "rev": "690ffff026b4e635b46f69002c0f4e81c65dfc2e", 111 | "type": "github" 112 | }, 113 | "original": { 114 | "owner": "NixOs", 115 | "ref": "nixos-unstable", 116 | "repo": "nixpkgs", 117 | "type": "github" 118 | } 119 | }, 120 | "reactor-cpp": { 121 | "flake": false, 122 | "locked": { 123 | "lastModified": 1668160441, 124 | "narHash": "sha256-sqcccj1yGUbYIw6JsZZCvId/a/cZSzd0EeaXV3Vk9AI=", 125 | "owner": "lf-lang", 126 | "repo": "reactor-cpp", 127 | "rev": "6318913840747f8a85cf22ed1ca7c60f9f51a294", 128 | "type": "github" 129 | }, 130 | "original": { 131 | "owner": "lf-lang", 132 | "repo": "reactor-cpp", 133 | "type": "github" 134 | } 135 | }, 136 | "root": { 137 | "inputs": { 138 | "lf-benchmark-runner": "lf-benchmark-runner", 139 | "lingua-franca-benchmarks": "lingua-franca-benchmarks", 140 | "lingua-franca-src": "lingua-franca-src", 141 | "lingua-franca-tests": "lingua-franca-tests", 142 | "nixpkgs": "nixpkgs_2", 143 | "reactor-cpp": "reactor-cpp", 144 | "utils": "utils_2" 145 | } 146 | }, 147 | "utils": { 148 | "locked": { 149 | "lastModified": 1648297722, 150 | "narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=", 151 | "owner": "numtide", 152 | "repo": "flake-utils", 153 | "rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade", 154 | "type": "github" 155 | }, 156 | "original": { 157 | "owner": "numtide", 158 | "repo": "flake-utils", 159 | "type": "github" 160 | } 161 | }, 162 | "utils_2": { 163 | "locked": { 164 | "lastModified": 1667395993, 165 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 166 | "owner": "numtide", 167 | "repo": "flake-utils", 168 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 169 | "type": "github" 170 | }, 171 | "original": { 172 | "owner": "numtide", 173 | "repo": "flake-utils", 174 | "type": "github" 175 | } 176 | } 177 | }, 178 | "root": "root", 179 | "version": 7 180 | } 181 | -------------------------------------------------------------------------------- /nix/library.nix: -------------------------------------------------------------------------------- 1 | { fetchFromGitHub 2 | , lingua-franca-src 3 | , reactor-cpp-src 4 | , valgrind 5 | , pkgs 6 | , lib 7 | , stdenv 8 | }: 9 | let 10 | # lingua-franca compiler package 11 | lingua-franca = pkgs.callPackage ./lfc.nix { 12 | lingua-franca-src = lingua-franca-src; 13 | mkDerivation = stdenv.mkDerivation; 14 | }; 15 | 16 | # reactor-cpp runtime package 17 | cpp-runtime = pkgs.callPackage ./reactor-cpp.nix { 18 | reactor-cpp-src = reactor-cpp-src; 19 | mkDerivation = stdenv.mkDerivation; 20 | debug = false; 21 | }; 22 | 23 | # list of compiler for which packages are generated 24 | compilers = with pkgs; [ gcc11 gcc10 gcc9 clang_11 clang_12 clang_13 ]; 25 | 26 | default_compiler = pkgs.gcc11; 27 | 28 | mkDerivation = stdenv.mkDerivation; 29 | 30 | # has file extension .lf there is one cmake file and that needs to filtered out 31 | has_file_extensions_lf = file: lib.strings.hasSuffix ".lf" file; 32 | 33 | # there are under lib/ lingua fracna programs that dont define an main reactor and therefore fail 34 | is_regular_program = file: !(lib.strings.hasInfix "/lib/" file); 35 | 36 | # checks if a given file is in the unrunable file list 37 | # TODO: this needs fixing 38 | is_borked = file: !((lib.strings.hasInfix "StructAsState.lf" file) || (lib.strings.hasInfix "StructAsType.lf" file) || (lib.strings.hasInfix "Struct" file)); 39 | 40 | # functiosn combines the two rules above 41 | filter_function = file: (is_regular_program file) && (has_file_extensions_lf file) && (is_borked file); 42 | 43 | # extracts the name of the file without the .lf ending from a given file name 44 | extract_name = file: builtins.head (lib.strings.splitString ".lf" (builtins.head (lib.reverseList (builtins.split "/" file)))); 45 | 46 | # given a set { name = ...; value = ... } it will retrun the derivation stored in value 47 | extract_derivation = (attribute_set: attribute_set.value); 48 | 49 | # format version of the format x.y.z to x-y-z 50 | fmt_version = (version: builtins.replaceStrings [ "." ] [ "-" ] "${version}"); 51 | 52 | # list of available compilers 53 | list_of_compilers = (lib.strings.concatStringsSep "\n" (builtins.map (compiler: (''echo "${compiler.pname}-${fmt_version compiler.version}"'')) compilers)); 54 | 55 | # writes a shell script which prints all the available compilers with their corresponding versions 56 | all-compilers-script = (pkgs.writeScriptBin "all-compilers" ('' 57 | #!${pkgs.runtimeShell} 58 | 59 | '' + list_of_compilers)); 60 | 61 | # function which build the derivation b 62 | buildDerivation = (test_file: compiler: 63 | let 64 | package_version = builtins.replaceStrings [ "." ] [ "-" ] "${compiler.version}"; 65 | file_name = extract_name test_file; 66 | package_name = "${file_name}-${compiler.pname}-${package_version}"; 67 | in 68 | { 69 | name = package_name; 70 | value = mkDerivation { 71 | name = package_name; 72 | 73 | src = ./.; 74 | 75 | # libgmp-dev is only required here because there is some special snowflake benchmark 76 | buildInputs = with pkgs; [ lingua-franca which cmake git boost gmp ] ++ [ compiler ]; 77 | 78 | configurePhase = '' 79 | echo "+++++ CURRENT TEST: ${test_file} +++++"; 80 | ''; 81 | 82 | buildPhase = '' 83 | ${lingua-franca}/bin/lfc --external-runtime-path ${cpp-runtime}/ --output ./ ${test_file} 84 | ''; 85 | 86 | installPhase = '' 87 | mkdir -p $out/bin 88 | mkdir -p $out/debug 89 | cp -r ./src-gen/* $out/debug/ 90 | cp -r ./bin/${file_name} $out/bin/${file_name}-${compiler.pname} 91 | cp -r ./bin/${file_name} $out/bin/${package_name} 92 | ''; 93 | }; 94 | }); 95 | in 96 | { 97 | compilers = compilers; 98 | buildDerivation = buildDerivation; 99 | mkDerivation = mkDerivation; 100 | has_file_extensions_lf = has_file_extensions_lf; 101 | 102 | # extracts the name of the file without the .lf ending from a given file name 103 | extract_name = file: builtins.head (lib.strings.splitString ".lf" (builtins.head (lib.reverseList (builtins.split "/" file)))); 104 | 105 | # searches all lingua-franca files in that repo 106 | search_files = (path: lib.filter filter_function (lib.filesystem.listFilesRecursive path)); 107 | 108 | # creates the cartesian product 109 | double_map = (list_1: list_2: func: lib.lists.flatten (builtins.map (x: builtins.map (y: func x y) list_2) list_1)); 110 | 111 | # function which takes the set of files and computes a list of derivation out of those files 112 | create_derivations = (files: builtins.map extract_derivation (builtins.map (test: buildDerivation test default_compiler) files)); 113 | 114 | # derivation which holds the script to list all available compilers 115 | list-compilers = mkDerivation { 116 | src = ./.; 117 | name = "list-compilers"; 118 | installPhase = '' 119 | mkdir -p $out/bin/ 120 | cp ${all-compilers-script}/bin/all-compilers $out/bin/list-compilers 121 | ''; 122 | }; 123 | 124 | # creates the copy command for every derivation 125 | create_install_command = (list_of_derivations: (lib.strings.concatStringsSep "\n" (builtins.map (x: "cp -r ${x}/bin/* $out/bin/") list_of_derivations))); 126 | 127 | # checks the given package for memory leaks and exports a the result 128 | memtest = (package: 129 | { 130 | name = "MLeaks-${package.name}"; 131 | value = mkDerivation { 132 | name = "MLeaks-${package.name}"; 133 | src = ./.; 134 | nativeBuildInputs = [ valgrind ]; 135 | 136 | buildPhase = '' 137 | ${valgrind}/bin/valgrind --leak-check=full \ 138 | --show-leak-kinds=all \ 139 | --track-origins=yes \ 140 | --verbose \ 141 | --log-file=valgrind-out.txt \ 142 | ${package}/bin/${package.name} 143 | ''; 144 | installPhase = '' 145 | mkdir -p $out/data 146 | cp valgrind-out.txt $out/data/${package.name}-memtest.out 147 | ''; 148 | }; 149 | } 150 | ); 151 | } 152 | -------------------------------------------------------------------------------- /nix/benchmark.nix: -------------------------------------------------------------------------------- 1 | { fetchFromGitHub 2 | , pkgs 3 | , lib 4 | , stdenv 5 | , lf-benchmark-runner 6 | , reactor-cpp-src 7 | , lingua-franca-src 8 | , lingua-franca-benchmarks 9 | , valgrind 10 | , rev-reactor 11 | , rev-lingua-franca 12 | }: 13 | let 14 | 15 | # custom library which supplies essential functionality 16 | library = pkgs.callPackage ./library.nix { 17 | lingua-franca-src = lingua-franca-src; 18 | reactor-cpp-src = reactor-cpp-src; 19 | }; 20 | 21 | hashed-inputs = builtins.hashString "sha1" (rev-reactor + rev-lingua-franca); 22 | 23 | mkDerivation = library.mkDerivation; 24 | 25 | # Borked Benchmarks or not even Benchmarks 26 | borked_benchmarks = [ 27 | "BenchmarkRunner.lf" 28 | ]; 29 | 30 | # filter out name 31 | extract_name = (file: builtins.head (lib.reverseList (builtins.split "/" file))); 32 | 33 | borked_benchmarks_filter = (file: !(lib.lists.elem (extract_name file) borked_benchmarks)); 34 | 35 | filter_function = (file: (borked_benchmarks_filter file) && (library.has_file_extensions_lf file)); 36 | 37 | # searches all lingua-franca files in that repo 38 | benchmarks = (lib.filter filter_function (library.search_files "${lingua-franca-benchmarks}/Cpp/Savina/src")); 39 | 40 | list_of_derivations = (library.create_derivations benchmarks); 41 | 42 | # executes all benchmarks 43 | execute_all = (lib.strings.concatStringsSep "\n" (builtins.map (x: "${x}/bin/${x.name}") list_of_derivations)); 44 | 45 | # writes the execute command to file 46 | run_all = (pkgs.writeScriptBin "all-benchmarks" ('' 47 | #!${pkgs.runtimeShell} 48 | 49 | '' + execute_all)); 50 | 51 | # string of all benchmarks 52 | list_of_benchmarks = (lib.strings.concatStringsSep "\n" (builtins.map library.extract_name benchmarks)); 53 | 54 | # script of all benchmarks 55 | all-benchmarks-script = (pkgs.writeScriptBin "all-benchmarks" (list_of_benchmarks + "\n")); 56 | 57 | # derivation which hold the script 58 | list-benchmarks = mkDerivation { 59 | src = ./.; 60 | name = "list-benchmarks"; 61 | installPhase = '' 62 | mkdir -p $out/bin 63 | echo "cat ${all-benchmarks-script}/bin/all-benchmarks" > $out/bin/list-benchmarks 64 | chmod +x $out/bin/list-benchmarks 65 | ''; 66 | }; 67 | 68 | # created a concatination of copy commands to collect them in one area 69 | install_command = (library.create_install_command (library.create_derivations benchmarks)); 70 | 71 | # package that triggers a build of every test file 72 | all-benchmarks = mkDerivation { 73 | src = ./.; 74 | name = "all-benchmarks"; 75 | buildInputs = list_of_derivations; 76 | installPhase = '' 77 | ${pkgs.coreutils}/bin/mkdir -p $out/bin 78 | ${pkgs.coreutils}/bin/cp ${run_all}/bin/* $out/bin/ 79 | '' + install_command; 80 | }; 81 | 82 | # package that builds all backages 83 | build-all-benchmarks = mkDerivation { 84 | src = ./.; 85 | name = "build-all-benchmarks"; 86 | buildInputs = list_of_derivations; 87 | installPhase = '' 88 | ${pkgs.coreutils}/bin/mkdir -p $out/bin 89 | '' + install_command; 90 | }; 91 | 92 | # runs our custom benchmark data extractor on the specified benchmark 93 | benchmark_command = (benchmark: "${lf-benchmark-runner}/bin/lf-benchmark-runner --target lf-cpp-${hashed-inputs} --binary ${benchmark}/bin/${benchmark.name} --file ./result.csv"); 94 | 95 | # compiles a giant script to run every benchmark and collect their results into on csv file 96 | benchmark_commands = lib.strings.concatStringsSep "\n" (builtins.map benchmark_command list_of_derivations); 97 | 98 | # derivation defintion for running and collecting data from benchmarks 99 | make-benchmark = mkDerivation { 100 | src = ./.; 101 | name = "make-benchmark"; 102 | 103 | buildPhase = benchmark_commands; 104 | 105 | installPhase = '' 106 | mkdir -p $out/data/ 107 | cp result.csv $out/data/ 108 | ''; 109 | }; 110 | 111 | individual-benchmark = (package: { 112 | name = "benchmark-${package.name}"; 113 | value = mkDerivation { 114 | name = "benchmark-${package.name}"; 115 | src = ./.; 116 | 117 | buildPhase = '' 118 | ${lf-benchmark-runner}/bin/lf-benchmark-runner --target lf-cpp --binary ${package}/bin/${package.name} --file ./result.csv --image result.svg 119 | ''; 120 | 121 | installPhase = '' 122 | mkdir -p $out/data 123 | cp result.csv $out/data 124 | ''; 125 | }; 126 | }); 127 | 128 | # derivation for call and cachegrind. measuring a given package 129 | profiler = (package: valgrind_check: 130 | { 131 | name = "${valgrind_check}-${package.name}"; 132 | value = mkDerivation { 133 | name = "${valgrind_check}-${package.name}"; 134 | src = ./.; 135 | nativeBuildInputs = [ valgrind ]; 136 | 137 | buildPhase = '' 138 | ${valgrind}/bin/valgrind --tool=${valgrind_check} ${package}/bin/${package.name} 139 | ''; 140 | installPhase = '' 141 | mkdir -p $out/data 142 | cp ${valgrind_check}.out.* $out/data/${package.name}-${valgrind_check}.out 143 | ''; 144 | }; 145 | } 146 | ); 147 | 148 | extract_derivations = (list: lib.attrValues (lib.listToAttrs list)); 149 | attribute_set_derivations = (library.double_map benchmarks library.compilers library.buildDerivation); 150 | 151 | 152 | attribute_set_cachegrind = (builtins.map (x: profiler x "cachegrind") (extract_derivations attribute_set_derivations)); 153 | attribute_set_callgrind = (builtins.map (x: profiler x "callgrind") (extract_derivations attribute_set_derivations)); 154 | attribute_set_benchmarks = (builtins.map individual-benchmark (extract_derivations attribute_set_derivations)); 155 | attribute_set_memory = (builtins.map library.memtest (extract_derivations attribute_set_derivations)); 156 | in 157 | lib.listToAttrs ( 158 | attribute_set_derivations 159 | ++ attribute_set_cachegrind 160 | ++ attribute_set_callgrind 161 | ++ attribute_set_benchmarks 162 | ++ attribute_set_memory 163 | ++ [ 164 | { name = "all-benchmarks"; value = all-benchmarks; } 165 | { name = "list-benchmarks"; value = list-benchmarks; } 166 | { name = "list-compilers"; value = library.list-compilers; } 167 | { name = "make-benchmark"; value = make-benchmark; } 168 | ]) 169 | -------------------------------------------------------------------------------- /lib/port.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #include "reactor-cpp/port.hh" 10 | 11 | #include "reactor-cpp/assert.hh" 12 | #include "reactor-cpp/connection.hh" 13 | #include "reactor-cpp/environment.hh" 14 | #include "reactor-cpp/reaction.hh" 15 | #include "reactor-cpp/statistics.hh" 16 | 17 | namespace reactor { 18 | 19 | void BasePort::register_dependency(Reaction* reaction, bool is_trigger) noexcept { 20 | reactor_assert(reaction != nullptr); 21 | reactor_assert(this->environment() == reaction->environment()); 22 | validate(!this->has_outward_bindings(), "Dependencies may no be declared on ports with an outward binding!"); 23 | assert_phase(this, Phase::Assembly); 24 | 25 | if (this->is_input()) { 26 | validate(this->container() == reaction->container(), "Dependent input ports must belong to the same reactor as the " 27 | "reaction"); 28 | } else { 29 | validate(this->container()->container() == reaction->container(), 30 | "Dependent output ports must belong to a contained reactor"); 31 | } 32 | 33 | [[maybe_unused]] bool result = dependencies_.insert(reaction).second; 34 | reactor_assert(result); 35 | if (is_trigger) { 36 | result = triggers_.insert(reaction).second; 37 | reactor_assert(result); 38 | } 39 | } 40 | 41 | void BasePort::register_antidependency(Reaction* reaction) noexcept { 42 | reactor_assert(reaction != nullptr); 43 | reactor_assert(this->environment() == reaction->environment()); 44 | validate(!this->has_inward_binding(), "Antidependencies may no be declared on ports with an inward binding!"); 45 | assert_phase(this, Phase::Assembly); 46 | 47 | if (this->is_output()) { 48 | validate(this->container() == reaction->container(), 49 | "Antidependent output ports must belong to the same reactor as " 50 | "the reaction"); 51 | } else { 52 | validate(this->container()->container() == reaction->container(), 53 | "Antidependent input ports must belong to a contained reactor"); 54 | } 55 | 56 | [[maybe_unused]] bool result = anti_dependencies_.insert(reaction).second; 57 | reactor_assert(result); 58 | } 59 | 60 | [[maybe_unused]] auto Port::typed_outward_bindings() const noexcept -> const std::set*>& { 61 | // this is undefined behavior and should be changed 62 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 63 | return reinterpret_cast*>&>(outward_bindings()); // use C++20 std::bit_cast 64 | } 65 | 66 | auto Port::typed_inward_binding() const noexcept -> Port* { 67 | // we can use a reinterpret cast here since we know that this port is always 68 | // connected with another Port. 69 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 70 | return reinterpret_cast*>(inward_binding()); 71 | } 72 | 73 | void Port::set() { 74 | validate(!has_inward_binding(), "set() may only be called on ports that do not have an inward " 75 | "binding!"); 76 | 77 | auto* scheduler = environment()->scheduler(); 78 | scheduler->set_port(this); 79 | this->present_ = true; 80 | } 81 | 82 | void Port::instantiate_connection_to(const ConnectionProperties& properties, 83 | const std::vector& downstream) { 84 | std::unique_ptr> connection = nullptr; 85 | 86 | if (downstream.empty()) { 87 | return; 88 | } 89 | 90 | // normal connections should be handled by environment 91 | reactor_assert(properties.type_ != ConnectionType::Normal); 92 | 93 | Environment* enclave = downstream[0]->environment(); 94 | auto index = this->container()->number_of_connections(); 95 | 96 | if (properties.type_ == ConnectionType::Delayed) { 97 | connection = std::make_unique>( 98 | this->name() + "_delayed_connection_" + std::to_string(index), this->container(), properties.delay_); 99 | } 100 | if (properties.type_ == ConnectionType::Physical) { 101 | connection = std::make_unique>( 102 | this->name() + "_physical_connection_" + std::to_string(index), this->container(), properties.delay_); 103 | } 104 | if (properties.type_ == ConnectionType::Enclaved) { 105 | connection = std::make_unique>( 106 | this->name() + "_enclave_connection_" + std::to_string(index), enclave); 107 | } 108 | if (properties.type_ == ConnectionType::DelayedEnclaved) { 109 | connection = std::make_unique>( 110 | this->name() + "_delayed_enclave_connection_" + std::to_string(index), enclave, properties.delay_); 111 | } 112 | if (properties.type_ == ConnectionType::PhysicalEnclaved) { 113 | connection = std::make_unique>( 114 | this->name() + "_physical_enclave_connection_" + std::to_string(index), enclave); 115 | } 116 | 117 | // if the connection here is null we have a vaulty enum value 118 | reactor_assert(connection != nullptr); 119 | connection->bind_downstream_ports(downstream); 120 | connection->bind_upstream_port(this); 121 | this->register_set_callback(connection->upstream_set_callback()); 122 | this->container()->register_connection(std::move(connection)); 123 | } 124 | 125 | // This function can be used to chain two callbacks. This mechanism is not 126 | // very efficient if many callbacks are registered on the same port. However, 127 | // it is more efficient than, e.g., a vector of callbacks if usually only one 128 | // callback is registered. At the moment, we use at most two callbacks on the 129 | // same port (one if the port is in a multiport, and one if it is upstream of 130 | // a delayed connection). 131 | auto compose_callbacks(const PortCallback& callback1, const PortCallback& callback2) -> PortCallback { 132 | return [=](const BasePort& port) { 133 | callback1(port); 134 | callback2(port); 135 | }; 136 | } 137 | 138 | void BasePort::register_set_callback(const PortCallback& callback) { 139 | if (set_callback_ == nullptr) { 140 | set_callback_ = callback; 141 | } else { 142 | set_callback_ = compose_callbacks(set_callback_, callback); 143 | } 144 | } 145 | 146 | void BasePort::register_clean_callback(const PortCallback& callback) { 147 | if (clean_callback_ == nullptr) { 148 | clean_callback_ = callback; 149 | } else { 150 | clean_callback_ = compose_callbacks(clean_callback_, callback); 151 | } 152 | } 153 | 154 | } // namespace reactor 155 | -------------------------------------------------------------------------------- /tracing/ctf_to_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2020 TU Dresden 4 | # All rights reserved. 5 | # 6 | # Authors: 7 | # Christian Menard 8 | 9 | 10 | import argparse 11 | import bt2 12 | import json 13 | import os 14 | import sys 15 | 16 | 17 | pid_registry = {} 18 | tid_registry = {} 19 | 20 | 21 | def get_ids(process, thread): 22 | if process not in pid_registry: 23 | pid_registry[process] = len(pid_registry) + 1 24 | tid_registry[process] = {} 25 | pid = pid_registry[process] 26 | tid_reg = tid_registry[process] 27 | if thread not in tid_reg: 28 | tid_reg[thread] = len(tid_reg) 29 | tid = tid_reg[thread] 30 | return pid, tid 31 | 32 | 33 | def main(): 34 | parser = argparse.ArgumentParser( 35 | description="Convert a CTF trace to a json trace viewable with google " 36 | "chrome") 37 | parser.add_argument("ctf", metavar="CTF", type=str, 38 | help="Path to the CTF trace") 39 | parser.add_argument("-o", "--output", metavar="OUT", type=str, 40 | default="trace.json", help="the output file") 41 | args = parser.parse_args() 42 | 43 | if not os.path.isdir(args.ctf): 44 | raise NotADirectoryError(args.ctf) 45 | 46 | ctf_path = None 47 | for root, dirs, files in os.walk(args.ctf): 48 | for f in files: 49 | if f == "metadata": 50 | if ctf_path is None: 51 | ctf_path = str(root) 52 | else: 53 | raise RuntimeError("%s is not a single trace (contains " 54 | "more than one metadata file!" % 55 | args.ctf) 56 | 57 | if ctf_path is None: 58 | raise RuntimeError("%s is not a CTF trace (does not contain a metadata" 59 | " file)" % args.ctf) 60 | 61 | # Find the `ctf` plugin (shipped with Babeltrace 2). 62 | ctf_plugin = bt2.find_plugin('ctf') 63 | 64 | # Get the `source.ctf.fs` component class from the plugin. 65 | fs_cc = ctf_plugin.source_component_classes['fs'] 66 | 67 | # Create a trace collection message iterator, instantiating a single 68 | # `source.ctf.fs` component class with the `inputs` initialization 69 | # parameter set to open a single CTF trace. 70 | msg_it = bt2.TraceCollectionMessageIterator(bt2.ComponentSpec(fs_cc, { 71 | # Get the CTF trace path from the first command-line argument. 72 | 'inputs': [ctf_path], 73 | })) 74 | 75 | # keep a list of events to dump later to JSON 76 | trace_events = [] 77 | 78 | # Iterate the trace messages. 79 | for msg in msg_it: 80 | # `bt2._EventMessageConst` is the Python type of an event message. 81 | if type(msg) is bt2._EventMessageConst: 82 | event = msg.event 83 | 84 | if (event.name == "reactor_cpp:reaction_execution_starts"): 85 | trace_events.append(reaction_execution_starts_to_dict(msg)) 86 | elif (event.name == "reactor_cpp:reaction_execution_finishes"): 87 | trace_events.append(reaction_execution_finishes_to_dict(msg)) 88 | elif (event.name == "reactor_cpp:schedule_action"): 89 | trace_events.append(schedule_action_to_dict(msg)) 90 | elif (event.name == "reactor_cpp:trigger_reaction"): 91 | trace_events.append(trigger_reaction_to_dict(msg)) 92 | 93 | # add some metadata 94 | configure_process_name(trace_events, 0, "Execution") 95 | for i in range(1, 128): 96 | configure_thread_name(trace_events, 0, i, "Worker %d" % i) 97 | for process, pid in pid_registry.items(): 98 | configure_process_name(trace_events, pid, process) 99 | for thread, tid in tid_registry[process].items(): 100 | configure_thread_name(trace_events, pid, tid, thread) 101 | 102 | data = { 103 | "traceEvents": trace_events, 104 | "displayTimeUnit": "ns", 105 | } 106 | with open(args.output, 'w') as outfile: 107 | json.dump(data, outfile, indent=2) 108 | 109 | 110 | def configure_process_name(events, pid, name): 111 | events.append({ 112 | "name": "process_name", 113 | "ph": "M", 114 | "pid": pid, 115 | "args": { 116 | "name": name 117 | } 118 | }) 119 | 120 | 121 | def configure_thread_name(events, pid, tid, name): 122 | events.append({ 123 | "name": "thread_name", 124 | "ph": "M", 125 | "pid": pid, 126 | "tid": tid, 127 | "args": { 128 | "name": name 129 | } 130 | }) 131 | 132 | 133 | def get_timestamp_us(msg): 134 | timestamp_ns = msg.default_clock_snapshot.ns_from_origin 135 | return timestamp_ns / 1000.0 136 | 137 | 138 | def reaction_execution_starts_to_dict(msg): 139 | event = msg.event 140 | return { 141 | "name": str(event["reaction_name"]), 142 | "cat": "Execution", 143 | "ph": "B", 144 | "ts": get_timestamp_us(msg), 145 | "pid": 0, 146 | "tid": int(event["worker_id"]), 147 | "args": { 148 | "microstep": int(event["timestamp_microstep"]), 149 | "timestamp_ns": int(event["timestamp_ns"]) 150 | } 151 | } 152 | 153 | 154 | def reaction_execution_finishes_to_dict(msg): 155 | event = msg.event 156 | return { 157 | "name": str(event["reaction_name"]), 158 | "cat": "Execution", 159 | "ph": "E", 160 | "ts": get_timestamp_us(msg), 161 | "pid": 0, 162 | "tid": int(event["worker_id"]), 163 | "args": { 164 | "microstep": int(event["timestamp_microstep"]), 165 | "timestamp_ns": int(event["timestamp_ns"]) 166 | } 167 | } 168 | 169 | 170 | def schedule_action_to_dict(msg): 171 | event = msg.event 172 | pid, tid = get_ids(str(event["reactor_name"]), str(event["action_name"])) 173 | return { 174 | "name": "schedule", 175 | "cat": "Reactors", 176 | "ph": "i", 177 | "ts": float(event["timestamp_ns"]) / 1000.0, 178 | "pid": pid, 179 | "tid": tid, 180 | "s": "t", 181 | "cname": "terrible", 182 | "args": { 183 | "microstep": int(event["timestamp_microstep"]), 184 | "timestamp_ns": int(event["timestamp_ns"]) 185 | } 186 | } 187 | 188 | 189 | def trigger_reaction_to_dict(msg): 190 | event = msg.event 191 | pid, tid = get_ids(str(event["reactor_name"]), str(event["reaction_name"])) 192 | return { 193 | "name": "trigger", 194 | "cat": "Reactors", 195 | "ph": "i", 196 | "ts": float(event["timestamp_ns"]) / 1000.0, 197 | "pid": pid, 198 | "tid": tid, 199 | "s": "t", 200 | "cname": "light_memory_dump", 201 | "args": { 202 | "microstep": int(event["timestamp_microstep"]), 203 | "timestamp_ns": int(event["timestamp_ns"]) 204 | } 205 | } 206 | 207 | 208 | if(__name__ == "__main__"): 209 | main() 210 | -------------------------------------------------------------------------------- /include/reactor-cpp/action.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_ACTION_HH 10 | #define REACTOR_CPP_ACTION_HH 11 | 12 | #include "assert.hh" 13 | #include "environment.hh" 14 | #include "fwd.hh" 15 | #include "logical_time.hh" 16 | #include "reactor.hh" 17 | #include "reactor_element.hh" 18 | #include "time_barrier.hh" 19 | #include "value_ptr.hh" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace reactor { 26 | 27 | class BaseAction : public ReactorElement { 28 | private: 29 | std::set triggers_{}; 30 | std::set schedulers_{}; 31 | Duration min_delay_{0}; 32 | bool logical_{true}; 33 | bool present_{false}; 34 | 35 | protected: 36 | void register_trigger(Reaction* reaction); 37 | void register_scheduler(Reaction* reaction); 38 | 39 | virtual void setup() noexcept { present_ = true; } 40 | virtual void cleanup() noexcept { present_ = false; } 41 | 42 | /** 43 | * Use the given condition variable and lock to wait until the given tag it 44 | * safe to process. The waiting is interrupted when the condition variable is 45 | * notified (or has a spurious wakeup) and a call to the given `abort_waiting` 46 | * function returns true. or until the condition variable is notified. 47 | * 48 | * Returns false if the wait was interrupted and true otherwise. True 49 | * indicates that the tag is safe to process. 50 | */ 51 | virtual auto acquire_tag(const Tag& tag, std::unique_lock& lock, 52 | const std::function& abort_waiting) -> bool; 53 | 54 | BaseAction(const std::string& name, Reactor* container, bool logical, Duration min_delay) 55 | : ReactorElement(name, ReactorElement::Type::Action, container) 56 | , min_delay_(min_delay) 57 | , logical_(logical) {} 58 | BaseAction(const std::string& name, Environment* environment, bool logical, Duration min_delay); 59 | 60 | public: 61 | [[nodiscard]] auto triggers() const noexcept -> const auto& { return triggers_; } 62 | [[nodiscard]] auto schedulers() const noexcept -> const auto& { return schedulers_; } 63 | [[nodiscard]] auto is_logical() const noexcept -> bool { return logical_; } 64 | [[nodiscard]] auto is_physical() const noexcept -> bool { return !logical_; } 65 | [[nodiscard]] auto min_delay() const noexcept -> Duration { return min_delay_; } 66 | [[nodiscard]] auto is_present() const noexcept -> bool { return present_; } 67 | 68 | friend class Reaction; 69 | friend class Scheduler; 70 | }; 71 | 72 | template class Action : public BaseAction { 73 | private: 74 | ImmutableValuePtr value_ptr_{nullptr}; 75 | 76 | std::map> events_{}; 77 | std::mutex mutex_events_{}; 78 | 79 | protected: 80 | void setup() noexcept override; 81 | void cleanup() noexcept final; 82 | 83 | Action(const std::string& name, Reactor* container, bool logical, Duration min_delay) 84 | : BaseAction(name, container, logical, min_delay) {} 85 | Action(const std::string& name, Environment* environment, bool logical, Duration min_delay) 86 | : BaseAction(name, environment, logical, min_delay) {} 87 | 88 | public: 89 | // Normally, we should lock the mutex while moving to make this 90 | // fully thread safe. However, we rely assembly happening before 91 | // execution and hence can ignore the mutex. 92 | Action(Action&& action) noexcept 93 | : BaseAction(std::move(action)) {} 94 | auto operator=(Action&& action) noexcept -> Action& { 95 | BaseAction::operator=(std::move(action)); 96 | return *this; 97 | } 98 | 99 | Action(const Action& action) = delete; 100 | auto operator=(const Action& action) -> Action& = delete; 101 | 102 | ~Action() override = default; 103 | 104 | void startup() final {} 105 | void shutdown() final {} 106 | 107 | template void schedule(const ImmutableValuePtr& value_ptr, Dur delay = Dur::zero()); 108 | auto schedule_at(const ImmutableValuePtr& value_ptr, const Tag& tag) -> bool; 109 | 110 | template void schedule(MutableValuePtr&& value_ptr, Dur delay = Dur::zero()) { 111 | schedule(ImmutableValuePtr(std::move(value_ptr)), delay); 112 | } 113 | 114 | template void schedule(const T& value, Dur delay = Dur::zero()) { 115 | schedule(make_immutable_value(value), delay); 116 | } 117 | 118 | template void schedule(T&& value, Dur delay = Dur::zero()) { 119 | schedule(make_immutable_value(std::move(value)), delay); 120 | } 121 | 122 | // Scheduling an action with nullptr value is not permitted. 123 | template void schedule(std::nullptr_t, Dur) = delete; 124 | 125 | [[nodiscard]] auto get() const noexcept -> const ImmutableValuePtr& { return value_ptr_; } 126 | }; 127 | 128 | template <> class Action : public BaseAction { 129 | protected: 130 | Action(const std::string& name, Reactor* container, bool logical, Duration min_delay) 131 | : BaseAction(name, container, logical, min_delay) {} 132 | Action(const std::string& name, Environment* environment, bool logical, Duration min_delay) 133 | : BaseAction(name, environment, logical, min_delay) {} 134 | 135 | public: 136 | template void schedule(Dur delay = Dur::zero()); 137 | auto schedule_at(const Tag& tag) -> bool; 138 | 139 | void startup() final {} 140 | void shutdown() final {} 141 | }; 142 | 143 | template class PhysicalAction : public Action { 144 | public: 145 | PhysicalAction(const std::string& name, Reactor* container); 146 | }; 147 | 148 | template class LogicalAction : public Action { 149 | public: 150 | LogicalAction(const std::string& name, Reactor* container, Duration min_delay = Duration::zero()) 151 | : Action(name, container, true, min_delay) {} 152 | }; 153 | 154 | class Timer : public BaseAction { 155 | private: 156 | Duration offset_{0}; 157 | Duration period_{0}; 158 | 159 | void cleanup() noexcept final; 160 | 161 | public: 162 | Timer(const std::string& name, Reactor* container, Duration period = Duration::zero(), 163 | Duration offset = Duration::zero()) 164 | : BaseAction(name, container, true, Duration::zero()) 165 | , offset_(offset) 166 | , period_(period) {} 167 | 168 | void startup() final; 169 | void shutdown() override {} 170 | 171 | [[nodiscard]] auto offset() const noexcept -> const Duration& { return offset_; } 172 | 173 | [[nodiscard]] auto period() const noexcept -> const Duration& { return period_; } 174 | }; 175 | 176 | class StartupTrigger : public Timer { 177 | public: 178 | StartupTrigger(const std::string& name, Reactor* container) 179 | : Timer(name, container) {} 180 | }; 181 | 182 | class ShutdownTrigger : public Timer { 183 | public: 184 | ShutdownTrigger(const std::string& name, Reactor* container); 185 | 186 | void setup() noexcept final; 187 | void shutdown() final; 188 | }; 189 | 190 | } // namespace reactor 191 | 192 | #include "impl/action_impl.hh" 193 | 194 | #endif // REACTOR_CPP_ACTION_HH 195 | -------------------------------------------------------------------------------- /include/reactor-cpp/port.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_PORT_HH 10 | #define REACTOR_CPP_PORT_HH 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "assert.hh" 17 | #include "connection_properties.hh" 18 | #include "fwd.hh" 19 | #include "multiport.hh" 20 | #include "reactor_element.hh" 21 | #include "value_ptr.hh" 22 | 23 | namespace reactor { 24 | 25 | enum class PortType : std::uint8_t { Input, Output, Delay }; 26 | 27 | class BasePort : public ReactorElement { 28 | private: 29 | BasePort* inward_binding_{nullptr}; 30 | std::set outward_bindings_{}; 31 | PortType type_; 32 | 33 | std::set dependencies_{}; 34 | std::set triggers_{}; 35 | std::set anti_dependencies_{}; 36 | 37 | PortCallback set_callback_{nullptr}; 38 | PortCallback clean_callback_{nullptr}; 39 | 40 | protected: 41 | bool present_{false}; // NOLINT cppcoreguidelines-non-private-member-variables-in-classes 42 | 43 | BasePort(const std::string& name, PortType type, Reactor* container) 44 | : ReactorElement(name, match_port_enum(type), container) 45 | , type_(type) {} 46 | 47 | void register_dependency(Reaction* reaction, bool is_trigger) noexcept; 48 | void register_antidependency(Reaction* reaction) noexcept; 49 | virtual void cleanup() = 0; 50 | 51 | static auto match_port_enum(PortType type) noexcept -> ReactorElement::Type { 52 | switch (type) { 53 | case PortType::Output: 54 | return ReactorElement::Type::Output; 55 | case PortType::Input: 56 | return ReactorElement::Type::Input; 57 | default: 58 | return ReactorElement::Type::Port; 59 | }; 60 | } 61 | 62 | void invoke_set_callback() noexcept { 63 | if (set_callback_ != nullptr) { 64 | set_callback_(*this); 65 | } 66 | } 67 | 68 | void invoke_clean_callback() noexcept { 69 | if (clean_callback_ != nullptr) { 70 | clean_callback_(*this); 71 | } 72 | } 73 | 74 | public: 75 | void set_inward_binding(BasePort* port) noexcept { inward_binding_ = port; } 76 | void add_outward_binding(BasePort* port) noexcept { outward_bindings_.insert(port); } 77 | 78 | virtual void instantiate_connection_to(const ConnectionProperties& properties, 79 | const std::vector& downstreams) = 0; 80 | 81 | [[nodiscard]] auto is_input() const noexcept -> bool { return type_ == PortType::Input; } 82 | [[nodiscard]] auto is_output() const noexcept -> bool { return type_ == PortType::Output; } 83 | [[nodiscard]] auto is_present() const noexcept -> bool { 84 | if (has_inward_binding()) { 85 | return inward_binding()->is_present(); 86 | } 87 | return present_; 88 | }; 89 | 90 | [[nodiscard]] auto has_inward_binding() const noexcept -> bool { return inward_binding_ != nullptr; } 91 | [[nodiscard]] auto has_outward_bindings() const noexcept -> bool { return !outward_bindings_.empty(); } 92 | [[nodiscard]] auto has_dependencies() const noexcept -> bool { return !dependencies_.empty(); } 93 | [[nodiscard]] auto has_anti_dependencies() const noexcept -> bool { return !anti_dependencies_.empty(); } 94 | 95 | [[nodiscard]] auto inward_binding() const noexcept -> BasePort* { return inward_binding_; } 96 | [[nodiscard]] auto outward_bindings() const noexcept -> const auto& { return outward_bindings_; } 97 | 98 | [[nodiscard]] auto triggers() const noexcept -> const auto& { return triggers_; } 99 | [[nodiscard]] auto dependencies() const noexcept -> const auto& { return dependencies_; } 100 | [[nodiscard]] auto anti_dependencies() const noexcept -> const auto& { return anti_dependencies_; } 101 | [[nodiscard]] auto port_type() const noexcept -> PortType { return type_; } 102 | 103 | void register_set_callback(const PortCallback& callback); 104 | void register_clean_callback(const PortCallback& callback); 105 | 106 | friend class Reaction; 107 | friend class Scheduler; 108 | }; 109 | 110 | template class Port : public BasePort { 111 | private: 112 | ImmutableValuePtr value_ptr_{nullptr}; 113 | 114 | void cleanup() noexcept final { 115 | value_ptr_ = nullptr; 116 | present_ = false; 117 | invoke_clean_callback(); 118 | } 119 | 120 | public: 121 | using value_type = T; 122 | 123 | Port(const std::string& name, PortType type, Reactor* container) 124 | : BasePort(name, type, container) {} 125 | 126 | void instantiate_connection_to(const ConnectionProperties& properties, 127 | const std::vector& downstream) override; 128 | [[nodiscard]] auto typed_inward_binding() const noexcept -> Port*; 129 | [[nodiscard]] auto typed_outward_bindings() const noexcept -> const std::set*>&; 130 | 131 | virtual void set(const ImmutableValuePtr& value_ptr); 132 | void set(MutableValuePtr&& value_ptr) { set(ImmutableValuePtr(std::move(value_ptr))); } 133 | void set(const T& value) { set(make_immutable_value(value)); } 134 | void set(T&& value) { set(make_immutable_value(std::move(value))); } 135 | 136 | // Setting a port to nullptr is not permitted. We use enable_if to only delete 137 | // set() if it is actually called with nullptr. Without enable_if set(0) would 138 | // be ambiguous as 0 can be implicitly casted to nullptr_t. 139 | template >> void set(V) = delete; 140 | 141 | void startup() final {} 142 | void shutdown() final {} 143 | 144 | auto get() const noexcept -> const ImmutableValuePtr&; 145 | }; 146 | 147 | template <> class Port : public BasePort { 148 | private: 149 | void cleanup() noexcept final { 150 | present_ = false; 151 | invoke_clean_callback(); 152 | } 153 | 154 | public: 155 | using value_type = void; 156 | 157 | Port(const std::string& name, PortType type, Reactor* container) 158 | : BasePort(name, type, container) {} 159 | 160 | void instantiate_connection_to(const ConnectionProperties& properties, 161 | const std::vector& downstream) override; 162 | [[nodiscard]] auto typed_inward_binding() const noexcept -> Port*; 163 | [[nodiscard]] auto typed_outward_bindings() const noexcept -> const std::set*>&; 164 | 165 | void set(); 166 | 167 | void startup() final {} 168 | void shutdown() final {} 169 | }; 170 | 171 | template class Input : public Port { // NOLINT(cppcoreguidelines-special-member-functions) 172 | public: 173 | Input(const std::string& name, Reactor* container) 174 | : Port(name, PortType::Input, container) {} 175 | 176 | Input(Input&&) noexcept = default; 177 | }; 178 | 179 | template class Output : public Port { // NOLINT(cppcoreguidelines-special-member-functions) 180 | public: 181 | Output(const std::string& name, Reactor* container) 182 | : Port(name, PortType::Output, container) {} 183 | 184 | Output(Output&&) noexcept = default; 185 | }; 186 | 187 | } // namespace reactor 188 | 189 | #include "impl/port_impl.hh" 190 | 191 | #endif // REACTOR_CPP_PORT_HH 192 | -------------------------------------------------------------------------------- /include/reactor-cpp/scheduler.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #ifndef REACTOR_CPP_SCHEDULER_HH 10 | #define REACTOR_CPP_SCHEDULER_HH 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "assert.hh" 23 | #include "fwd.hh" 24 | #include "logical_time.hh" 25 | #include "reactor-cpp/logging.hh" 26 | #include "reactor-cpp/time.hh" 27 | #include "safe_vector.hh" 28 | #include "semaphore.hh" 29 | #include "time.hh" 30 | 31 | namespace reactor { 32 | 33 | using ReleaseTagCallback = std::function; 34 | 35 | // forward declarations 36 | class Scheduler; 37 | class Worker; 38 | 39 | class Worker { 40 | private: 41 | Scheduler* scheduler_; 42 | unsigned int identity_{0}; 43 | std::thread thread_{}; 44 | log::NamedLogger log_; 45 | 46 | // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables) 47 | static thread_local const Worker* current_worker; 48 | 49 | void work() const; 50 | void execute_reaction(Reaction* reaction) const; 51 | 52 | public: 53 | Worker(Scheduler* scheduler, unsigned int identity, const std::string& name) 54 | : scheduler_{scheduler} 55 | , identity_{identity} 56 | , log_(name) {} 57 | Worker(Worker&& worker) = default; 58 | Worker(const Worker& worker) = delete; 59 | ~Worker() = default; 60 | 61 | auto operator=(const Worker&) -> Worker& = delete; 62 | auto operator=(Worker&&) -> Worker& = delete; 63 | 64 | void start_thread() { thread_ = std::thread(&Worker::work, this); } 65 | void join_thread() { thread_.join(); } 66 | 67 | static auto current_worker_id() -> unsigned { return current_worker->identity_; } 68 | }; 69 | 70 | class ReadyQueue { 71 | private: 72 | std::vector queue_{}; 73 | std::atomic size_{0}; 74 | Semaphore sem_{0}; 75 | std::ptrdiff_t waiting_workers_{0}; 76 | const std::ptrdiff_t num_workers_{}; 77 | log::NamedLogger& log_; 78 | 79 | public: 80 | explicit ReadyQueue(log::NamedLogger& log, std::ptrdiff_t num_workers) 81 | : num_workers_(num_workers) 82 | , log_(log) {} 83 | 84 | /** 85 | * Retrieve a ready reaction from the queue. 86 | * 87 | * This method may be called concurrently. In case the queue is empty, the 88 | * method blocks and waits until a ready reaction becomes available. 89 | */ 90 | auto pop() -> Reaction*; 91 | 92 | /** 93 | * Fill the queue up with ready reactions. 94 | * 95 | * This method assumes that the internal queue is empty. It moves all 96 | * reactions from the provided `ready_reactions` vector to the internal 97 | * queue, leaving `ready_reactions` empty. 98 | * 99 | * Note that this method is not thread-safe. The caller needs to ensure that 100 | * no other thread will try to read from the queue during this operation. 101 | */ 102 | void fill_up(std::vector& ready_reactions); 103 | }; 104 | 105 | using ActionList = SafeVector; 106 | using ActionListPtr = std::unique_ptr; 107 | 108 | class EventQueue { 109 | private: 110 | std::shared_mutex mutex_{}; 111 | std::map event_queue_{}; 112 | /// stores the actions triggered at the current tag 113 | ActionListPtr triggered_actions_{nullptr}; 114 | 115 | std::vector action_list_pool_{}; 116 | static constexpr std::size_t action_list_pool_increment_{10}; 117 | 118 | void fill_action_list_pool(); 119 | 120 | public: 121 | EventQueue() { fill_action_list_pool(); } 122 | 123 | [[nodiscard]] auto empty() const -> bool { return event_queue_.empty(); } 124 | [[nodiscard]] auto next_tag() const -> Tag; 125 | 126 | auto insert_event_at(const Tag& tag) -> const ActionListPtr&; 127 | 128 | // should only be called while holding the scheduler mutex 129 | auto extract_next_event() -> ActionListPtr; 130 | 131 | // should only be called while holding the scheduler mutex 132 | void return_action_list(ActionListPtr&& action_list); 133 | 134 | // should only be called while holding the scheduler mutex 135 | void discard_events_until_tag(const Tag& tag); 136 | }; 137 | 138 | class Scheduler { 139 | private: 140 | const bool using_workers_; 141 | LogicalTime logical_time_{}; 142 | 143 | Environment* environment_{nullptr}; 144 | std::vector workers_{}; 145 | log::NamedLogger log_; 146 | 147 | std::mutex scheduling_mutex_{}; 148 | std::condition_variable cv_schedule_{}; 149 | 150 | EventQueue event_queue_; 151 | /// stores the actions triggered at the current tag 152 | ActionListPtr triggered_actions_{nullptr}; 153 | 154 | std::vector> set_ports_{}; 155 | std::vector> triggered_reactions_{}; 156 | std::vector> reaction_queue_{}; 157 | unsigned int reaction_queue_pos_{std::numeric_limits::max()}; 158 | 159 | ReadyQueue ready_queue_; 160 | std::atomic reactions_to_process_{0}; 161 | 162 | std::atomic stop_{false}; 163 | bool continue_execution_{true}; 164 | 165 | std::vector release_tag_callbacks_{}; 166 | void release_current_tag(); 167 | 168 | void cleanup_after_tag(); 169 | 170 | void schedule() noexcept; 171 | auto schedule_ready_reactions() -> bool; 172 | void next(); 173 | void terminate_all_workers(); 174 | void set_port_helper(BasePort* port); 175 | 176 | void advance_logical_time_to(const Tag& tag); 177 | 178 | public: 179 | explicit Scheduler(Environment* env); 180 | ~Scheduler(); 181 | Scheduler(const Scheduler&) = delete; 182 | Scheduler(Scheduler&&) = delete; 183 | auto operator=(const Scheduler&) -> Scheduler& = delete; 184 | auto operator=(Scheduler&&) -> Scheduler& = delete; 185 | 186 | void schedule_sync(BaseAction* action, const Tag& tag); 187 | auto schedule_async(BaseAction* action, const Duration& delay) -> Tag; 188 | auto schedule_async_at(BaseAction* action, const Tag& tag) -> bool; 189 | auto schedule_empty_async_at(const Tag& tag) -> bool; 190 | 191 | auto lock() noexcept -> std::unique_lock { return std::unique_lock(scheduling_mutex_); } 192 | void notify() noexcept { cv_schedule_.notify_one(); } 193 | void wait(std::unique_lock& lock, const std::function& predicate) noexcept { 194 | reactor_assert(lock.owns_lock()); 195 | cv_schedule_.wait(lock, predicate); 196 | }; 197 | auto wait_until(std::unique_lock& lock, TimePoint time_point, 198 | const std::function& predicate) noexcept -> bool { 199 | reactor_assert(lock.owns_lock()); 200 | return cv_schedule_.wait_until(lock, time_point, predicate); 201 | }; 202 | 203 | void set_port(BasePort* port); 204 | 205 | [[nodiscard]] auto logical_time() const noexcept -> const auto& { return logical_time_; } 206 | 207 | void start(); 208 | void stop(); 209 | 210 | void register_release_tag_callback(const ReleaseTagCallback& callback); 211 | 212 | friend Worker; 213 | }; 214 | 215 | } // namespace reactor 216 | 217 | #endif // REACTOR_CPP_SCHEDULER_HH 218 | -------------------------------------------------------------------------------- /include/reactor-cpp/connection.hh: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Tassilo Tanneberger 7 | * Christian Menard 8 | */ 9 | 10 | #ifndef REACTOR_CPP_CONNECTION_HH 11 | #define REACTOR_CPP_CONNECTION_HH 12 | 13 | #include "action.hh" 14 | #include "assert.hh" 15 | #include "environment.hh" 16 | #include "fwd.hh" 17 | #include "logical_time.hh" 18 | #include "port.hh" 19 | #include "reaction.hh" 20 | #include "reactor.hh" 21 | #include "time.hh" 22 | #include "time_barrier.hh" 23 | 24 | namespace reactor { 25 | 26 | template class Connection : public Action { 27 | private: 28 | Port* upstream_port_{nullptr}; 29 | std::set*> downstream_ports_{}; 30 | 31 | protected: 32 | Connection(const std::string& name, Reactor* container, bool is_logical, Duration min_delay) 33 | : Action(name, container, is_logical, min_delay) {} 34 | Connection(const std::string& name, Environment* environment, bool is_logical, Duration min_delay) 35 | : Action(name, environment, is_logical, min_delay) {} 36 | 37 | [[nodiscard]] auto downstream_ports() -> auto& { return downstream_ports_; } 38 | [[nodiscard]] auto downstream_ports() const -> const auto& { return downstream_ports_; } 39 | [[nodiscard]] auto upstream_port() -> auto* { return upstream_port_; } 40 | [[nodiscard]] auto upstream_port() const -> const auto* { return upstream_port_; } 41 | 42 | public: 43 | virtual auto upstream_set_callback() noexcept -> PortCallback = 0; 44 | virtual void bind_upstream_port(Port* port) { 45 | reactor_assert(upstream_port_ == nullptr); 46 | upstream_port_ = port; 47 | } 48 | 49 | virtual void bind_downstream_ports(const std::vector& ports) { 50 | // with C++23 we can use insert_rage here 51 | for ([[maybe_unused]] auto* port : ports) { 52 | this->downstream_ports_.insert(static_cast*>(port)); 53 | } 54 | } 55 | virtual void bind_downstream_port(Port* port) { 56 | [[maybe_unused]] bool result = this->downstream_ports_.insert(port).second; 57 | reactor_assert(result); 58 | }; 59 | }; 60 | 61 | template class BaseDelayedConnection : public Connection { 62 | protected: 63 | BaseDelayedConnection(const std::string& name, Reactor* container, bool is_logical, Duration delay) 64 | : Connection(name, container, is_logical, delay) {} 65 | BaseDelayedConnection(const std::string& name, Environment* environment, bool is_logical, Duration delay) 66 | : Connection(name, environment, is_logical, delay) {} 67 | 68 | auto upstream_set_callback() noexcept -> PortCallback override { 69 | return [this](const BasePort& port) { 70 | // We know that port must be of type Port 71 | auto& typed_port = reinterpret_cast&>(port); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) 72 | if constexpr (std::is_same::value) { 73 | this->schedule(); 74 | } else { 75 | this->schedule(std::move(typed_port.get())); 76 | } 77 | }; 78 | } 79 | 80 | public: 81 | void setup() noexcept override { 82 | Action::setup(); 83 | 84 | if constexpr (std::is_same::value) { 85 | for (auto port : this->downstream_ports()) { 86 | port->set(); 87 | } 88 | } else { 89 | for (auto port : this->downstream_ports()) { 90 | port->set(std::move(this->get())); 91 | } 92 | } 93 | } 94 | }; 95 | 96 | template class DelayedConnection : public BaseDelayedConnection { 97 | public: 98 | DelayedConnection(const std::string& name, Reactor* container, Duration delay) 99 | : BaseDelayedConnection(name, container, true, delay) {} 100 | }; 101 | 102 | template class PhysicalConnection : public BaseDelayedConnection { 103 | public: 104 | PhysicalConnection(const std::string& name, Reactor* container, Duration delay) 105 | : BaseDelayedConnection(name, container, false, delay) {} 106 | }; 107 | 108 | template class EnclaveConnection : public BaseDelayedConnection { 109 | private: 110 | LogicalTimeBarrier logical_time_barrier_; 111 | 112 | protected: 113 | log::NamedLogger log_; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) 114 | 115 | EnclaveConnection(const std::string& name, Environment* enclave, const Duration& delay) 116 | : BaseDelayedConnection(name, enclave, false, delay) 117 | , logical_time_barrier_(enclave->scheduler()) 118 | , log_{this->fqn()} {} 119 | 120 | public: 121 | EnclaveConnection(const std::string& name, Environment* enclave) 122 | : BaseDelayedConnection(name, enclave, false, Duration::zero()) 123 | , logical_time_barrier_(enclave->scheduler()) 124 | , log_{this->fqn()} {}; 125 | 126 | auto upstream_set_callback() noexcept -> PortCallback override { 127 | return [this](const BasePort& port) { 128 | // We know that port must be of type Port 129 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 130 | auto& typed_port = reinterpret_cast&>(port); 131 | const auto* scheduler = port.environment()->scheduler(); 132 | // This callback will be called from a reaction executing in the context 133 | // of the upstream port. Hence, we can retrieve the current tag directly 134 | // without locking. 135 | auto tag = Tag::from_logical_time(scheduler->logical_time()); 136 | [[maybe_unused]] bool result{false}; 137 | if constexpr (std::is_same::value) { 138 | result = this->schedule_at(tag); 139 | } else { 140 | result = this->schedule_at(std::move(typed_port.get()), tag); 141 | } 142 | reactor_assert(result); 143 | }; 144 | } 145 | 146 | auto acquire_tag(const Tag& tag, std::unique_lock& lock, 147 | const std::function& abort_waiting) -> bool override { 148 | reactor_assert(lock.owns_lock()); 149 | log_.debug() << "downstream tries to acquire tag " << tag; 150 | 151 | if (this->upstream_port() == nullptr) { 152 | return true; 153 | } 154 | 155 | if (logical_time_barrier_.try_acquire_tag(tag)) { 156 | return true; 157 | } 158 | 159 | // Insert an empty event into the upstream event queue. This ensures that we 160 | // will get notified and woken up as soon as the tag becomes safe to process. 161 | // It is important to unlock the mutex here. Otherwise we could enter a deadlock as 162 | // scheduling the upstream event also requires holding the upstream mutex. 163 | lock.unlock(); 164 | bool result = this->upstream_port()->environment()->scheduler()->schedule_empty_async_at(tag); 165 | lock.lock(); 166 | 167 | // If inserting the empty event was not successful, then this is because the upstream 168 | // scheduler already processes a later event. In this case, it is safe to assume that 169 | // the tag is acquired. 170 | if (!result) { 171 | return true; 172 | } 173 | 174 | // Wait until we receive a release_tag message from upstream 175 | return logical_time_barrier_.acquire_tag(tag, lock, abort_waiting); 176 | } 177 | 178 | void bind_upstream_port(Port* port) override { 179 | Connection::bind_upstream_port(port); 180 | port->environment()->scheduler()->register_release_tag_callback([this](const LogicalTime& tag) { 181 | logical_time_barrier_.release_tag(tag); 182 | log_.debug() << "upstream released tag " << tag; 183 | this->environment()->scheduler()->notify(); 184 | }); 185 | } 186 | }; 187 | 188 | template class DelayedEnclaveConnection : public EnclaveConnection { 189 | public: 190 | DelayedEnclaveConnection(const std::string& name, Environment* enclave, Duration delay) 191 | : EnclaveConnection(name, enclave, delay) {} 192 | 193 | auto upstream_set_callback() noexcept -> PortCallback override { 194 | return [&](const BasePort& port) { 195 | // This callback will be called from a reaction executing in the context 196 | // of the upstream port. Hence, we can retrieve the current tag directly 197 | // without locking. 198 | 199 | if constexpr (std::is_same::value) { 200 | this->schedule_at( 201 | Tag::from_logical_time(port.environment()->scheduler()->logical_time()).delay(this->min_delay())); 202 | } else { 203 | // We know that port must be of type Port 204 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 205 | auto& typed_port = reinterpret_cast&>(port); 206 | this->schedule_at( 207 | std::move(typed_port.get()), 208 | Tag::from_logical_time(port.environment()->scheduler()->logical_time()).delay(this->min_delay())); 209 | } 210 | }; 211 | } 212 | 213 | auto acquire_tag(const Tag& tag, std::unique_lock& lock, 214 | const std::function& abort_waiting) -> bool override { 215 | // Since this is a delayed connection, we can go back in time and need to 216 | // acquire the latest upstream tag that can create an event at the given 217 | // tag. We also need to consider that given a delay d and a tag g=(t, n), 218 | // for any value of n, g + d = (t, 0). Hence, we need to quire a tag with 219 | // the highest possible microstep value. 220 | auto upstream_tag = tag.subtract(this->min_delay()); 221 | return EnclaveConnection::acquire_tag(upstream_tag, lock, abort_waiting); 222 | } 223 | }; 224 | 225 | template class PhysicalEnclaveConnection : public EnclaveConnection { 226 | public: 227 | PhysicalEnclaveConnection(const std::string& name, Environment* enclave) 228 | : EnclaveConnection(name, enclave) {} 229 | 230 | auto upstream_set_callback() noexcept -> PortCallback override { 231 | return [this](const BasePort& port) { 232 | // We know that port must be of type Port 233 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 234 | auto& typed_port = reinterpret_cast&>(port); 235 | if constexpr (std::is_same::value) { 236 | this->schedule(); 237 | } else { 238 | this->schedule(std::move(typed_port.get())); 239 | } 240 | }; 241 | } 242 | 243 | auto acquire_tag(const Tag& tag, std::unique_lock& lock, 244 | const std::function& abort_waiting) -> bool override { 245 | this->log_.debug() << "downstream tries to acquire tag " << tag; 246 | return PhysicalTimeBarrier::acquire_tag(tag, lock, this->environment()->scheduler(), abort_waiting); 247 | } 248 | 249 | void bind_upstream_port(Port* port) override { Connection::bind_upstream_port(port); } 250 | }; 251 | 252 | } // namespace reactor 253 | 254 | #endif // REACTOR_CPP_CONNECTION_HH 255 | -------------------------------------------------------------------------------- /lib/environment.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 TU Dresden 3 | * All rights reserved. 4 | * 5 | * Authors: 6 | * Christian Menard 7 | */ 8 | 9 | #include "reactor-cpp/environment.hh" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "reactor-cpp/action.hh" 18 | #include "reactor-cpp/assert.hh" 19 | #include "reactor-cpp/logging.hh" 20 | #include "reactor-cpp/port.hh" 21 | #include "reactor-cpp/reaction.hh" 22 | #include "reactor-cpp/statistics.hh" 23 | #include "reactor-cpp/time.hh" 24 | 25 | namespace reactor { 26 | 27 | Environment::Environment(unsigned int num_workers, bool fast_fwd_execution, const Duration& timeout) 28 | : log_("Environment") 29 | , num_workers_(num_workers) 30 | , fast_fwd_execution_(fast_fwd_execution) 31 | , top_environment_(this) 32 | , scheduler_(this) 33 | , timeout_(timeout) {} 34 | 35 | Environment::Environment(const std::string& name, Environment* containing_environment) 36 | : name_(name) 37 | , log_("Environment " + name) 38 | , num_workers_(containing_environment->num_workers_) 39 | , fast_fwd_execution_(containing_environment->fast_fwd_execution_) 40 | , containing_environment_(containing_environment) 41 | , top_environment_(containing_environment_->top_environment_) 42 | , scheduler_(this) 43 | , timeout_(containing_environment->timeout()) { 44 | [[maybe_unused]] bool result = containing_environment->contained_environments_.insert(this).second; 45 | reactor_assert(result); 46 | } 47 | 48 | void Environment::register_reactor(Reactor* reactor) { 49 | reactor_assert(reactor != nullptr); 50 | validate(this->phase() == Phase::Construction, "Reactors may only be registered during construction phase!"); 51 | validate(reactor->is_top_level(), "The environment may only contain top level reactors!"); 52 | [[maybe_unused]] bool result = top_level_reactors_.insert(reactor).second; 53 | reactor_assert(result); 54 | } 55 | 56 | void Environment::register_input_action(BaseAction* action) { 57 | reactor_assert(action != nullptr); 58 | validate(this->phase() == Phase::Construction || this->phase() == Phase::Assembly, 59 | "Input actions may only be registered during construction or assembly phase!"); 60 | [[maybe_unused]] bool result = input_actions_.insert(action).second; 61 | reactor_assert(result); 62 | run_forever_ = true; 63 | } 64 | 65 | void Environment::optimize() { 66 | // no optimizations 67 | optimized_graph_ = graph_; 68 | } 69 | 70 | void recursive_assemble(Reactor* container) { 71 | container->assemble(); 72 | for (auto* reactor : container->reactors()) { 73 | recursive_assemble(reactor); 74 | } 75 | } 76 | 77 | void Environment::assemble() { // NOLINT(readability-function-cognitive-complexity) 78 | phase_ = Phase::Assembly; 79 | 80 | // constructing all the reactors 81 | // this mainly tell the reactors that they should connect their ports and actions not ports and ports 82 | 83 | log::Debug() << "start assembly of reactors"; 84 | for (auto* reactor : top_level_reactors_) { 85 | recursive_assemble(reactor); 86 | } 87 | 88 | // this assembles all the contained environments aka enclaves 89 | for (auto* env : contained_environments_) { 90 | env->assemble(); 91 | } 92 | 93 | // If this is the top level environment, then instantiate all connections. 94 | if (top_environment_ == nullptr || top_environment_ == this) { 95 | log::Debug() << "start optimization on port graph"; 96 | this->optimize(); 97 | 98 | log::Debug() << "instantiating port graph declaration"; 99 | log::Debug() << "graph: "; 100 | log::Debug() << optimized_graph_; 101 | 102 | auto graph = optimized_graph_.get_edges(); 103 | // this generates the port graph 104 | for (auto const& [source, sinks] : graph) { 105 | 106 | auto* source_port = source.first; 107 | auto properties = source.second; 108 | 109 | if (properties.type_ == ConnectionType::Normal) { 110 | for (auto* const destination_port : sinks) { 111 | destination_port->set_inward_binding(source_port); 112 | source_port->add_outward_binding(destination_port); 113 | log::Debug() << "from: " << source_port->fqn() << "(" << source_port << ")" 114 | << " --> to: " << destination_port->fqn() << "(" << destination_port << ")"; 115 | } 116 | } else { 117 | if (properties.type_ == ConnectionType::Enclaved || properties.type_ == ConnectionType::PhysicalEnclaved || 118 | properties.type_ == ConnectionType::DelayedEnclaved) { 119 | // here we need to bundle the downstream ports by their enclave 120 | std::map> collector{}; 121 | 122 | for (auto* downstream : sinks) { 123 | if (collector.find(downstream->environment()) == std::end(collector)) { 124 | // didn't find the enviroment in collector yet 125 | collector.insert(std::make_pair(downstream->environment(), std::vector{downstream})); 126 | } else { 127 | // environment already contained in collector 128 | collector[downstream->environment()].push_back(downstream); 129 | } 130 | } 131 | for (auto& [env, sinks_same_env] : collector) { 132 | source_port->instantiate_connection_to(properties, sinks_same_env); 133 | 134 | log::Debug() << "from: " << source_port->container()->fqn() << " |-> to: " << sinks_same_env.size() 135 | << " objects"; 136 | } 137 | } else { 138 | source_port->instantiate_connection_to(properties, sinks); 139 | 140 | log::Debug() << "from: " << source_port->container()->fqn() << " |-> to: " << sinks.size() << " objects"; 141 | } 142 | } 143 | } 144 | } 145 | } 146 | 147 | void Environment::build_dependency_graph(Reactor* reactor) { 148 | // obtain dependencies from each contained reactor 149 | for (auto* sub_reactor : reactor->reactors()) { 150 | build_dependency_graph(sub_reactor); 151 | } 152 | // get reactions_ from this reactor; also order reactions_ by their priority 153 | std::map priority_map; 154 | for (auto* reaction : reactor->reactions()) { 155 | reactions_.insert(reaction); 156 | auto result = priority_map.emplace(reaction->priority(), reaction); 157 | validate(result.second, "priorities must be unique for all reactions_ of the same reactor"); 158 | } 159 | 160 | // connect all reactions_ this reaction depends on 161 | for (auto* reaction : reactor->reactions()) { 162 | for (auto* dependency : reaction->dependencies()) { 163 | auto* source = dependency; 164 | while (source->has_inward_binding()) { 165 | source = source->inward_binding(); 166 | } 167 | for (auto* antidependency : source->anti_dependencies()) { 168 | dependencies_.emplace_back(reaction, antidependency); 169 | } 170 | } 171 | } 172 | 173 | // connect reactions_ by priority 174 | if (priority_map.size() > 1) { 175 | auto iterator = priority_map.begin(); 176 | auto next = std::next(iterator); 177 | while (next != priority_map.end()) { 178 | dependencies_.emplace_back(next->second, iterator->second); 179 | iterator++; 180 | next = std::next(iterator); 181 | } 182 | } 183 | } 184 | 185 | void Environment::sync_shutdown() { 186 | { 187 | std::lock_guard lock{shutdown_mutex_}; 188 | 189 | if (phase_ >= Phase::Shutdown) { 190 | // sync_shutdown() was already called -> abort 191 | return; 192 | } 193 | 194 | validate(phase_ == Phase::Execution, "sync_shutdown() may only be called during execution phase!"); 195 | phase_ = Phase::Shutdown; 196 | } 197 | 198 | // the following will only be executed once 199 | log_.debug() << "Terminating the execution"; 200 | 201 | for (auto* reactor : top_level_reactors_) { 202 | reactor->shutdown(); 203 | } 204 | 205 | phase_ = Phase::Deconstruction; 206 | scheduler_.stop(); 207 | } 208 | 209 | void Environment::async_shutdown() { 210 | [[maybe_unused]] auto lock_guard = scheduler_.lock(); 211 | sync_shutdown(); 212 | } 213 | 214 | auto dot_name([[maybe_unused]] ReactorElement* reactor_element) -> std::string { 215 | std::string fqn{reactor_element->fqn()}; 216 | std::replace(fqn.begin(), fqn.end(), '.', '_'); 217 | return fqn; 218 | } 219 | 220 | void Environment::export_dependency_graph(const std::string& path) { 221 | std::ofstream dot; 222 | dot.open(path); 223 | 224 | // sort all reactions_ by their index 225 | std::map> reactions_by_index; 226 | for (auto* reaction : reactions_) { 227 | reactions_by_index[reaction->index()].push_back(reaction); 228 | } 229 | 230 | // start the graph 231 | dot << "digraph {\n"; 232 | dot << "rankdir=LR;\n"; 233 | 234 | // place reactions_ of the same index in the same subgraph 235 | for (auto& index_reactions : reactions_by_index) { 236 | dot << "subgraph {\n"; 237 | dot << "rank=same;\n"; 238 | for (auto* reaction : index_reactions.second) { 239 | dot << dot_name(reaction) << " [label=\"" << reaction->fqn() << "\"];\n"; 240 | } 241 | dot << "}\n"; 242 | } 243 | 244 | // establish an order between subgraphs 245 | Reaction* reaction_from_last_index = nullptr; 246 | for (auto& index_reactions : reactions_by_index) { 247 | Reaction* reaction_from_this_index = index_reactions.second.front(); 248 | if (reaction_from_last_index != nullptr) { 249 | dot << dot_name(reaction_from_last_index) << " -> " << dot_name(reaction_from_this_index) << " [style=invis];\n"; 250 | } 251 | reaction_from_last_index = reaction_from_this_index; 252 | } 253 | 254 | // add all the dependencies 255 | for (auto dependency : dependencies_) { 256 | dot << dot_name(dependency.first) << " -> " << dot_name(dependency.second) << '\n'; 257 | } 258 | dot << "}\n"; 259 | 260 | dot.close(); 261 | 262 | log_.info() << "Reaction graph was written to " << path; 263 | } 264 | 265 | void Environment::calculate_indexes() { 266 | // build the graph 267 | std::map> graph; 268 | for (auto* reaction : reactions_) { 269 | graph[reaction]; 270 | } 271 | for (auto dependencies : dependencies_) { 272 | graph[dependencies.first].insert(dependencies.second); 273 | } 274 | 275 | log_.debug() << "Reactions sorted by index:"; 276 | unsigned int index = 0; 277 | while (!graph.empty()) { 278 | // find nodes with degree zero and assign index 279 | std::set degree_zero; 280 | for (auto& edge : graph) { 281 | if (edge.second.empty()) { 282 | edge.first->set_index(index); 283 | degree_zero.insert(edge.first); 284 | } 285 | } 286 | 287 | if (degree_zero.empty()) { 288 | export_dependency_graph("/tmp/reactor_dependency_graph.dot"); 289 | throw ValidationError("There is a loop in the dependency graph. Graph was written to " 290 | "/tmp/reactor_dependency_graph.dot"); 291 | } 292 | 293 | auto dbg = log_.debug(); 294 | dbg << index << ": "; 295 | for (auto* reaction : degree_zero) { 296 | dbg << reaction->fqn() << ", "; 297 | } 298 | 299 | // reduce graph 300 | for (auto* reaction : degree_zero) { 301 | graph.erase(reaction); 302 | } 303 | for (auto& edge : graph) { 304 | for (auto* reaction : degree_zero) { 305 | edge.second.erase(reaction); 306 | } 307 | } 308 | 309 | index++; 310 | } 311 | 312 | max_reaction_index_ = index - 1; 313 | } 314 | 315 | auto Environment::startup() -> std::thread { 316 | validate(this == top_environment_, "startup() may only be called on the top environment"); 317 | return startup(get_physical_time()); 318 | } 319 | 320 | auto Environment::startup(const TimePoint& start_time) -> std::thread { 321 | validate(this->phase() == Phase::Assembly, "startup() may only be called during assembly phase!"); 322 | 323 | log::Debug() << "Building the Dependency-Graph"; 324 | for (auto* reactor : top_level_reactors_) { 325 | build_dependency_graph(reactor); 326 | } 327 | 328 | calculate_indexes(); 329 | 330 | log_.debug() << "Starting the execution"; 331 | phase_ = Phase::Startup; 332 | 333 | this->start_tag_ = Tag::from_physical_time(start_time); 334 | if (this->timeout_ == Duration::max()) { 335 | this->timeout_tag_ = Tag::max(); 336 | } else if (this->timeout_ == Duration::zero()) { 337 | this->timeout_tag_ = this->start_tag_; 338 | } else { 339 | this->timeout_tag_ = this->start_tag_.delay(this->timeout_); 340 | } 341 | 342 | // start up initialize all reactors 343 | for (auto* reactor : top_level_reactors_) { 344 | reactor->startup(); 345 | } 346 | 347 | // start processing events 348 | phase_ = Phase::Execution; 349 | 350 | return std::thread([this, start_time]() { 351 | std::vector threads; 352 | threads.reserve(contained_environments_.size()); 353 | // startup all contained environments recursively 354 | for (auto* env : contained_environments_) { 355 | threads.emplace_back(env->startup(start_time)); 356 | } 357 | // start the local scheduler and wait until it returns 358 | this->scheduler_.start(); 359 | // then join all the created threads 360 | for (auto& thread : threads) { 361 | thread.join(); 362 | } 363 | 364 | // If this is the top level environment, then print some execution statistics 365 | if (this->containing_environment_ == nullptr) { 366 | Statistics::print(); 367 | } 368 | }); 369 | } 370 | 371 | } // namespace reactor 372 | --------------------------------------------------------------------------------