diff --git a/xo-simulator/.github/workflows/main.yml b/xo-simulator/.github/workflows/main.yml new file mode 100644 index 00000000..c5719ada --- /dev/null +++ b/xo-simulator/.github/workflows/main.yml @@ -0,0 +1,269 @@ +name: build xo-simulator + xo dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install catch2 + # install catch2. see [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + run: sudo apt-get install -y catch2 + + # ---------------------------------------------------------------- + + - name: Clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: Configure xo-cmake + run: cmake -B ${{github.workspace}}/build_xo-cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/xo-cmake + + - name: Build xo-cmake (trivial) + run: cmake --build ${{github.workspace}}/build_xo-cmake --config ${{env.BUILD_TYPE}} + + - name: Install xo-cmake + run: cmake --install ${{github.workspace}}/build_xo-cmake + + # ---------------------------------------------------------------- + + - name: Clone indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/indentlog + + - name: Configure indentlog + # configure cmake for indentlog in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_indentlog -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/indentlog + + - name: Build indentlog + run: cmake --build ${{github.workspace}}/build_indentlog --config ${{env.BUILD_TYPE}} + + - name: Install indentlog + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_indentlog + + # ---------------------------------------------------------------- + + - name: Clone refcnt + uses: actions/checkout@v3 + with: + repository: Rconybea/refcnt + path: repo/refcnt + + - name: Configure refcnt + # configure cmake for refcnt in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_refcnt -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/refcnt + + - name: Build refcnt + run: cmake --build ${{github.workspace}}/build_refcnt --config ${{env.BUILD_TYPE}} + + - name: Install refcnt + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_refcnt + + # ---------------------------------------------------------------- + + - name: Clone subsys + uses: actions/checkout@v3 + with: + repository: Rconybea/subsys + path: repo/subsys + + - name: Configure subsys + # configure cmake for subsys in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_subsys -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/subsys + + - name: Build subsys + run: cmake --build ${{github.workspace}}/build_subsys --config ${{env.BUILD_TYPE}} + + - name: Install subsys + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_subsys + + # ---------------------------------------------------------------- + + - name: Clone reflect + uses: actions/checkout@v3 + with: + repository: Rconybea/reflect + path: repo/reflect + + - name: Configure reflect + # configure cmake for reflect in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reflect -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reflect + + - name: Build reflect + run: cmake --build ${{github.workspace}}/build_reflect --config ${{env.BUILD_TYPE}} + + - name: Install reflect + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reflect + + # ---------------------------------------------------------------- + + - name: Clone callback + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-callback + path: repo/callback + + - name: Configure callback + # configure cmake for callback in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_callback -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/callback + + - name: Build callback + run: cmake --build ${{github.workspace}}/build_callback --config ${{env.BUILD_TYPE}} + + - name: Install callback + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_callback + + # ---------------------------------------------------------------- + + - name: Clone webutil + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-webutil + path: repo/webutil + + - name: Configure webutil + # configure cmake for webutil in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_webutil -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/webutil + + - name: Build webutil + run: cmake --build ${{github.workspace}}/build_webutil --config ${{env.BUILD_TYPE}} + + - name: Install webutil + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_webutil + + # ---------------------------------------------------------------- + + - name: Clone printjson + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-printjson + path: repo/printjson + + - name: Configure printjson + # configure cmake for printjson in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_printjson -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/printjson + + - name: Build printjson + run: cmake --build ${{github.workspace}}/build_printjson --config ${{env.BUILD_TYPE}} + + - name: Install printjson + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_printjson + + # ---------------------------------------------------------------- + + - name: Clone randomgen + uses: actions/checkout@v3 + with: + repository: Rconybea/randomgen + path: repo/randomgen + + - name: Configure randomgen + # configure cmake for randomgen in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_randomgen -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/randomgen + + - name: Build randomgen + run: cmake --build ${{github.workspace}}/build_randomgen --config ${{env.BUILD_TYPE}} + + - name: Install randomgen + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_randomgen + + # ---------------------------------------------------------------- + + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + + - name: Clone ordinaltree + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-ordinaltree + path: repo/ordinaltree + + - name: Configure ordinaltree + # configure cmake for ordinaltree in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_ordinaltree -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/ordinaltree + + - name: Build ordinaltree + run: cmake --build ${{github.workspace}}/build_ordinaltree --config ${{env.BUILD_TYPE}} + + - name: Install ordinaltree + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_ordinaltree + + # ---------------------------------------------------------------- + + - name: Clone reactor + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-reactor + path: repo/reactor + + - name: Configure reactor + # configure cmake for reactor in dedicated build directory. + run: cmake -B ${{github.workspace}}/build_reactor -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local repo/reactor + + - name: Build reactor + run: cmake --build ${{github.workspace}}/build_reactor --config ${{env.BUILD_TYPE}} + + - name: Install reactor + # install into ${{github.workspace}}/local + run: cmake --install ${{github.workspace}}/build_reactor + + # ---------------------------------------------------------------- + + - name: Configure self (simulator) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build_simulator -DCMAKE_MODULE_PATH=${{github.workspace}}/local/share/cmake -DCMAKE_PREFIX_PATH=${{github.workspace}}/local -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/local -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build self (simulator) + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build_simulator --config ${{env.BUILD_TYPE}} + + - name: Test self (simulator) + working-directory: ${{github.workspace}}/build_simulator + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} diff --git a/xo-simulator/.gitignore b/xo-simulator/.gitignore new file mode 100644 index 00000000..f57a21b4 --- /dev/null +++ b/xo-simulator/.gitignore @@ -0,0 +1,6 @@ +# lsp keep state here +.cache +# typical build directories +.build* +# for lsp: manual symlink to chosen build directory +compile_commands.json diff --git a/xo-simulator/CMakeLists.txt b/xo-simulator/CMakeLists.txt new file mode 100644 index 00000000..624c63d5 --- /dev/null +++ b/xo-simulator/CMakeLists.txt @@ -0,0 +1,30 @@ +# xo-simulator/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(simulator VERSION 1.0) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(src/simulator) +#add_subdirectory(utest) + +# ---------------------------------------------------------------- +# provide find_package() support for reactor customers + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/xo-simulator/README.md b/xo-simulator/README.md new file mode 100644 index 00000000..f62f480e --- /dev/null +++ b/xo-simulator/README.md @@ -0,0 +1,55 @@ +# simulator library + +in-memory deterministic simulator + +## Getting Started + +### build + install dependencies + +build+install these first + +- xo-reactor [github.com/Rconybea/xo-reactor](https://github.com/Rconybea/xo-reactor) +- xo-ordinaltree [github.com/Rconybea/xo-ordinaltree](https://github.com/Rconybea/xo-ordinaltree) + +### build + install xo-simulator +``` +$ cd xo-simulator +$ mkdir build +$ cd build +$ INSTALL_PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} .. +$ make +$ make install +``` +(also see .github/workflows/main.yml) + +### build for unit test coverage +``` +$ cd xo-simulator +$ mkdir ccov +$ cd ccov +$ cmake -DCMAKE_MODULE_PATH=${INSTALL_PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${INSTALL_PREFIX} -DCODE_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug .. +``` + +## Development + +### LSP support + +LSP looks for compile commands in the root of the source tree; +cmake creates them in the root of its build directory. + +``` +$ cd xo-simulator +$ ln -s build/compile_commands.json +``` + +### display cmake variables + +- `-L` list variables +- `-A` include 'advanced' variables +- `-H` include help text + +``` +$ cd xo-simulator/build +$ cmake -LAH +``` diff --git a/xo-simulator/cmake/simulatorConfig.cmake.in b/xo-simulator/cmake/simulatorConfig.cmake.in new file mode 100644 index 00000000..4f721e9f --- /dev/null +++ b/xo-simulator/cmake/simulatorConfig.cmake.in @@ -0,0 +1,13 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +find_dependency(reactor) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-simulator/cmake/xo-bootstrap-macros.cmake b/xo-simulator/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..aba31169 --- /dev/null +++ b/xo-simulator/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (NOT XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/xo-simulator/include/xo/simulator/EventSink.hpp b/xo-simulator/include/xo/simulator/EventSink.hpp new file mode 100644 index 00000000..2804b1e6 --- /dev/null +++ b/xo-simulator/include/xo/simulator/EventSink.hpp @@ -0,0 +1,19 @@ +/* @file EventSink.hpp */ + +#pragma once + +namespace xo { + namespace sim { + /* something that observes (consumes) events of type T. + * we deliberately hide event sinks from top-level of simulator scaffold, + * so that we don't have to impose a common event type for T + */ + template + class EventSink { + public: + void operator()(T const & x); + }; /*EventSink*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end EventSink.hpp */ diff --git a/xo-simulator/include/xo/simulator/Simulator.hpp b/xo-simulator/include/xo/simulator/Simulator.hpp new file mode 100644 index 00000000..da87b555 --- /dev/null +++ b/xo-simulator/include/xo/simulator/Simulator.hpp @@ -0,0 +1,299 @@ +/* @file Simulator.hpp */ + +#pragma once + +#include "xo/reactor/Reactor.hpp" +#include "SourceTimestamp.hpp" +#include "xo/reactor/ReactorSource.hpp" +#include "xo/refcnt/Refcounted.hpp" +//#include "time/Time.hpp" +#include + +namespace xo { + namespace sim { + class TimeSlip; + + /* delay state-changing simulator command while handling + * simulator events. need this to permit reentrancy + */ + struct ReentrantSimulatorCmd { + enum SimulatorCmdEnum { NotifySourcePrimed, CompleteAddSource, CompleteRemoveSource }; + + using ReactorSource = xo::reactor::ReactorSource; + + public: + ReentrantSimulatorCmd() = default; + ReentrantSimulatorCmd(SimulatorCmdEnum cmd, + rp const & src) + : cmd_{cmd}, src_{src} {} + + static ReentrantSimulatorCmd notify_source_primed(rp const & src) { + return ReentrantSimulatorCmd(NotifySourcePrimed, src); + } + + static ReentrantSimulatorCmd complete_add_source(rp const & src) { + return ReentrantSimulatorCmd(CompleteAddSource, src); + } + + static ReentrantSimulatorCmd complete_remove_source(rp const & src) { + return ReentrantSimulatorCmd(CompleteRemoveSource, src); + } + + SimulatorCmdEnum cmd() const { return cmd_; } + rp const & src() const { return src_; } + + private: + /* NotifySourcePrimed: deferred Simulator.notify_source_primed(.src) + * CompleteAddSource: deferred Simulator.complete_add_source(.src) + * CompleteRemoveSource: deferred Simulator.complete_remove_source(.src) + */ + SimulatorCmdEnum cmd_ = NotifySourcePrimed; + /* if .cmd=NotifySourcePrimed|CompleteAddSource|CompleteRemoveSource: reactor source */ + rp src_; + }; /*ReentrantSimulatorCmd*/ + + /* Generic simulator + * + * - time advances monotonically + * - applies a modifiable set of sources + * + * A Simulator isn't an example of a Reactor, + * because it can't work with arbitrary Sources + * (may find it expedient to fake this later, + * so we can easily adopt + * Source.notify_reactor_add() / Source.notify_reactor_remove()) + * in a simulation context + */ + class Simulator : public reactor::Reactor { + public: + using ReactorSourcePtr = xo::reactor::ReactorSourcePtr; + using ReactorSource = xo::reactor::ReactorSource; + using utc_nanos = xo::time::utc_nanos; + using nanos = xo::time::nanos; + + public: + ~Simulator(); + + static rp make(utc_nanos t0); + + /* value of .t0() is estabished in ctor. + * it will not change except across call to .advance_one() + * in particular .add_source() does not change .t0() + */ + utc_nanos t0() const { return t0_; } + + /* timestamp of last event delivered */ + utc_nanos last_tm() const { return last_tm_; } + /* total #of events delivered since sim start */ + uint64_t n_event() const { return n_event_; } + + /* true iff all simulation source are exhausted + * a newly-created simulator is in the exhausted state; + * it may transition to non-exhausted state across + * call to .add_source() + */ + bool is_exhausted() const { return this->src_v_.empty(); } + + /* true iff src has been added to this simulator + * (by .add_source()) + */ + bool is_source_present(ref::brw src) const; + + /* promise: + * .next_tm() > .t0() || .is_exhausted() + * + * .next_tm() may decrease across .add_source() call + * .next_tm() may increase across .advance_one() call + */ + utc_nanos next_tm() const; + + /* returns source that will be used for next simulator event. + * nullptr if no remaining sources + */ + ReactorSource * next_src() const; + + /* cross-reference realtime to simulated time, + * for throttled replay + */ + TimeSlip timeslip() const; + + /* compute throttled real time for next event. + * caller supplies: + * 1. a pair of timesstamps xref_ts = (sim_tm, real_tm) + * - xref_ts.sim_tm is time in simulation coords of last event + * (i.e. most recent available value of .last_tm()) + * - xref_ts.real_tm is wall clock time associated with simtime + * 2. a replay factor, representing desired + * elapsed_simulation_time : elapsed_real_time + * + * return value is realtime delay to apply before next simulated event, + * in order to maintain desired replay factor + * + * The incremental api here is intended to be used from a python thread. + * + * Expect python simulation loop like: + * import pysimulator + * + * replay_factor = 1.0 + * sim = pysimulator.Simulator.make(t0) + * sim.run_one() + * tslip = sim.timeslip() + * while(True): + * dt = sim.throttled_event_dt(tslip, replay_factor) + * sleep(dt) + * sim.run_one() + * + * This allows sleep() to be invoked from python, + * which plays nicely with python threading model + */ + nanos throttled_event_dt(TimeSlip xref_ts, + double replay_factor) const; + + /* current contents of simulation heap, in increasing time order. + * copies heap to drain it in heap order + */ + std::vector heap_contents() const; + + /* print heap contents to *p_scope. intended for diagnostics */ + void log_heap_contents(xo::scope * p_scope) const; + + /* human-readable string identifying this simulator */ + std::string display_string() const; + + /* emit the first available event from a single simulation source. + * resolve ties arbitrarily. + * + * returns the #of events dispatched + * (expect this always = 1) + */ + std::uint64_t advance_one_event(); + + /* run simulation until earliest event time t satisfies t > t1 + */ + void run_until(utc_nanos t1); + + /* run simulation at realtime speed, throttling according to replay_factor, + * until either: + * - simulation exhausted + * - n events handled, if n>0 + * - sim clock reaches t1, if t1>t0 + * + * see also .run_one(), .run_until(), .run_n(), .run() + * + * note: this method not suitable for use from python wrappers; + * would hold GIL until complete. + * for that use case better to implement throttled sim loop + * in python + * + * t1. if > .t0, limit sim to events with t < t1 + * n. if > 0, sim at most n events + * replay_factor. throttle sim to keep + * {elapsed sim time} <= replay_factor * {elapsed real time} + * return. #of events simmed + */ + uint64_t run_throttled_until(utc_nanos t1, + int32_t n, + double replay_factor); + + // ----- inherited from Reactor ----- + + /* notification when nonprimed source becomes primed + */ + virtual void notify_source_primed(ref::brw src) override; + + /* add a new simulation source. + * event that precede .t0 will be discarded. + * + * returns true if src added; false if already present + */ + virtual bool add_source(ref::brw src) override; + + /* remove simulation source. + * returns true if src removed; false if was not present + * + * (not typically needed for simulations) + */ + virtual bool remove_source(ref::brw src) override; + + /* synonym for .advance_one_event() */ + virtual std::uint64_t run_one() override; + + private: + explicit Simulator(utc_nanos t0); + + /* updates source timestamp in simulation heap. + * preserves + * + * Require: + * - src->is_primed() + * - .sim_heap[.sim_heap.size - 1] already refers to src + * + * need_pop_flag, if true, src is at back of heap vector, + * need to pop before re-inserting. + */ + void heap_update_source(ReactorSource * src, + bool need_pop_flag); + + /* insert source into .sim_heap. + * increase sim_heap.size() by +1 + */ + void heap_insert_source(ReactorSource * src); + + /* complete any reentrant work encountered + * while deliverying another event + */ + void complete_delivery_work(); + + /* complete reentrant call to .add_source() */ + void complete_add_source(ref::brw src); + /* complete reentrant call to .remove_source() */ + void complete_remove_source(ref::brw src); + + friend class RaiiDeliveryWork; + + private: + /* simulation heap: + * each unexhausted source appears + * exactly once, in increasing time order of next event + * + * Invariant: + * - all sources s in .sim_heap satisfy: + * - s.is_exhausted() = false + * - s.t0() >= .t0 + */ + std::vector sim_heap_; + + /* initial simulation clock */ + utc_nanos t0_; + + /* time of most recent simulated event */ + utc_nanos last_tm_; + + /* #of simulated events handled */ + uint64_t n_event_ = 0; + + /* simulation sources + * Invariant: + * - all source s in .src_v satisfy: + * EITHER + * 1. s.is_exhausted() = true + * OR + * 2.1 s.is_exhausted() = false + * 2.2 s.t0() >= .t0 + */ + std::vector src_v_; + + /* reentrancy protection. set during .advance_one_event() */ + bool delivery_in_progress_ = false; + + /* when certain Simulator methods are invoked + * while in the midst of delivering another event, + * must defer until delivery has completed + */ + std::vector reentrant_cmd_v_; + }; /*Simulator*/ + + } /*namespace sim*/ +} /*namespace xo*/ + +/* end Simulator.hpp */ diff --git a/xo-simulator/include/xo/simulator/SourceTimestamp.hpp b/xo-simulator/include/xo/simulator/SourceTimestamp.hpp new file mode 100644 index 00000000..380e81c4 --- /dev/null +++ b/xo-simulator/include/xo/simulator/SourceTimestamp.hpp @@ -0,0 +1,107 @@ +/* file SourceTimestamp.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/reactor/ReactorSource.hpp" + +namespace xo { + namespace sim { + /* remember a timestamp for a simulation source; + * use to insert a source into simulation heap. + * don't want to use SimulationSource.t0, so that we can + * promise heap invariants without reying on + * any behavior of SimulationSource. + * + * Note: Need to resolve ties between different sources, + * if they coincide on timestamp of next event. + * For now use SimulationSource address + */ + class SourceTimestamp { + public: + using ReactorSource = xo::reactor::ReactorSource; + using utc_nanos = xo::time::utc_nanos; + + public: + SourceTimestamp(utc_nanos t0, + ReactorSource * src) + : t0_(t0), src_(src) {} + + static int32_t compare(SourceTimestamp const & x, + SourceTimestamp const & y) { + using xo::time::utc_nanos; + using xo::time::nanos; + + nanos dt = x.t0_ - y.t0_; + + if(dt < nanos(0)) + return -1; + else if(dt > nanos(0)) + return +1; + + /* timestamps are equal */ + + std::ptrdiff_t dptr = (x.src() - y.src()); + + return dptr; + } /*compare*/ + + utc_nanos t0() const { return t0_; } + ReactorSource * src() const { return src_; } + + void display(std::ostream & os) const; + std::string display_string() const; + + private: + /* timestamp for this source */ + utc_nanos t0_; + /* simulation source + * promise: + * - src.t0() >= .t0 || src.is_exhausted + */ + ReactorSource * src_ = nullptr; + }; /*SourceTimestamp*/ + + inline bool operator==(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) == 0; + } /*operator==*/ + + inline bool operator<(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) < 0; + } /*operator<*/ + + inline bool operator<=(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) <= 0; + } /*operator<=*/ + + inline bool operator>(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) > 0; + } /*operator>*/ + + inline bool operator>=(SourceTimestamp const & x, + SourceTimestamp const & y) + { + return SourceTimestamp::compare(x, y) >= 0; + } /*operator>=*/ + + inline std::ostream & + operator<<(std::ostream & os, + SourceTimestamp const & x) + { + x.display(os); + return os; + } /*operator<<*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end SourceTimestamp.hpp*/ diff --git a/xo-simulator/include/xo/simulator/TimeSlip.hpp b/xo-simulator/include/xo/simulator/TimeSlip.hpp new file mode 100644 index 00000000..3399c8ff --- /dev/null +++ b/xo-simulator/include/xo/simulator/TimeSlip.hpp @@ -0,0 +1,39 @@ +/* file TimeSlip.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +//#include "time/Time.hpp" + +namespace xo { + namespace sim { + /* helper class for a throttled simulation, + * where we want simulated time to evolve at a constant rate, + * relative to real elapsed time. + * + * A TimeSlip instance pins + * simulation-time coordinates to realtime coordinates + */ + class TimeSlip { + public: + using utc_nanos = xo::time::utc_nanos; + + public: + TimeSlip(utc_nanos sim_tm, utc_nanos real_tm) + : sim_tm_{sim_tm}, real_tm_{real_tm} {} + + utc_nanos sim_tm() const { return sim_tm_; } + utc_nanos real_tm() const { return real_tm_; } + + private: + utc_nanos sim_tm_; + utc_nanos real_tm_; + }; /*TimeSlip*/ + } /*namespace sim*/ + +} /*namespace xo*/ + + +/* end TimeSlip.hpp */ diff --git a/xo-simulator/include/xo/simulator/init_simulator.hpp b/xo-simulator/include/xo/simulator/init_simulator.hpp new file mode 100644 index 00000000..43137575 --- /dev/null +++ b/xo-simulator/include/xo/simulator/init_simulator.hpp @@ -0,0 +1,20 @@ +/* file init_simulator.hpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#pragma once + +#include "xo/subsys/Subsystem.hpp" + +namespace xo { + enum S_simulator_tag {}; + + template<> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_simulator.hpp */ diff --git a/xo-simulator/src/simulator/CMakeLists.txt b/xo-simulator/src/simulator/CMakeLists.txt new file mode 100644 index 00000000..2e8aaf73 --- /dev/null +++ b/xo-simulator/src/simulator/CMakeLists.txt @@ -0,0 +1,17 @@ +# xo-simulator/src/simulator/CMakeLists.txt + +set(SELF_LIB simulator) +set(SELF_SRCS + Simulator.cpp SourceTimestamp.cpp + init_simulator.cpp) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# external dependencies + +# note: changes to xo_dependency() calls here +# must coordinate with find_dependency() calls +# in xo-simulator/cmake/simulatorConfig.cmake.in +# +xo_dependency(${SELF_LIB} reactor) diff --git a/xo-simulator/src/simulator/Simulator.cpp b/xo-simulator/src/simulator/Simulator.cpp new file mode 100644 index 00000000..3d64c1b4 --- /dev/null +++ b/xo-simulator/src/simulator/Simulator.cpp @@ -0,0 +1,550 @@ +/* @file Simulator.cpp */ + +//#include "time/Time.hpp" /*need this 1st for tag(., time_point)*/ +#include "init_simulator.hpp" +#include "Simulator.hpp" +#include "TimeSlip.hpp" +#include "xo/indentlog/scope.hpp" +#include +#include +#include + +namespace xo { + using xo::reactor::ReactorSource; + using xo::ref::brw; + using xo::time::utc_nanos; + using xo::time::nanos; + + namespace sim { + class RaiiDeliveryWork { + public: + RaiiDeliveryWork(Simulator * sim) : sim_{sim} { + this->sim_->delivery_in_progress_ = true; + } + + ~RaiiDeliveryWork() { + this->sim_->delivery_in_progress_ = false; + this->sim_->complete_delivery_work(); + } + + Simulator * sim_ = nullptr; + }; /*RaiiDeliveryWork*/ + + rp + Simulator::make(utc_nanos t0) { + return new Simulator(t0); + } /*make*/ + + Simulator::Simulator(utc_nanos t0) : t0_(t0) + { + XO_SUBSYSTEM_REQUIRE(simulator); + } /*ctor*/ + + Simulator::~Simulator() { + scope log(XO_ENTER0(verbose), "clear heap.."); + + this->sim_heap_.clear(); + + if (log.enabled()) { + log("visit .src_v", xtag("size", this->src_v_.size())); + for (size_t i=0; isrc_v_.size(); ++i) { + log(":src_v[", i, "] ", this->src_v_[i].get()); + } + } + + log && log("clear .src_v", xtag("size", this->src_v_.size())); + + this->src_v_.clear(); + } /*dtor*/ + + bool + Simulator::is_source_present(brw src) const + { + for (ReactorSourcePtr const & s : this->src_v_) { + if (s == src) + return true; + } + + return false; + } /*is_source_pesent*/ + + utc_nanos + Simulator::next_tm() const { + if(this->sim_heap_.empty()) { + /* 0 remaining events in simulator */ + return this->t0(); + } + + return this->sim_heap_.front().t0(); + } /*next_tm*/ + + ReactorSource* + Simulator::next_src() const { + if (this->sim_heap_.empty()) { + /* 0 remaining events in simulator */ + return nullptr; + } + + return this->sim_heap_.front().src(); + } /*next_src*/ + + void + Simulator::notify_source_primed(brw src) + { + scope log(XO_ENTER1(always, src->debug_sim_flag())); + + brw sim_src + = brw::from(src); + + log && log(xtag("src", (sim_src.get() != nullptr)), + xtag("src.name", src->name())); + + if(!sim_src) + return; + + log && log(xtag("sim.name", sim_src->name()), + xtag("src.current_tm", sim_src->sim_current_tm()), + xtag("sim_heap.size", this->sim_heap_.size())); + + if (this->delivery_in_progress_) { + log && log("reentrant call to .notify_source_primed(), defer", + xtag("src.name", src->name())); + + /* defer reentrant work until delivery completes + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::notify_source_primed(src.promote())); + } else { + /* inform Simulator when a source transitions from + * 'notready' to 'ready'. + * + * this means: + * - source knows its next event + * - source should be put back into .sim_heap + */ + this->heap_insert_source(sim_src.get()); + } + + //if (lscope.enabled()) + // this->log_heap_contents(&lscope); + } /*notify_source_primed*/ + + void + Simulator::complete_add_source(brw src) + { + /* also add to simulation heap */ + this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), + src.get())); + + /* use std::greater<> because we need a min-heap; + * smallest timestamp at the front + */ + std::push_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + } /*complete_add_source*/ + + bool + Simulator::add_source(brw sim_src) + { + scope log(XO_ENTER1(always, sim_src->debug_sim_flag())); + + log && log("enter", + xtag("src", sim_src.get()), + xtag("src.name", sim_src->name())); + + if(!sim_src || this->is_source_present(sim_src)) + return false; + + log && log("advance to t0", + xtag("t0", this->t0())); + + sim_src->sim_advance_until(this->t0(), false /*!replay_flag*/); + + this->src_v_.push_back(sim_src.promote()); + + if(sim_src->is_exhausted()) { + log && log("source exhausted!"); + } else { + sim_src->notify_reactor_add(this /*reactor*/); + + log && log(xtag("src.sim_current_tm", sim_src->sim_current_tm())); + + if (sim_src->is_empty()) { + log && log("empty source, do not insert into .sim_heap"); + + /* if source is empty, don't add to sim heap yet. + * when source becomes non-empty, source will invoke + * .notify_source_primed() + * which will insert it into .sim_heap[] + */ + ; + } else if (this->delivery_in_progress_) { + log && log("reentrant add non-empty source, delay"); + + /* defer reentrant work until delivery completes + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::complete_add_source(sim_src.promote())); + } else { + log && log("non-empty source, add to .sim_heap"); + + this->complete_add_source(sim_src); + } + } + + return true; + } /*add_source*/ + + void + Simulator::complete_remove_source(brw sim_src) + { + /* rebuild .sim_heap, with sim_src removed */ + std::vector sim_heap2; + + for(SourceTimestamp const & item : this->sim_heap_) { + if(item.src() == sim_src.get()) { + /* item refers to the source we are removing -> discard */ + ; + } else { + sim_heap2.push_back(item); + + std::push_heap(sim_heap2.begin(), + sim_heap2.end(), + std::greater()); + } + + /* now discard .sim_heap, replacing with sim_heap2 */ + this->sim_heap_ = std::move(sim_heap2); + } + } /*complete_remove_source*/ + + bool + Simulator::remove_source(brw sim_src) + { + scope log(XO_DEBUG(sim_src->debug_sim_flag())); + + log && log("enter", + xtag("src", sim_src.get()), + xtag("src.name", sim_src->name())); + + //brw sim_src = brw::from(src); + + if(!sim_src || !this->is_source_present(sim_src)) + return false; + + /* WARNING: O(n)implementation here */ + + if (this->delivery_in_progress_) { + /* defer reentrant work until delivery completes. + * see .complete_delivery_work() + */ + this->reentrant_cmd_v_.push_back + (ReentrantSimulatorCmd::complete_remove_source(sim_src.promote())); + } else { + this->complete_remove_source(sim_src); + } + + return true; + } /*remove_source*/ + + std::uint64_t + Simulator::run_one() { + return this->advance_one_event(); + } /*run_one*/ + + void + Simulator::heap_update_source(ReactorSource * src, bool need_pop_flag) + { + /* Require: + * .sim_heap[.sim_heap.size - 1] already refers to src + * just updating timestamp here + */ + + std::size_t simheap_z + = this->sim_heap_.size(); + + scope log(XO_DEBUG(src->debug_sim_flag()), + xtag("src.name", src->name()), + xtag("simheap_z", simheap_z), + xtag("src.sim_current_tm", src->sim_current_tm())); + + if (need_pop_flag) + this->sim_heap_.pop_back(); + /* re-insert at new timestamp */ + this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), src)); + + /* use std::greater<> because we need a min-heap; + * smallest timestamp at the front + */ + std::push_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + } /*heap_update_source*/ + + void + Simulator::heap_insert_source(ReactorSource * src) + { + /* santify check -- src should not currently appear in heap */ + for (SourceTimestamp const & src_recd : this->sim_heap_) { + if(src_recd.src() == src) { + /* uh oh. src is already present in heap! */ + assert(false); + } + } + + // don't need this: .heap_update_source() will insert + //this->sim_heap_.push_back(SourceTimestamp(src->sim_current_tm(), src)); + + this->heap_update_source(src, false /*!need_pop_flag*/); + } /*heap_insert_source*/ + + void + Simulator::complete_delivery_work() + { + for (ReentrantSimulatorCmd const & cmd : this->reentrant_cmd_v_) { + scope log(XO_DEBUG(cmd.src() && cmd.src()->debug_sim_flag()), + "complete reentrant work", + xtag("src.name", cmd.src()->name())); + + switch (cmd.cmd()) { + case ReentrantSimulatorCmd::NotifySourcePrimed: + this->notify_source_primed(cmd.src()); + break; + case ReentrantSimulatorCmd::CompleteAddSource: + this->complete_add_source(cmd.src()); + break; + case ReentrantSimulatorCmd::CompleteRemoveSource: + this->complete_remove_source(cmd.src()); + break; + } + + //if (lscope.enabled()) + // this->log_heap_contents(&lscope); + } + + this->reentrant_cmd_v_.clear(); + } /*complete_delivery_work*/ + + TimeSlip + Simulator::timeslip() const + { + utc_nanos real_tm = std::chrono::system_clock::now(); + utc_nanos sim_tm = this->next_tm(); + + return TimeSlip(sim_tm, real_tm); + } /*timeslip*/ + + nanos + Simulator::throttled_event_dt(TimeSlip xref, + double replay_factor) const + { + if (replay_factor <= 0.0) + replay_factor = 1e-6; + + /* hi_sim_tm: simtime for next event to be handled */ + utc_nanos hi_sim_tm = this->next_tm(); + /* desired elapsed /real time/ from start of simulation to + * to when simulation handles event @ hi_sim_tm + */ + nanos sim_dt = (hi_sim_tm - xref.sim_tm()); + auto hi_real_tm = (xref.real_tm() + + std::chrono::duration_cast(sim_dt / replay_factor)); + utc_nanos now_tm = std::chrono::system_clock::now(); + + if (now_tm < hi_real_tm) + return hi_real_tm - now_tm; + else + return nanos(0); + } /*next_throttled_tm*/ + + std::vector + Simulator::heap_contents() const + { + std::vector heap = this->sim_heap_; + std::vector retval; + + while (!heap.empty()) { + retval.push_back(heap.front()); + + std::pop_heap(heap.begin(), heap.end(), + std::greater()); + heap.pop_back(); + } + + return retval; + } /*heap_contents*/ + + void + Simulator::log_heap_contents(scope * p_scope) const + { + std::vector heap = this->sim_heap_; + + p_scope->log("/ sim heap contents:"); + p_scope->log("| t0 name n_in_ev n_queued_out_ev n_out_ev"); + + while(!heap.empty()) { + SourceTimestamp const & ts = heap.front(); + + p_scope->log("|" + , " ", ts.t0() + , " ", ts.src()->name() + , " ", ts.src()->n_queued_out_ev() + , " ", ts.src()->n_out_ev()); + + std::pop_heap(heap.begin(), heap.end(), + std::greater()); + heap.pop_back(); + } + + p_scope->log("\\"); + } /*print_heap_contents*/ + + std::string + Simulator::display_string() const + { + return ""; + } /*display_string*/ + + std::uint64_t + Simulator::advance_one_event() + { + bool debug_flag = (this->loglevel() <= log_level::chatty); + + if(this->sim_heap_.empty()) { + scope log(XO_DEBUG(debug_flag)); + + /* nothing todo */ + return 0; + } + + uint32_t old_heap_z = this->sim_heap_.size(); + + /* *src is source with earliest timestamp */ + ReactorSource * src + = this->sim_heap_.front().src(); + + utc_nanos src_tm = this->sim_heap_.front().t0(); + + scope log(XO_DEBUG(debug_flag), + xtag("threshold-loglevel", this->loglevel()), + xtag("src", src != nullptr), + xtag("src.name", src->name()), + xtag("sim.src_tm", src_tm), + xtag("src.sim_current_tm", src->sim_current_tm()), + xtag("heap_z", old_heap_z)); + + /* NOTE: src.current_tm() isn't preserved across + * call to src.deliver_one() + */ + uint64_t retval = 0; + + { + RaiiDeliveryWork raii_work(this); + + retval = src->deliver_one(); + + this->last_tm_ = src_tm; + this->n_event_ += retval; + + /* note that src.t0 may have advanced */ + + /* moves just-consumed timestamp event for src + * to back of .sim_heap + */ + std::pop_heap(this->sim_heap_.begin(), + this->sim_heap_.end(), + std::greater()); + + /* now .sim_heap[.sim_heap.size() = 1].src() is src, + * with stale timestamp + */ + + if(src->is_exhausted() || src->is_notprimed()) { + /* remove src from .sim_ + * - if src->is_exhausted(), permanently + * - if src->is_notready(), until source calls + * .notify_source_ready() + */ + this->sim_heap_.pop_back(); + } else { + this->heap_update_source(src, true /*need_pop_flag*/); + } + + assert(raii_work.sim_); + } + + return retval; + } /*advance_one_event*/ + + void + Simulator::run_until(utc_nanos t1) + { + assert(!this->delivery_in_progress_); + + while(!this->is_exhausted()) { + utc_nanos t = this->next_tm(); + + if(t > t1) + break; + + this->advance_one_event(); + } /*loop until done*/ + } /*run_until*/ + + uint64_t + Simulator::run_throttled_until(utc_nanos t1, + int32_t n_max, + double replay_factor) + { + Subsystem::verify_all_initialized(); + + scope log(XO_ENTER0(info)); + + assert(!this->delivery_in_progress_); + + uint64_t n = 0; + + if(!this->is_exhausted()) { + n += this->run_one(); + } + + /* cross-reference real time with sim time */ + TimeSlip tslip = this->timeslip(); + + while(!this->is_exhausted()) { + if ((n_max > 0) && (n >= static_cast(n_max))) { + /* reached limit on #of events simmed */ + return n; + } + + if ((t1 > this->t0()) && (this->next_tm() > t1)) { + /* reached limit on sim time */ + return n; + } + + /* if sim time passing faster than realtime (scaled by replay_factor), + * wait for real elapsed time to catch up + */ + nanos wait_dt = this->throttled_event_dt(tslip, replay_factor); + + if (wait_dt > std::chrono::milliseconds(1)) { + log && log(xtag("sleep_dt", wait_dt)); + + std::this_thread::sleep_for(wait_dt); + } else { + /* don't bother throttling for period less than 1ms, linux != rtos */ + } + + n += this->run_one(); + } + + return n; + } /*run_throttled_until*/ + + } /*namespace sim*/ +} /*namespace xo*/ + +/* end Simulator.cpp */ diff --git a/xo-simulator/src/simulator/SourceTimestamp.cpp b/xo-simulator/src/simulator/SourceTimestamp.cpp new file mode 100644 index 00000000..f425beeb --- /dev/null +++ b/xo-simulator/src/simulator/SourceTimestamp.cpp @@ -0,0 +1,32 @@ +/* file SourceTimestamp.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "SourceTimestamp.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/tostr.hpp" + +namespace xo { + using xo::xtag; + using xo::tostr; + + namespace sim { + void + SourceTimestamp::display(std::ostream & os) const + { + os << "(src_)); + os << ">"; + } /*display*/ + + std::string + SourceTimestamp::display_string() const + { + return tostr(*this); + } /*display_string*/ + } /*namespace sim*/ +} /*namespace xo*/ + +/* end SourceTimestamp.cpp */ diff --git a/xo-simulator/src/simulator/init_simulator.cpp b/xo-simulator/src/simulator/init_simulator.cpp new file mode 100644 index 00000000..33d039c3 --- /dev/null +++ b/xo-simulator/src/simulator/init_simulator.cpp @@ -0,0 +1,31 @@ +/* file init_simulator.cpp + * + * author: Roland Conybeare, Sep 2022 + */ + +#include "init_simulator.hpp" +#include "xo/reactor/init_reactor.hpp" + +namespace xo { + void + InitSubsys::init() + { + } /*init*/ + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* subsystem dependencies for simulator/ */ + retval ^= InitSubsys::require(); + + /* simulator/'s own initialization code */ + retval ^= XO_SUBSYSTEM_PROVIDE(simulator, &init); + //retval ^= Subsystem::provide("simulator", &init); + + return retval; + } /*require*/ +} /*namespace xo*/ + +/* end init_simulator.cpp */