├── .clang-format ├── .gitignore ├── include └── fschuetz04 │ ├── simcpp20.hpp │ └── simcpp20 │ ├── CMakeLists.txt │ ├── awaitable.hpp │ ├── promise_type.hpp │ ├── resource.hpp │ ├── value_event.hpp │ ├── store.hpp │ ├── process.hpp │ ├── value_process.hpp │ ├── event.hpp │ └── simulation.hpp ├── examples ├── value_event.cpp ├── any_of.cpp ├── clocks.cpp ├── value_process.cpp ├── all_of.cpp ├── clocks_units.cpp ├── CMakeLists.txt ├── store.cpp ├── ping_pong.cpp ├── any_of_process.cpp ├── carwash.cpp ├── bank_renege.cpp └── machine_shop.cpp ├── .github └── workflows │ ├── windows.yml │ └── linux.yml ├── tests ├── CMakeLists.txt ├── store_tests.cpp ├── awaitable_tests.cpp ├── resource_tests.cpp └── tests.cpp ├── CMakeSettings.json ├── CMakeLists.txt ├── LICENSE └── README.md /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | AllowShortFunctionsOnASingleLine: Inline 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.cache 2 | /.vs 3 | /.vscode 4 | /build 5 | /debug 6 | /out 7 | /release 8 | -------------------------------------------------------------------------------- /include/fschuetz04/simcpp20.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // IWYU pragma: begin_exports 4 | #include "simcpp20/awaitable.hpp" 5 | #include "simcpp20/event.hpp" 6 | #include "simcpp20/process.hpp" 7 | #include "simcpp20/resource.hpp" 8 | #include "simcpp20/simulation.hpp" 9 | #include "simcpp20/store.hpp" 10 | #include "simcpp20/value_event.hpp" 11 | #include "simcpp20/value_process.hpp" 12 | // IWYU pragma: end_exports 13 | -------------------------------------------------------------------------------- /examples/value_event.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | 5 | simcpp20::process<> consumer(simcpp20::simulation<> &sim, 6 | simcpp20::value_event ev) { 7 | auto val = co_await ev; 8 | printf("[%.0f] val = %d\n", sim.now(), val); 9 | } 10 | 11 | int main() { 12 | simcpp20::simulation<> sim; 13 | auto ev = sim.timeout(1, 42); 14 | consumer(sim, ev); 15 | sim.run(); 16 | } 17 | -------------------------------------------------------------------------------- /examples/any_of.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | 5 | simcpp20::process<> process(simcpp20::simulation<> &sim) { 6 | printf("[%.0f] 1\n", sim.now()); 7 | 8 | co_await (sim.timeout(1) | sim.timeout(2)); 9 | printf("[%.0f] 2\n", sim.now()); 10 | 11 | co_await (sim.timeout(1) | sim.event()); 12 | printf("[%.0f] 3\n", sim.now()); 13 | } 14 | 15 | int main() { 16 | simcpp20::simulation<> sim; 17 | process(sim); 18 | sim.run(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/clocks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | 5 | simcpp20::process<> clock_proc(simcpp20::simulation<> &sim, char const *name, 6 | double delay) { 7 | while (true) { 8 | printf("[%.0f] %s\n", sim.now(), name); 9 | co_await sim.timeout(delay); 10 | } 11 | } 12 | 13 | int main() { 14 | simcpp20::simulation<> sim; 15 | clock_proc(sim, "slow", 2); 16 | clock_proc(sim, "fast", 1); 17 | sim.run_until(5); 18 | } 19 | -------------------------------------------------------------------------------- /examples/value_process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | 5 | simcpp20::value_process producer(simcpp20::simulation<> &sim) { 6 | co_await sim.timeout(1); 7 | co_return 42; 8 | } 9 | 10 | simcpp20::process<> consumer(simcpp20::simulation<> &sim) { 11 | auto val = co_await producer(sim); 12 | printf("[%.0f] val = %d\n", sim.now(), val); 13 | } 14 | 15 | int main() { 16 | simcpp20::simulation<> sim; 17 | consumer(sim); 18 | sim.run(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/all_of.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | 5 | simcpp20::process<> process(simcpp20::simulation<> &sim) { 6 | printf("[%.0f] 1\n", sim.now()); 7 | 8 | co_await (sim.timeout(1) & sim.timeout(2)); 9 | printf("[%.0f] 2\n", sim.now()); 10 | 11 | // sim.event() will never be triggered -> the resulting event will never 12 | // be triggered too 13 | co_await (sim.timeout(1) & sim.event()); 14 | printf("[%.0f] 3\n", sim.now()); 15 | } 16 | 17 | int main() { 18 | simcpp20::simulation<> sim; 19 | process(sim); 20 | sim.run(); 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | build: 9 | strategy: 10 | matrix: 11 | os: [windows-latest] 12 | build_type: [Debug, Release] 13 | runs-on: ${{matrix.os}} 14 | steps: 15 | - uses: actions/checkout@v4.0.0 16 | - name: Configure 17 | run: cmake -B build 18 | - name: Build 19 | run: cmake --build build --config ${{matrix.build_type}} 20 | - name: Test 21 | run: build/tests/${{matrix.build_type}}/tests 22 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare(Catch2 4 | GIT_REPOSITORY https://github.com/catchorg/Catch2 5 | GIT_TAG 6e79e682b726f524310d55dec8ddac4e9c52fb5f) # v3.4.0 6 | FetchContent_MakeAvailable(Catch2) 7 | 8 | add_executable(tests 9 | tests.cpp 10 | awaitable_tests.cpp 11 | resource_tests.cpp 12 | store_tests.cpp) 13 | target_link_libraries(tests PRIVATE 14 | fschuetz04::simcpp20 15 | Catch2::Catch2WithMain) 16 | target_compile_options(tests PRIVATE 17 | $<$:/W4> 18 | $<$>:-Wall -Wextra -Wpedantic> 19 | ) 20 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | build: 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest] 12 | cxx: [g++, clang++] 13 | build_type: [Debug, Release] 14 | runs-on: ${{matrix.os}} 15 | steps: 16 | - uses: actions/checkout@v4.0.0 17 | - name: Configure 18 | run: CXX=${{matrix.cxx}} cmake -B build -D CMAKE_BUILD_TYPE=${{matrix.build_type}} 19 | - name: Build 20 | run: cmake --build build 21 | - name: Test 22 | run: build/tests/tests 23 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "buildRoot": "${projectDir}\\out\\build\\${name}", 5 | "configurationType": "Debug", 6 | "generator": "Ninja", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "installRoot": "${projectDir}\\out\\install\\${name}", 9 | "name": "VS Debug" 10 | }, 11 | { 12 | "buildRoot": "${projectDir}\\out\\build\\${name}", 13 | "configurationType": "RelWithDebInfo", 14 | "generator": "Ninja", 15 | "inheritEnvironments": [ "msvc_x64_x64" ], 16 | "installRoot": "${projectDir}\\out\\install\\${name}", 17 | "name": "VS Release" 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /examples/clocks_units.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | #include "units.h" 5 | 6 | using namespace units::literals; 7 | 8 | using time_type = units::time::second_t; 9 | using process = simcpp20::process; 10 | using simulation = simcpp20::simulation; 11 | 12 | process clock_proc(simulation &sim, char const *name, time_type delay) { 13 | while (true) { 14 | std::cout << "[" << sim.now() << "] " << name << std::endl; 15 | co_await sim.timeout(delay); 16 | } 17 | } 18 | 19 | int main() { 20 | simulation sim; 21 | clock_proc(sim, "slow", 2_s); 22 | clock_proc(sim, "fast", 1_s); 23 | sim.run_until(5_s); 24 | } 25 | -------------------------------------------------------------------------------- /include/fschuetz04/simcpp20/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(fschuetz04_simcpp20 INTERFACE) 2 | add_library(fschuetz04::simcpp20 ALIAS fschuetz04_simcpp20) 3 | 4 | target_include_directories(fschuetz04_simcpp20 INTERFACE ${PROJECT_SOURCE_DIR}/include) 5 | target_compile_features(fschuetz04_simcpp20 INTERFACE cxx_std_20) 6 | 7 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 8 | if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10") 9 | message(WARNING "SimCpp20 requires GCC 10 or later") 10 | endif() 11 | target_compile_options(fschuetz04_simcpp20 INTERFACE -fcoroutines) 12 | endif() 13 | 14 | if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 15 | if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.28") 16 | message(WARNING "SimCpp20 requires MSVC 19.28 or later") 17 | endif() 18 | endif() 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14...3.31) 2 | 3 | if(DEFINED PROJECT_NAME) 4 | set(MAIN_PROJECT OFF) 5 | else() 6 | set(MAIN_PROJECT ON) 7 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 8 | endif() 9 | 10 | project(simcpp20 11 | VERSION 0.1.0 12 | DESCRIPTION "Discrete-event simulation in C++20 using coroutines" 13 | HOMEPAGE_URL "https://github.com/fschuetz04/simcpp20" 14 | LANGUAGES CXX) 15 | 16 | add_subdirectory(include/fschuetz04/simcpp20) 17 | 18 | option(FSCHUETZ04_SIMCPP20_BUILD_TESTS "Build tests" ${MAIN_PROJECT}) 19 | if(FSCHUETZ04_SIMCPP20_BUILD_TESTS) 20 | add_subdirectory(tests) 21 | endif() 22 | 23 | option(FSCHUETZ04_SIMCPP20_BUILD_EXAMPLES "Build examples" ${MAIN_PROJECT}) 24 | if(FSCHUETZ04_SIMCPP20_BUILD_EXAMPLES) 25 | add_subdirectory(examples) 26 | endif() 27 | 28 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | OPTION(BUILD_TESTS OFF) 4 | FetchContent_Declare(units 5 | GIT_REPOSITORY https://github.com/nholthaus/units 6 | GIT_TAG 1e2c7ed7cd8e61db7a82b8827caef7ed49b01831) 7 | FetchContent_MakeAvailable(units) 8 | 9 | set(TARGETS 10 | all_of 11 | any_of 12 | any_of_process 13 | bank_renege 14 | carwash 15 | clocks 16 | clocks_units 17 | machine_shop 18 | ping_pong 19 | store 20 | value_event 21 | value_process) 22 | 23 | foreach(TARGET ${TARGETS}) 24 | add_executable(${TARGET} ${TARGET}.cpp) 25 | target_link_libraries(${TARGET} PRIVATE fschuetz04::simcpp20) 26 | target_compile_options(${TARGET} PRIVATE 27 | $<$:/W4> 28 | $<$>:-Wall -Wextra -Wpedantic> 29 | ) 30 | endforeach() 31 | 32 | target_link_libraries(clocks_units PRIVATE units) 33 | -------------------------------------------------------------------------------- /examples/store.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | #include "fschuetz04/simcpp20/process.hpp" 5 | 6 | simcpp20::process<> producer(simcpp20::simulation<> &sim, 7 | simcpp20::store &store) { 8 | for (int i = 0; i < 5; ++i) { 9 | co_await store.put(i); 10 | printf("[%2.0f] store <- %d\n", sim.now(), i); 11 | } 12 | } 13 | 14 | simcpp20::process<> consumer(simcpp20::simulation<> &sim, 15 | simcpp20::store &store) { 16 | for (int i = 0; i < 5; ++i) { 17 | co_await sim.timeout(5); 18 | auto value = co_await store.get(); 19 | printf("[%2.0f] store -> %d\n", sim.now(), value); 20 | } 21 | } 22 | 23 | int main() { 24 | simcpp20::simulation<> sim; 25 | simcpp20::store store{sim, 1}; 26 | producer(sim, store); 27 | consumer(sim, store); 28 | sim.run(); 29 | } 30 | -------------------------------------------------------------------------------- /include/fschuetz04/simcpp20/awaitable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // std::function 4 | 5 | namespace simcpp20 { 6 | 7 | /// An interface for objects that can be awaited. 8 | class awaitable { 9 | public: 10 | /// Destructor. 11 | virtual ~awaitable() = default; 12 | 13 | /// @return Whether the awaitable is pending. 14 | virtual bool pending() const = 0; 15 | 16 | /// @return Whether the event is triggered or processed. 17 | virtual bool triggered() const = 0; 18 | 19 | /// @return Whether the awaitable is processed. 20 | virtual bool processed() const = 0; 21 | 22 | /// @return Whether the awaitable is aborted. 23 | virtual bool aborted() const = 0; 24 | 25 | /** 26 | * Add a callback to be called when the awaitable is processed. 27 | * 28 | * @param cb Callback. 29 | */ 30 | virtual void add_callback(std::function cb) const = 0; 31 | }; 32 | 33 | } // namespace simcpp20 34 | -------------------------------------------------------------------------------- /examples/ping_pong.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | 5 | struct ev_type; 6 | using ev_inner = simcpp20::value_event; 7 | struct ev_type { 8 | ev_inner ev; 9 | }; 10 | 11 | simcpp20::process<> party(simcpp20::simulation<> &sim, const char *name, 12 | simcpp20::value_event my_event, 13 | double delay) { 14 | while (true) { 15 | auto their_event = (co_await my_event).ev; 16 | printf("[%.0f] %s\n", sim.now(), name); 17 | co_await sim.timeout(delay); 18 | my_event = sim.event(); 19 | their_event.trigger(ev_type{my_event}); 20 | } 21 | } 22 | 23 | int main() { 24 | simcpp20::simulation<> sim; 25 | auto pong_event = sim.event(); 26 | auto ping_event = sim.timeout(0, ev_type{pong_event}); 27 | party(sim, "ping", ping_event, 1); 28 | party(sim, "pong", pong_event, 2); 29 | sim.run_until(8); 30 | } 31 | -------------------------------------------------------------------------------- /examples/any_of_process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "fschuetz04/simcpp20.hpp" 4 | 5 | simcpp20::process<> producer(simcpp20::simulation<> &sim, int id, 6 | double delay) { 7 | printf("[%.0f] Producer %d starting\n", sim.now(), id); 8 | co_await sim.timeout(delay); 9 | printf("[%.0f] Producer %d finished\n", sim.now(), id); 10 | } 11 | 12 | simcpp20::process<> consumer(simcpp20::simulation<> &sim) { 13 | printf("[%.0f] Consumer starting\n", sim.now()); 14 | 15 | // Create two producer processes 16 | auto p1 = producer(sim, 1, 5); 17 | auto p2 = producer(sim, 2, 10); 18 | 19 | // Wait for any producer to finish 20 | co_await (p1 | p2); 21 | printf("[%.0f] First producer finished\n", sim.now()); 22 | 23 | // Wait for all producers to finish 24 | co_await (p1 & p2); 25 | printf("[%.0f] All producers finished\n", sim.now()); 26 | } 27 | 28 | int main() { 29 | simcpp20::simulation<> sim; 30 | consumer(sim); 31 | sim.run(); 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2025 Felix Schütz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/carwash.cpp: -------------------------------------------------------------------------------- 1 | // See https://simpy.readthedocs.io/en/latest/examples/carwash.html 2 | 3 | #include 4 | #include 5 | 6 | #include "fschuetz04/simcpp20.hpp" 7 | 8 | struct config { 9 | int initial_cars; 10 | double wash_time; 11 | simcpp20::resource<> machines; 12 | std::uniform_int_distribution<> arrival_time_dist; 13 | std::default_random_engine gen; 14 | }; 15 | 16 | simcpp20::process<> wash(simcpp20::simulation<> &sim, config &conf, int id) { 17 | co_await sim.timeout(conf.wash_time); 18 | printf("[%4.1f] Car %d washed\n", sim.now(), id); 19 | } 20 | 21 | simcpp20::process<> car(simcpp20::simulation<> &sim, config &conf, int id) { 22 | printf("[%4.1f] Car %d arrives\n", sim.now(), id); 23 | 24 | auto request = conf.machines.request(); 25 | co_await request; 26 | 27 | printf("[%4.1f] Car %d enters\n", sim.now(), id); 28 | 29 | co_await wash(sim, conf, id); 30 | 31 | printf("[%4.1f] Car %d leaves\n", sim.now(), id); 32 | conf.machines.release(); 33 | } 34 | 35 | simcpp20::process<> car_source(simcpp20::simulation<> &sim, config &conf) { 36 | for (int id = 1;; ++id) { 37 | if (id > conf.initial_cars) { 38 | co_await sim.timeout(conf.arrival_time_dist(conf.gen)); 39 | } 40 | 41 | car(sim, conf, id); 42 | } 43 | } 44 | 45 | int main() { 46 | simcpp20::simulation<> sim; 47 | 48 | std::random_device rd; 49 | config conf{ 50 | .initial_cars = 4, 51 | .wash_time = 5, 52 | .machines = simcpp20::resource{sim, 2}, 53 | .arrival_time_dist = std::uniform_int_distribution<>{3, 7}, 54 | .gen = std::default_random_engine{rd()}, 55 | }; 56 | 57 | car_source(sim, conf); 58 | 59 | sim.run_until(20); 60 | } 61 | -------------------------------------------------------------------------------- /include/fschuetz04/simcpp20/promise_type.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // assert 4 | #include // std::coroutine_handle, std::suspend_never 5 | 6 | namespace simcpp20 { 7 | 8 | template class simulation; 9 | template class event; 10 | 11 | } // namespace simcpp20 12 | 13 | namespace simcpp20::internal { 14 | 15 | /** 16 | * Abstract base class for all promise types. 17 | * 18 | * @tparam Time Type used for simulation time. 19 | */ 20 | template class generic_promise_type { 21 | public: 22 | /// Constructor. 23 | generic_promise_type(simulation