├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── example-minimal.cpp ├── example-twocars.cpp ├── protothread.h ├── simcpp.cpp ├── simcpp.h └── simobj.h /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.cpp 3 | !*.h 4 | !*.md 5 | !.gitignore 6 | !Makefile 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2021 Bjørnar Steinnes Luteberget, 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HEADER=simcpp.h protothread.h 2 | SOURCE=simcpp.cpp 3 | EXE=example-minimal example-twocars 4 | 5 | .PHONY: clean 6 | 7 | all: $(EXE) 8 | 9 | %: %.cpp $(HEADER) $(SOURCE) 10 | g++ -Wall -std=c++11 $< $(SOURCE) -o $@ 11 | 12 | clean: 13 | rm $(EXE) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SimCpp 2 | 3 | SimCpp is a discrete event simulation framework for C++. 4 | It aims to be a port of [SimPy](https://simpy.readthedocs.io/en/latest/). 5 | It is based on [Protothreads](http://dunkels.com/adam/pt) and a [C++ port of Protothreads](https://github.com/benhoyt/protothreads-cpp). 6 | 7 | ## Minimal Example 8 | 9 | ```c++ 10 | #include "simcpp.h" 11 | 12 | class Car : public simcpp::Process { 13 | public: 14 | explicit Car(simcpp::SimulationPtr sim) : Process(sim) {} 15 | 16 | bool Run() override { 17 | auto sim = this->sim.lock(); 18 | 19 | PT_BEGIN(); 20 | 21 | printf("Car running at %g.\n", sim->get_now()); 22 | PROC_WAIT_FOR(sim->timeout(5.0)); 23 | printf("Car running at %g.\n", sim->get_now()); 24 | 25 | PT_END(); 26 | } 27 | }; 28 | 29 | int main() { 30 | auto sim = simcpp::Simulation::create(); 31 | sim->start_process(); 32 | sim->run(); 33 | 34 | return 0; 35 | } 36 | ``` 37 | 38 | This example can be compiled with `g++ -Wall -std=c++11 example-minimal.cpp simcpp.cpp -o example.minimal`. 39 | When executed with `./example-minimal`, it produces the following output: 40 | 41 | ```text 42 | Car running at 0. 43 | Car running at 5. 44 | ``` 45 | 46 | ## Installation 47 | 48 | To use SimCpp, you need the files `simcpp.cpp`, `simcpp.h`, and `protothread.h`. 49 | Whenever you want to use SimCpp in a file, include it with `#include "simcpp.h"`. 50 | When compiling your program, you have to include the `simcpp.cpp` file. 51 | 52 | ## Getting Started 53 | 54 | A SimCpp simulation is created by calling `simcpp::Simulation::create();`. 55 | This returns a shared pointer to an instance of `simcpp::Simulation` to simplify memory management. 56 | In this simulation, one or more processes have to be started to actually do things. 57 | This is done with `sim->start_process()`. 58 | 59 | Each process is modeled as a subclass of `simcpp::Process`. 60 | The behavior of a process is implemented in its `Run` method. 61 | The `Run` method should start with (optional) variable declarations, followed by `PT_BEGIN();`, the behavior code, and finally `PT_END();`. 62 | 63 | In between `PT_BEGIN();` and `PT_END();` you can use `PROC_WAIT_FOR(...);` to wait for events. 64 | Events can be created with `sim->event()` or `sim->timeout(...)`. 65 | These methods will be explained later. 66 | Processes are events as well, so you can start new processes and wait for their completion using `PROC_WAIT_FOR(...);` too. 67 | 68 | The `sim` class attribute is a weak pointer to the simulation instance to prevent cyclic references. 69 | Never permanently store shared pointers to the simulation inside your processes! 70 | The local variables of the `Run` method are reset after (most) calls to `PROC_WAIT_FOR`, so keeping a shared pointer to the simulation instance in the local variable `sim` is acceptable (this is done via `auto sim = this->sim.lock()` in the above example). 71 | This also means that you have to use class attributes instead of local variables to keep state across calls to `PROC_WAIT_FOR`. 72 | 73 | As stated before, `auto event = sim->event()` can be used to create new events. 74 | Again, this returns a shared pointer to an instance of `simcpp::Event` to simplify memory management. 75 | Initially, the returned event is pending. 76 | When calling `PROC_WAIT_FOR(event);` with a pending event, the process is paused. 77 | 78 | By calling `event->trigger()`, the event is triggered. 79 | This means that all processes waiting for the event are resumed. 80 | When calling `PROC_WAIT_FOR(event);` with a triggered event, nothing special happens. 81 | 82 | When calling `auto event = sim->timeout(5)`, a pending event is returned too. 83 | However, this event is automatically triggered after 5 timesteps. 84 | Thus, if you write `PROC_WAIT_FOR(sim->timeout(5))`, the process is paused for 5 timesteps. 85 | 86 | To check the current simulation time, you can use `sim->get_now()`. 87 | Initially, it will return 0, but it will increase when the simulation is run. 88 | To run the simulation, use `sim->run()`. 89 | This runs the simulation until no further events are scheduled to be triggered. 90 | It is also possible to run the simulation for a specific duration with `sim->advance_by(100)` (in this case, the simulation is advanced by 100 timesteps). 91 | 92 | Now you should know all the basics to write your discrete event simulation with SimCpp. 93 | Continue reading to learn about all features. 94 | If you have any questions or problems, please file an issue on GitHub: . 95 | If you want to improve SimCpp, feel free to submit a pull request. 96 | 97 | ## API 98 | 99 | ### Creating the simulation 100 | 101 | ```c++ 102 | simcpp::SimulationPtr sim = simcpp::Simulation::create(); 103 | // or 104 | std::shared_ptr sim2 = simcpp::Simulation::create(); 105 | ``` 106 | 107 | ### Starting processes 108 | 109 | Construct the `MyProcess` process with two additional arguments and run it: 110 | 111 | *The `MyProcess` instance is returned.* 112 | 113 | ```c++ 114 | std::shared_ptr process = sim->start_process(arg1, arg2); 115 | ``` 116 | 117 | Construct the `MyProcess` process with two additional arguments and run it after the given delay: 118 | 119 | *The `MyProcess` instance is returned.* 120 | 121 | ```c++ 122 | std::shared_ptr = sim->start_process_delayed(delay, arg1, arg2); 123 | ``` 124 | 125 | ### Creating events 126 | 127 | Construct an event: 128 | 129 | ```c++ 130 | simcpp::EventPtr event = sim->event(); 131 | // or 132 | std::shared_ptr event = sim->event(); 133 | ``` 134 | 135 | Construct the custom `MyEvent` event with two additional arguments: 136 | 137 | ```c++ 138 | std::shared_ptr event = sim->event(arg1, arg2); 139 | ``` 140 | 141 | Construct a timeout event which is scheduled to be processed after the given delay: 142 | 143 | ```c++ 144 | simcpp::EventPtr event = sim->timeout(delay); 145 | ``` 146 | 147 | Construct an event which is triggered when any of the given events is processed: 148 | 149 | ```c++ 150 | simcpp::EventPtr event = sim->any_of({ event1, event2 }); 151 | ``` 152 | 153 | Construct an event which is triggered when all of the given events are processed: 154 | 155 | ```c++ 156 | simcpp::EventPtr event = sim->all_of({ event1, event2 }); 157 | ``` 158 | 159 | ### Running the simulation 160 | 161 | Run the simulation until no scheduled events are left: 162 | 163 | ```c++ 164 | sim->run(); 165 | ``` 166 | 167 | Advance the simulation by the given duration: 168 | 169 | ```c++ 170 | sim->advance_by(duration); 171 | ``` 172 | 173 | Advance the simulation until the given event is triggered: 174 | 175 | *Returns `true` if the event was triggered and `false` is the simulation stopped because the event was aborted or no scheduled events are left.* 176 | 177 | ```c++ 178 | bool ok = sim->advance_to(event); 179 | ``` 180 | 181 | Process the next scheduled event: 182 | 183 | *Returns `true` if there was a scheduled event to be processed and `false` otherwise.* 184 | 185 | ```c++ 186 | bool ok = sim->step(); 187 | ``` 188 | 189 | ### Checking the simulation state 190 | 191 | Get the current simulation time: 192 | 193 | ```c++ 194 | double now = sim->get_now(); 195 | ``` 196 | 197 | Check whether a scheduled event is left: 198 | 199 | ```c++ 200 | bool has_next = sim->has_next(); 201 | ``` 202 | 203 | Get the time at which the next event is scheduled: 204 | 205 | *If no events are scheduled, this method throws an exception.* 206 | 207 | ```c++ 208 | double time = sim->peek_next_time(); 209 | ``` 210 | 211 | ### Changing the event state 212 | 213 | Schedule the event to be processed: 214 | 215 | *If the event was not pending, nothing is done. 216 | Returns `true` if the event was pending, `false` otherwise.* 217 | 218 | ```c++ 219 | bool ok = event->trigger(); 220 | ``` 221 | 222 | Schedule the event to be processed after the given delay: 223 | 224 | *If the event was not pending, nothing is done. 225 | Returns `true` if the event was pending, `false` otherwise.* 226 | 227 | ```c++ 228 | bool ok = event->trigger(delay); 229 | ``` 230 | 231 | Abort the event: 232 | 233 | *If the event was not pending, nothing is done. 234 | Otherwise, calls the `Abort` callback of the event / process. 235 | Returns `true` if the event was pending, `false` otherwise.* 236 | 237 | ```c++ 238 | bool ok = event->abort(); 239 | ``` 240 | 241 | ### Checking the event state 242 | 243 | Check whether the event is pending: 244 | 245 | ```c++ 246 | bool pending = event->is_pending(); 247 | ``` 248 | 249 | Check whether the event is triggered: 250 | 251 | *This also returns `true` if the event is already processed.* 252 | 253 | ```c++ 254 | bool triggered = event->is_triggered(); 255 | ``` 256 | 257 | Check whether the event is processed: 258 | 259 | ```c++ 260 | bool processed = event->is_processed(); 261 | ``` 262 | 263 | Check whether the event is aborted: 264 | 265 | ```c++ 266 | bool aborted = event->is_aborted(); 267 | ``` 268 | 269 | Get the state of an event: 270 | 271 | *The state is one of `{Pending, Triggered, Processed, Aborted}`.* 272 | 273 | ```c++ 274 | simcpp::Event::State state = event->get_state(); 275 | ``` 276 | 277 | ### Waiting for events 278 | 279 | Add a callback to be called when the event is processed: 280 | 281 | *If the event was not pending, nothing is done. 282 | Returns `false` if the event is triggered, `true` otherwise.* 283 | 284 | ```c++ 285 | bool ok = event->add_handler(callback); 286 | ``` 287 | 288 | Pause the process until the event is processed: 289 | 290 | *If the event is aborted, the process is paused indefinitely. 291 | If the event is triggered or processed, the process is not paused.* 292 | 293 | ```c++ 294 | PROC_WAIT_FOR(handler); 295 | ``` 296 | 297 | ### Subclassing `simcpp::Event` 298 | 299 | The `simcpp::Event` class can be subclassed to create custom event classes. 300 | Custom events can take additional arguments in their constructor, store attributes, offer methods, and override the `Aborted` callback. 301 | Note that the `sim` attribute is only a weak pointer to the simulation instance. 302 | It must be converted to a shared pointer first to use it (`sim.lock()`). 303 | Never store a shared pointer to the simulation instance as a permanent class attribute, this leads to cyclic references and therefore memory leaks. 304 | 305 | ```c++ 306 | class MyEvent : public simcpp::Event { 307 | public: 308 | MyEvent(simcpp::SimulationPtr sim, int arg1, int arg2) : Event(sim) {} 309 | 310 | void Aborted() override { 311 | // ... 312 | } 313 | } 314 | ``` 315 | 316 | ### Subclassing `simcpp::Process` 317 | 318 | The `simcpp::Process` class can be subclassed to create process classes. 319 | Custom processes can take additional arguments in their constructor, store attributes, offer methods, and override the `Run` method and `Aborted` callback. 320 | Note that the `sim` attribute is only a weak pointer to the simulation instance. 321 | It must be converted to a shared pointer first to use it (`sim.lock()`). 322 | Never store a shared pointer to the simulation instance as a permanent class attribute, this leads to cyclic references and therefore memory leaks. 323 | 324 | ```c++ 325 | class MyProcess : public simcpp::Process { 326 | public: 327 | MyProcess(simcpp::SimulationPtr sim, int arg1, int arg2) : Process(sim) {} 328 | 329 | bool Run() override { 330 | simcpp::SimulationPtr sim = this->sim.lock(); 331 | 332 | PT_BEGIN(); 333 | // ... 334 | PT_END(); 335 | } 336 | 337 | void Aborted() override { 338 | // ... 339 | } 340 | } 341 | ``` 342 | 343 | ## Copyright and License 344 | 345 | Copyright © 2021 Bjørnar Steinnes Luteberget, Felix Schütz. 346 | 347 | Licensed under the MIT License. 348 | See the LICENSE file for details. 349 | -------------------------------------------------------------------------------- /example-minimal.cpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 Bjørnar Steinnes Luteberget 2 | // Licensed under the MIT license. See the LICENSE file for details. 3 | 4 | #include 5 | 6 | #include "simcpp.h" 7 | 8 | class Car : public simcpp::Process { 9 | public: 10 | explicit Car(simcpp::SimulationPtr sim) : Process(sim) {} 11 | 12 | bool Run() override { 13 | auto sim = this->sim.lock(); 14 | 15 | PT_BEGIN(); 16 | 17 | printf("Car running at %g.\n", sim->get_now()); 18 | PROC_WAIT_FOR(sim->timeout(5.0)); 19 | printf("Car running at %g.\n", sim->get_now()); 20 | 21 | PT_END(); 22 | } 23 | }; 24 | 25 | int main() { 26 | auto sim = simcpp::Simulation::create(); 27 | sim->start_process(); 28 | sim->run(); 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /example-twocars.cpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 Bjørnar Steinnes Luteberget 2 | // Licensed under the MIT license. See the LICENSE file for details. 3 | 4 | #include 5 | #include 6 | 7 | #include "simcpp.h" 8 | 9 | class Car : public simcpp::Process { 10 | public: 11 | explicit Car(simcpp::SimulationPtr sim, std::string name) 12 | : Process(sim), target_time(sim->get_now() + 100.0), name(name) {} 13 | 14 | bool Run() override { 15 | auto sim = this->sim.lock(); 16 | 17 | PT_BEGIN(); 18 | 19 | while (sim->get_now() < target_time) { 20 | PROC_WAIT_FOR(sim->timeout(5.0)); 21 | printf("Car %s running at %g.\n", name.c_str(), sim->get_now()); 22 | } 23 | 24 | PT_END(); 25 | } 26 | 27 | private: 28 | bool finished = false; 29 | double target_time; 30 | std::string name; 31 | }; 32 | 33 | class TwoCars : public simcpp::Process { 34 | public: 35 | explicit TwoCars(simcpp::SimulationPtr sim) : Process(sim) {} 36 | 37 | bool Run() override { 38 | auto sim = this->sim.lock(); 39 | 40 | PT_BEGIN(); 41 | 42 | printf("Starting car C1.\n"); 43 | PROC_WAIT_FOR(sim->start_process("C1")); 44 | printf("Finished car C1.\n"); 45 | 46 | printf("Starting car C2.\n"); 47 | PROC_WAIT_FOR(sim->start_process("C2")); 48 | printf("Finished car C2.\n"); 49 | 50 | PT_END(); 51 | } 52 | }; 53 | 54 | int main() { 55 | auto sim = simcpp::Simulation::create(); 56 | sim->start_process(); 57 | sim->advance_by(10000); 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /protothread.h: -------------------------------------------------------------------------------- 1 | // Protothread class and macros for lightweight, stackless threads in C++. 2 | // 3 | // This was "ported" to C++ from Adam Dunkels' protothreads C library at: 4 | // http://www.sics.se/~adam/pt/ 5 | // 6 | // Originally ported for use by Hamilton Jet (www.hamiltonjet.co.nz) by 7 | // Ben Hoyt, but stripped down for public release. See his blog entry about 8 | // it for more information: 9 | // http://blog.brush.co.nz/2008/07/protothreads/ 10 | // 11 | // Visual Studio users: There's a quirk with VS where it defines __LINE__ 12 | // as a non-constant when you've got a project's Debug Information Format 13 | // set to "Program Database for Edit and Continue (/ZI)" -- the default. 14 | // To fix, just go to the project's Properties, Configuration Properties, 15 | // C/C++, General, Debug Information Format, and change it to "Program 16 | // Database (/Zi)". 17 | // 18 | // -------------------------- 19 | // Original BSD-style license 20 | // -------------------------- 21 | // Copyright (c) 2004-2005, Swedish Institute of Computer Science. 22 | // All rights reserved. 23 | // 24 | // Redistribution and use in source and binary forms, with or without 25 | // modification, are permitted provided that the following conditions 26 | // are met: 27 | // 28 | // 1. Redistributions of source code must retain the above copyright 29 | // notice, this list of conditions and the following disclaimer. 30 | // 2. Redistributions in binary form must reproduce the above copyright 31 | // notice, this list of conditions and the following disclaimer in the 32 | // documentation and/or other materials provided with the distribution. 33 | // 3. Neither the name of the Institute nor the names of its contributors 34 | // may be used to endorse or promote products derived from this software 35 | // without specific prior written permission. 36 | // 37 | // This software is provided by the Institute and contributors "as is" and 38 | // any express or implied warranties, including, but not limited to, the 39 | // implied warranties of merchantability and fitness for a particular purpose 40 | // are disclaimed. In no event shall the Institute or contributors be liable 41 | // for any direct, indirect, incidental, special, exemplary, or consequential 42 | // damages (including, but not limited to, procurement of substitute goods 43 | // or services; loss of use, data, or profits; or business interruption) 44 | // however caused and on any theory of liability, whether in contract, strict 45 | // liability, or tort (including negligence or otherwise) arising in any way 46 | // out of the use of this software, even if advised of the possibility of 47 | // such damage. 48 | // 49 | 50 | #ifndef __PROTOTHREAD_H__ 51 | #define __PROTOTHREAD_H__ 52 | 53 | // A lightweight, stackless thread. Override the Run() method and use 54 | // the PT_* macros to do work of the thread. 55 | // 56 | // A simple example 57 | // ---------------- 58 | // class LEDFlasher : public Protothread 59 | // { 60 | // public: 61 | // virtual bool Run(); 62 | // 63 | // private: 64 | // ExpiryTimer _timer; 65 | // uintf _i; 66 | // }; 67 | // 68 | // bool LEDFlasher::Run() 69 | // { 70 | // PT_BEGIN(); 71 | // 72 | // for (_i = 0; _i < 10; _i++) 73 | // { 74 | // SetLED(true); 75 | // _timer.Start(250); 76 | // PT_WAIT_UNTIL(_timer.Expired()); 77 | // 78 | // SetLED(false); 79 | // _timer.Start(750); 80 | // PT_WAIT_UNTIL(_timer.Expired()); 81 | // } 82 | // 83 | // PT_END(); 84 | // } 85 | // 86 | class Protothread 87 | { 88 | public: 89 | // Construct a new protothread that will start from the beginning 90 | // of its Run() function. 91 | Protothread() : _ptLine(0) { } 92 | 93 | // Restart protothread. 94 | void Restart() { _ptLine = 0; } 95 | 96 | // Stop the protothread from running. Happens automatically at PT_END. 97 | // Note: this differs from the Dunkels' original protothread behaviour 98 | // (his restart automatically, which is usually not what you want). 99 | void Stop() { _ptLine = LineNumberInvalid; } 100 | 101 | // Return true if the protothread is running or waiting, false if it has 102 | // ended or exited. 103 | bool IsRunning() { return _ptLine != LineNumberInvalid; } 104 | 105 | // Run next part of protothread or return immediately if it's still 106 | // waiting. Return true if protothread is still running, false if it 107 | // has finished. Implement this method in your Protothread subclass. 108 | virtual bool Run() = 0; 109 | 110 | protected: 111 | // Used to store a protothread's position (what Dunkels calls a 112 | // "local continuation"). 113 | typedef unsigned short LineNumber; 114 | 115 | // An invalid line number, used to mark the protothread has ended. 116 | static const LineNumber LineNumberInvalid = (LineNumber)(-1); 117 | 118 | // Stores the protothread's position (by storing the line number of 119 | // the last PT_WAIT, which is then switched on at the next Run). 120 | LineNumber _ptLine; 121 | }; 122 | 123 | // Declare start of protothread (use at start of Run() implementation). 124 | #define PT_BEGIN() bool ptYielded = true; (void) ptYielded; switch (_ptLine) { case 0: 125 | 126 | // Stop protothread and end it (use at end of Run() implementation). 127 | #define PT_END() default: ; } Stop(); return false; 128 | 129 | // Cause protothread to wait until given condition is true. 130 | #define PT_WAIT_UNTIL(condition) \ 131 | do { _ptLine = __LINE__; case __LINE__: \ 132 | if (!(condition)) return true; } while (0) 133 | 134 | // Cause protothread to wait while given condition is true. 135 | #define PT_WAIT_WHILE(condition) PT_WAIT_UNTIL(!(condition)) 136 | 137 | // Cause protothread to wait until given child protothread completes. 138 | #define PT_WAIT_THREAD(child) PT_WAIT_WHILE((child).Run()) 139 | 140 | // Restart and spawn given child protothread and wait until it completes. 141 | #define PT_SPAWN(child) \ 142 | do { (child).Restart(); PT_WAIT_THREAD(child); } while (0) 143 | 144 | // Restart protothread's execution at its PT_BEGIN. 145 | #define PT_RESTART() do { Restart(); return true; } while (0) 146 | 147 | // Stop and exit from protothread. 148 | #define PT_EXIT() do { Stop(); return false; } while (0) 149 | 150 | // Yield protothread till next call to its Run(). 151 | #define PT_YIELD() \ 152 | do { ptYielded = false; _ptLine = __LINE__; case __LINE__: \ 153 | if (!ptYielded) return true; } while (0) 154 | 155 | // Yield protothread until given condition is true. 156 | #define PT_YIELD_UNTIL(condition) \ 157 | do { ptYielded = false; _ptLine = __LINE__; case __LINE__: \ 158 | if (!ptYielded || !(condition)) return true; } while (0) 159 | 160 | #endif // __PROTOTHREAD_H__ 161 | -------------------------------------------------------------------------------- /simcpp.cpp: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 Bjørnar Steinnes Luteberget, Felix Schütz. 2 | // Licensed under the MIT license. See the LICENSE file for details. 3 | 4 | #include "simcpp.h" 5 | 6 | namespace simcpp { 7 | 8 | /* Simulation */ 9 | 10 | SimulationPtr Simulation::create() { return std::make_shared(); } 11 | 12 | void Simulation::run_process(ProcessPtr process, simtime delay /* = 0.0 */) { 13 | auto event = this->event(); 14 | event->add_handler(process); 15 | event->trigger(delay); 16 | } 17 | 18 | EventPtr Simulation::timeout(simtime delay) { 19 | auto event = this->event(); 20 | event->trigger(delay); 21 | return event; 22 | } 23 | 24 | EventPtr Simulation::any_of(std::initializer_list events) { 25 | int n = 1; 26 | for (auto &event : events) { 27 | if (event->is_triggered()) { 28 | n = 0; 29 | break; 30 | } 31 | } 32 | 33 | auto process = start_process(n); 34 | for (auto &event : events) { 35 | event->add_handler(process); 36 | } 37 | return process; 38 | } 39 | 40 | EventPtr Simulation::all_of(std::initializer_list events) { 41 | int n = 0; 42 | for (auto &event : events) { 43 | if (!event->is_triggered()) { 44 | ++n; 45 | } 46 | } 47 | 48 | auto process = start_process(n); 49 | for (auto &event : events) { 50 | event->add_handler(process); 51 | } 52 | return process; 53 | } 54 | 55 | void Simulation::schedule(EventPtr event, simtime delay /* = 0.0 */) { 56 | queued_events.emplace(now + delay, next_id, event); 57 | ++next_id; 58 | } 59 | 60 | bool Simulation::step() { 61 | if (queued_events.empty()) { 62 | return false; 63 | } 64 | 65 | auto queued_event = queued_events.top(); 66 | queued_events.pop(); 67 | now = queued_event.time; 68 | auto event = queued_event.event; 69 | event->process(); 70 | return true; 71 | } 72 | 73 | void Simulation::advance_by(simtime duration) { 74 | simtime target = now + duration; 75 | while (has_next() && peek_next_time() <= target) { 76 | step(); 77 | } 78 | now = target; 79 | } 80 | 81 | bool Simulation::advance_to(EventPtr event) { 82 | while (event->is_pending() && has_next()) { 83 | step(); 84 | } 85 | 86 | return event->is_triggered(); 87 | } 88 | 89 | void Simulation::run() { 90 | while (step()) { 91 | } 92 | } 93 | 94 | simtime Simulation::get_now() { return now; } 95 | 96 | bool Simulation::has_next() { return !queued_events.empty(); } 97 | 98 | simtime Simulation::peek_next_time() { return queued_events.top().time; } 99 | 100 | /* Simulation::QueuedEvent */ 101 | 102 | Simulation::QueuedEvent::QueuedEvent(simtime time, size_t id, EventPtr event) 103 | : time(time), id(id), event(event) {} 104 | 105 | bool Simulation::QueuedEvent::operator<(const QueuedEvent &other) const { 106 | if (time != other.time) { 107 | return time > other.time; 108 | } 109 | 110 | return id > other.id; 111 | } 112 | 113 | /* Event */ 114 | 115 | Event::Event(SimulationPtr sim) : sim(sim) {} 116 | 117 | bool Event::add_handler(ProcessPtr process) { 118 | // Handler takes an additional EventPtr arg, but this is ignored by the 119 | // bound function. 120 | return add_handler(std::bind(&Process::resume, process)); 121 | } 122 | 123 | bool Event::add_handler(Handler handler) { 124 | if (is_triggered()) { 125 | return false; 126 | } 127 | 128 | if (is_pending()) { 129 | handlers.push_back(handler); 130 | } 131 | 132 | return true; 133 | } 134 | 135 | bool Event::trigger(simtime delay /* = 0.0 */) { 136 | if (!is_pending()) { 137 | return false; 138 | } 139 | 140 | auto sim = this->sim.lock(); 141 | sim->schedule(shared_from_this(), delay); 142 | 143 | if (delay == 0.0) { 144 | state = State::Triggered; 145 | } 146 | 147 | return true; 148 | } 149 | 150 | bool Event::abort() { 151 | if (!is_pending()) { 152 | return false; 153 | } 154 | 155 | state = State::Aborted; 156 | handlers.clear(); 157 | 158 | Aborted(); 159 | 160 | return true; 161 | } 162 | 163 | void Event::process() { 164 | if (is_aborted() || is_processed()) { 165 | return; 166 | } 167 | 168 | state = State::Processed; 169 | 170 | for (auto &handler : handlers) { 171 | handler(shared_from_this()); 172 | } 173 | 174 | handlers.clear(); 175 | } 176 | 177 | bool Event::is_pending() { return state == State::Pending; } 178 | 179 | bool Event::is_triggered() { 180 | return state == State::Triggered || state == State::Processed; 181 | } 182 | 183 | bool Event::is_processed() { return state == State::Processed; } 184 | 185 | bool Event::is_aborted() { return state == State::Aborted; } 186 | 187 | Event::State Event::get_state() { return state; } 188 | 189 | void Event::Aborted() {} 190 | 191 | /* Process */ 192 | 193 | Process::Process(SimulationPtr sim) : Event(sim), Protothread() {} 194 | 195 | void Process::resume() { 196 | // Is the process already finished? 197 | if (!is_pending()) { 198 | return; 199 | } 200 | 201 | bool still_running = Run(); 202 | 203 | // Did the process finish now? 204 | if (!still_running) { 205 | // Process finished 206 | trigger(); 207 | } 208 | } 209 | 210 | ProcessPtr Process::shared_from_this() { 211 | return std::static_pointer_cast(Event::shared_from_this()); 212 | } 213 | 214 | /* Condition */ 215 | 216 | Condition::Condition(SimulationPtr sim, int n) : Process(sim), n(n) {} 217 | 218 | bool Condition::Run() { 219 | PT_BEGIN(); 220 | while (n > 0) { 221 | PT_YIELD(); 222 | --n; 223 | } 224 | PT_END(); 225 | } 226 | 227 | } // namespace simcpp 228 | -------------------------------------------------------------------------------- /simcpp.h: -------------------------------------------------------------------------------- 1 | // Copyright © 2021 Bjørnar Steinnes Luteberget, Felix Schütz. 2 | // Licensed under the MIT license. See the LICENSE file for details. 3 | 4 | #ifndef SIMCPP_H_ 5 | #define SIMCPP_H_ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "protothread.h" 13 | 14 | /** 15 | * Wait for an event inside the Run method of a process. 16 | * 17 | * If the event is pending, the process is paused until it is processed. If the 18 | * event is already triggered or processed, the process is not paused. If the 19 | * event is already aborted, the process is paused indefinitely. 20 | * 21 | * @param event Event to wait for. 22 | */ 23 | #define PROC_WAIT_FOR(event) \ 24 | do { \ 25 | if ((event)->add_handler(shared_from_this())) { \ 26 | PT_YIELD(); \ 27 | } \ 28 | } while (0) 29 | 30 | namespace simcpp { 31 | 32 | using simtime = double; 33 | 34 | class Event; 35 | using EventPtr = std::shared_ptr; 36 | using EventWeakPtr = std::weak_ptr; 37 | 38 | class Process; 39 | using ProcessPtr = std::shared_ptr; 40 | using ProcessWeakPtr = std::weak_ptr; 41 | 42 | class Simulation; 43 | using SimulationPtr = std::shared_ptr; 44 | using SimulationWeakPtr = std::weak_ptr; 45 | 46 | using Handler = std::function; 47 | 48 | /// Simulation environment. 49 | class Simulation : public std::enable_shared_from_this { 50 | public: 51 | /** 52 | * Create a simulation environment. 53 | * 54 | * @return Simulation instance. 55 | */ 56 | static SimulationPtr create(); 57 | 58 | /** 59 | * Construct a process and run it immediately. 60 | * 61 | * @tparam T Process class. Must be a subclass of Process. 62 | * @tparam Args Additional argument types of the constructor of T. 63 | * @param args Additional arguments for the construction of T. 64 | * @return Process instance. 65 | */ 66 | template 67 | std::shared_ptr start_process(Args &&...args) { 68 | auto process = std::make_shared(shared_from_this(), args...); 69 | run_process(process); 70 | return process; 71 | } 72 | 73 | /** 74 | * Construct a process and run it after a delay. 75 | * 76 | * @tparam T Process class. Must be a subclass of Process. 77 | * @tparam Args Additional argument types of the constructor of T. 78 | * @param delay Delay after which to run the process. 79 | * @param args Additional arguments for the construction of T. 80 | * @return Process instance. 81 | */ 82 | template 83 | std::shared_ptr start_process_delayed(simtime delay, Args &&...args) { 84 | auto process = std::make_shared(shared_from_this(), args...); 85 | run_process(process, delay); 86 | return process; 87 | } 88 | 89 | /** 90 | * Run a process after a delay. 91 | * 92 | * @param process Process to be run. 93 | * @param delay Delay after which to run the process. 94 | */ 95 | void run_process(ProcessPtr process, simtime delay = 0.0); 96 | 97 | /** 98 | * Construct an event. 99 | * 100 | * @tparam T Event class. Must be Event or a subclass thereof. 101 | * @tparam Args Additional argument types of the constructor. 102 | * @param args Additional arguments for the construction of T. 103 | * @return Event instance. 104 | */ 105 | template 106 | std::shared_ptr event(Args &&...args) { 107 | return std::make_shared(shared_from_this(), args...); 108 | } 109 | 110 | /** 111 | * Construct an event and schedule it to be processed after a delay. 112 | * 113 | * @param delay Delay after which the event is processed. 114 | * @return Event instance. 115 | */ 116 | EventPtr timeout(simtime delay); 117 | 118 | /** 119 | * Create an "any of" event. 120 | * 121 | * An "any of" event is the union of multiple events. It is triggered when 122 | * any of the underlying events is processed. 123 | * 124 | * @param events Underlying events. 125 | * @return Event instance. 126 | */ 127 | EventPtr any_of(std::initializer_list events); 128 | 129 | /** 130 | * Create an "all of" event. 131 | * 132 | * An "all of" event is the conjunction of multiple events. It is triggered 133 | * when all of the underlying events are processed. 134 | * 135 | * @return Event instance. 136 | */ 137 | EventPtr all_of(std::initializer_list events); 138 | 139 | /** 140 | * Schedule an event to be processed after a delay. 141 | * 142 | * @param event Event instance. 143 | * @param delay Delay after which the event is processed. 144 | */ 145 | void schedule(EventPtr event, simtime delay = 0.0); 146 | 147 | /** 148 | * Process the next scheduled event. 149 | * 150 | * @return Whether there was a scheduled event to be processed. 151 | */ 152 | bool step(); 153 | 154 | /** 155 | * Advance the simulation by a duration. 156 | * 157 | * @param duration Duration to advance the simulation by. 158 | */ 159 | void advance_by(simtime duration); 160 | 161 | /** 162 | * Advance the simulation until an event is triggered. 163 | * 164 | * @param event Event to wait for. 165 | * @return Whether the event was triggered. If no scheduled events are left or 166 | * the event is aborted, this is not the case. 167 | */ 168 | bool advance_to(EventPtr event); 169 | 170 | /// Run the simulation until no scheduled events are left. 171 | void run(); 172 | 173 | /// @return Current simulation time. 174 | simtime get_now(); 175 | 176 | /// @return Whether a scheduled event is left. 177 | bool has_next(); 178 | 179 | /// @return Time at which the next event is scheduled. 180 | simtime peek_next_time(); 181 | 182 | private: 183 | class QueuedEvent { 184 | public: 185 | simtime time; 186 | size_t id; 187 | EventPtr event; 188 | 189 | QueuedEvent(simtime time, size_t id, EventPtr event); 190 | 191 | bool operator<(const QueuedEvent &other) const; 192 | }; 193 | 194 | simtime now = 0.0; 195 | size_t next_id = 0; 196 | std::priority_queue queued_events; 197 | }; 198 | 199 | /** 200 | * Event in a simulation. 201 | * 202 | * This class can be subclassed to create custom events with special attributes 203 | * or methods. 204 | */ 205 | class Event : public std::enable_shared_from_this { 206 | public: 207 | /// State of an event. 208 | enum class State { 209 | /// Event has not been triggered or aborted. 210 | Pending, 211 | /// Event has been triggered. 212 | Triggered, 213 | /// Event has been triggered and processed. 214 | Processed, 215 | /// Event has been aborted. 216 | Aborted 217 | }; 218 | 219 | /** 220 | * Construct an event. 221 | * 222 | * @param sim Simulation instance. 223 | */ 224 | explicit Event(SimulationPtr sim); 225 | 226 | /** 227 | * Add the resume method of a process as an handler of the event. 228 | * 229 | * If the event is already triggered or aborted, nothing is done. If the event 230 | * is triggered, the process should not wait to be resumed. The return value 231 | * can be used to check this. 232 | * 233 | * @param process The process to resume when the event is processed. 234 | * @return Whether the event was not already triggered. 235 | */ 236 | bool add_handler(ProcessPtr process); 237 | 238 | /** 239 | * Add the callback as an handler of the event. 240 | * 241 | * If the event is already triggered or aborted, nothing is done. 242 | * 243 | * @param handler Callback to call when the event is processed. The callback 244 | * receives the event instance as an argument. 245 | * @return Whether the event was not already triggered. 246 | */ 247 | bool add_handler(Handler handler); 248 | 249 | /** 250 | * Trigger the event with a delay. 251 | * 252 | * The event is scheduled to be processed after the delay. 253 | * 254 | * @param delay Delay after with the event is processed. 255 | * @return Whether the event was not already triggered or aborted. 256 | */ 257 | bool trigger(simtime delay = 0.0); 258 | 259 | /** 260 | * Abort the event. 261 | * 262 | * The Aborted callback is called. 263 | * 264 | * @return Whether the event was not already triggered or aborted. 265 | */ 266 | bool abort(); 267 | 268 | /** 269 | * Process the event. 270 | * 271 | * All handlers of the event are called when the event is not already 272 | * processed or aborted. 273 | */ 274 | void process(); 275 | 276 | /// @return Whether the event is pending. 277 | bool is_pending(); 278 | 279 | /** 280 | * @return Whether the event is triggered. Also true if the event is 281 | * processed. 282 | */ 283 | bool is_triggered(); 284 | 285 | /// @return Whether the event is aborted. 286 | bool is_aborted(); 287 | 288 | /// @return Whether the event is processed. 289 | bool is_processed(); 290 | 291 | /// @return Whether the event is pending. 292 | State get_state(); 293 | 294 | /// Called when the event is aborted. 295 | virtual void Aborted(); 296 | 297 | protected: 298 | /** 299 | * Weak pointer to the simulation instance. 300 | * 301 | * Used in subclasses of Event to access the simulation instance. To convert 302 | * it to a shared pointer, use sim.lock(). 303 | */ 304 | SimulationWeakPtr sim; 305 | 306 | private: 307 | State state = State::Pending; 308 | std::vector handlers = {}; 309 | }; 310 | 311 | /// Process in a simulation. 312 | class Process : public Event, public Protothread { 313 | public: 314 | /** 315 | * Construct a process. 316 | * 317 | * @param sim Simulation instance. 318 | */ 319 | explicit Process(SimulationPtr sim); 320 | 321 | /** 322 | * Resumes the process. 323 | * 324 | * If the event is triggered or aborted, nothing is done. If the process 325 | * finishes, the event is triggered. 326 | */ 327 | void resume(); 328 | 329 | /// @return Shared pointer to the process instance. 330 | ProcessPtr shared_from_this(); 331 | }; 332 | 333 | /// Condition process used for Simulation::any_of and Simulation::all_of. 334 | class Condition : public Process { 335 | public: 336 | /** 337 | * Constructs a condition. 338 | * 339 | * @param sim Simulation instance. 340 | * @param n Number of times the process must be resumed before it finishes. 341 | */ 342 | Condition(SimulationPtr sim, int n); 343 | 344 | /** 345 | * Wait for the given number of resumes and then finish. 346 | * 347 | * @return Whether the process is still running. 348 | */ 349 | bool Run() override; 350 | 351 | private: 352 | int n; 353 | }; 354 | 355 | } // namespace simcpp 356 | 357 | #endif // SIMCPP_H_ 358 | -------------------------------------------------------------------------------- /simobj.h: -------------------------------------------------------------------------------- 1 | #include "simcpp.h" 2 | 3 | #define OBSERVABLE_PROPERTY(TYP, NAM, VAL) \ 4 | private: \ 5 | TYP NAM = VAL; \ 6 | \ 7 | public: \ 8 | shared_ptr NAM##_event = std::make_shared(env); \ 9 | TYP get_##NAM() { return this->NAM; } \ 10 | void set_##NAM(TYP v) { \ 11 | this->NAM = v; \ 12 | this->env->schedule(this->NAM##_event); \ 13 | this->NAM##_event = std::make_shared(env); \ 14 | } 15 | 16 | class EnvObj { 17 | protected: 18 | shared_ptr env; 19 | 20 | public: 21 | EnvObj(shared_ptr s) : env(s) {} 22 | }; 23 | --------------------------------------------------------------------------------