Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • weber/simcore-rs
  • markeffl/simcore-rs
2 results
Show changes
Commits on Source (40)
/target
/.idea/
/uml/
/odemx-lite/related documents/
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
version = 4
[[package]]
name = "autocfg"
version = "1.0.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bstr"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bumpalo"
version = "3.4.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cast"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "criterion"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
"criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"oorandom 11.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"plotters 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
"tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "criterion-plot"
version = "0.4.3"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam-deque"
version = "0.7.3"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.3"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "either"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "half"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hermit-abi"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itertools"
version = "0.9.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "itoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "js-sys"
version = "0.3.42"
name = "getrandom"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"wasm-bindgen 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.11"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
name = "libm"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memoffset"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "num-traits"
version = "0.2.12"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg",
"libm",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)",
"zerocopy",
]
[[package]]
name = "oorandom"
version = "11.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "plotters"
version = "0.2.15"
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"js-sys 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"web-sys 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-ident",
]
[[package]]
name = "ppv-lite86"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro2"
version = "1.0.19"
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2",
]
[[package]]
name = "quote"
version = "1.0.7"
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.7.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha",
"rand_core",
"zerocopy",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand_hc"
version = "0.2.0"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"getrandom",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
name = "rand_distr"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463"
dependencies = [
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits",
"rand",
]
[[package]]
name = "rayon"
version = "1.3.1"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-queue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-automata"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_cbor"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"half 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_derive"
version = "1.0.114"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "simcore-rs"
version = "0.1.0"
dependencies = [
"criterion 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand",
"rand_distr",
"rayon",
]
[[package]]
name = "syn"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tinytemplate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.65"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.65"
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro-support 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.65"
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "web-sys"
version = "0.3.42"
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"js-sys 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags",
]
[[package]]
name = "winapi"
version = "0.3.9"
name = "zerocopy"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"zerocopy-derive",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-util"
version = "0.1.5"
name = "zerocopy-derive"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
dependencies = [
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
"checksum bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
"checksum criterion 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "70daa7ceec6cf143990669a04c7df13391d55fb27bd4079d252fca774ba244d8"
"checksum criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
"checksum crossbeam-queue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279"
"checksum csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum half 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177"
"checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
"checksum js-sys 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)" = "52732a3d3ad72c58ad2dc70624f9c17b46ecd0943b9a4f1ee37c4c18c5d983e2"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.73 (registry+https://github.com/rust-lang/crates.io-index)" = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9"
"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
"checksum memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
"checksum oorandom 11.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c"
"checksum plotters 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb"
"checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
"checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
"checksum rayon 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
"checksum rayon-core 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
"checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
"checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
"checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
"checksum serde_cbor 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622"
"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
"checksum syn 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "fb7f4c519df8c117855e19dd8cc851e89eb746fe7a73f0157e0d95fdec5369b0"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f"
"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
"checksum wasm-bindgen 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "f3edbcc9536ab7eababcc6d2374a0b7bfe13a2b6d562c5e07f370456b1a8f33d"
"checksum wasm-bindgen-backend 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "89ed2fb8c84bfad20ea66b26a3743f3e7ba8735a69fe7d95118c33ec8fc1244d"
"checksum wasm-bindgen-macro 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "eb071268b031a64d92fc6cf691715ca5a40950694d8f683c5bb43db7c730929e"
"checksum wasm-bindgen-macro-support 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "cf592c807080719d1ff2f245a687cbadb3ed28b2077ed7084b47aba8b691f2c6"
"checksum wasm-bindgen-shared 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "72b6c0220ded549d63860c78c38f3bcc558d1ca3f4efa74942c536ddbbb55e87"
"checksum web-sys 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)" = "8be2398f326b7ba09815d0b403095f34dd708579220d099caae89be0b32137b2"
"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[package]
name = "simcore-rs"
version = "0.1.0"
authors = ["Dorian Weber <weber@informatik.hu-berlin.de>"]
edition = "2018"
authors = [
"Dorian Weber <weber@informatik.hu-berlin.de>",
]
edition = "2024"
[dev-dependencies]
rand = { version = "0.9", default-features = false, features = ["small_rng"] }
rand_distr = "0.5"
rayon = "1.10"
[profile.release]
lto = true
lto = "thin"
debug = "full"
opt-level = 3
[dependencies]
rand = { version = "0.7", default-features = false, features = ["small_rng"] }
[dev-dependencies]
criterion = "0.3.3"
[[bench]]
name = "barbershop"
harness = false
[profile.bench]
lto = true
debug = false
Copyright 2024 Dorian Weber
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the “Software”), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
# A Minimalistic Simulation Framework in Rust
## Introduction
This Rust project provides a minimally-viable library for creating and running simulations using asynchronous processes. By leveraging Rust's concurrency features, it allows you to simulate various scenarios, making it a valuable tool for studying the mechanics of simulation libraries in projects involving complex, process-based models.
This simulation core is not meant to be used in production code, see [odem-rs] for an earnest attempt.
## Features
The project includes:
- **Core Library (`lib.rs`)**: Defines reusable components for building simulators.
- **Utility Modules (`util.rs`)**: Provides support for common simulation tasks.
- **Example Models**: Demonstrates different aspects of the framework through three examples:
- `barbershop`
- `ferry`
- `philosophers`
- **Decompressor-Tokenizer (`bin/coroutines.rs`)**: An educational example with an executor, a corresponding spawner, and a channel that shows the mechanism without using unsafe code.
- **Decompressor-Tokenizer (`c/coroutines.c`)**: The same example implemented manually in C to illustrate the transformation performed by the Rust compiler on `async`/`await`.
## Getting Started
### Prerequisites
- **Rust Compiler**: Latest stable version is recommended. [Install Rust][Rust-compiler]
- no other dependencies
### Building the Project
To build the project, run:
```sh
cargo build
```
### Running the Examples
The examples are located in the `examples` directory and demonstrate different types of simulations:
- **Barbershop**: A discrete event simulation of a barbershop where customers arrive randomly, wait if the barber is busy, receive service, and depart. It tracks customer wait times and provides statistical analysis over the simulated period. This is a minimalistic model containing processes and time- and state-based synchronization.
- **Ferry**: Simulates a car-ferry system where cars arrive at multiple harbors and wait to be transported by ferries to other harbors. It tracks statistics like car wait times, ferry cargo lengths, and ferry load times. This example demonstrates complex synchronization, featuring channels and speculative execution.
- **Philosophers**: Simulates the classic Dining Philosophers problem, where philosophers alternate between thinking and eating, requiring coordination to avoid deadlocks. It tracks the time until a deadlock occurs and collects statistical data over multiple simulation runs. This example highlights parallel execution of multiple simulations and concurrent resource access.
To run an example, use:
```sh
cargo run --example <example_name>
```
For example, to run the barbershop simulation:
```sh
cargo run --example barbershop
```
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
## Acknowledgments
Special thanks to the contributors of the Rust community for creating powerful tools that make projects like this
possible. Extra-special thanks to Lukas Markeffsky for helping me to refine this library both through fruitful
discussions and by resolving its soundness problems through a refactoring of the code.
[Rust-compiler]: https://rust.sh
[odem-rs]: https://crates.io/crates/odem-rs
use simcore_rs::{Time, SimContext, Facility, simulation};
use rand::{distributions::Uniform, rngs::SmallRng, SeedableRng, Rng};
use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main, BatchSize};
use std::time::Duration;
const SEED_A : u64 = 100000;
const SEED_S : u64 = 200000;
const RANGE : u32 = 8;
const STEP : Time = 100000.0;
fn barbershop(c: &mut Criterion) {
let mut group = c.benchmark_group("Barbershop");
let mut rng_a = SmallRng::seed_from_u64(SEED_A);
let mut rng_s = SmallRng::seed_from_u64(SEED_S);
group.measurement_time(Duration::from_secs_f64(30.0));
group.confidence_level(0.99);
for stop_time in (1..=RANGE).map(|c| Time::from(c)*STEP) {
group.bench_with_input(
BenchmarkId::from_parameter(stop_time),
&stop_time,
|b, &stop_time|
b.iter_batched(
|| {
(SmallRng::from_rng(&mut rng_a).unwrap(),
SmallRng::from_rng(&mut rng_s).unwrap())
},
|(rng_a, rng_s)| {
let shop = BarberShop {
stop_time, rng_a, rng_s, joe: &Facility::new()
};
simulation(|sim| shop.actions(sim));
},
BatchSize::SmallInput
)
);
}
group.finish();
}
criterion_group!(benches, barbershop);
criterion_main!(benches);
/* *************************** Barbershop Example *************************** */
/// Barbershop process.
struct BarberShop<'j> {
stop_time: Time, rng_a: SmallRng, rng_s: SmallRng, joe: &'j Facility
}
impl<'j> BarberShop<'j> {
async fn actions(self, sim: SimContext<'j>) {
// unpack the barber shop structure for easier access
let Self {
stop_time, mut rng_a, mut rng_s, joe
} = self;
// activate a process to generate the customers
sim.activate(async move {
let dist = Uniform::new(12.0, 24.0);
// wait some time before activating the first customer
sim.advance(rng_a.sample(dist)).await;
// generate new customers until the store closes officially
while sim.now() < stop_time {
// activate the next customer
sim.activate(Customer {
joe, rng: SmallRng::from_seed(rng_s.gen())
}.actions(sim));
// wait some time before activating the next customer
sim.advance(rng_a.sample(dist)).await;
}
});
// wait until the store closes
sim.advance(self.stop_time).await;
// finish processing the queue (no more customers arrive)
joe.seize().await;
}
}
/// Customer process with access to the barber and a random number generator.
struct Customer<'j> { joe: &'j Facility, rng: SmallRng }
impl Customer<'_> {
pub async fn actions(mut self, sim: SimContext<'_>) {
// access the barber
self.joe.seize().await;
// spend time
sim.advance(self.rng.gen_range(12.0, 18.0)).await;
// release the barber
self.joe.release();
}
}
#!/usr/bin/make
.SUFFIXES:
.PHONY: all run clean
TAR = coroutines
SRC = $(wildcard *.c)
OBJ = $(SRC:%.c=%.o)
DEP = $(OBJ:%.o=%.d)
-include $(DEP)
CFLAGS = -std=c11 -Wall -pedantic -MMD -MP
%.o: %.c
$(CC) $(CFLAGS) $< -c
$(TAR): $(OBJ)
$(CC) $(CFLAGS) $^ -o $@
all: $(TAR)
run: all
./$(TAR)
clean:
$(RM) $(RMFILES) $(TAR) $(OBJ) $(DEP)
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
/* Define either of them to enable the corresponding rewritten forms of the
* routines or both to have both of them rewritten. */
#define DECOMPRESS_COROUTINE
// #define TOKENIZE_COROUTINE
/* Helper macro to simplify tracing of the function calls and messages. */
#define TRACE(...) do { \
fprintf(stderr, __VA_ARGS__); \
putc('\n', stderr); \
} while (0)
/* Helper macro to catch array overflows for extreme inputs. */
#define CATCH_OVERFLOW(ARR, LEN) do { \
if ((LEN) >= sizeof(ARR)/sizeof(*ARR)) { \
fprintf(stderr, "PANIC: Array " #ARR " overflow detected, abort\n"); \
exit(-1); \
} \
} while (0)
/* Enumeration of possible token tags. */
enum Tag { WORD, PUNCT };
/* Names of the token tags. */
static const char *TOKEN_TAG[] = {
[WORD] = "Word", [PUNCT] = "Punct"
};
/* Token type with tag and value. */
typedef struct {
enum Tag tag;
char val[256];
size_t len;
} Token;
/* Primitive token channel for buffering multiple detected tokens. */
static struct {
Token token[256];
size_t len;
} token_chan;
/* Function that adds another character to the token value. */
void add_to_token(char c) {
Token *token = &token_chan.token[token_chan.len];
CATCH_OVERFLOW(token->val, token->len);
token->val[token->len++] = c;
}
/* Function that adds the corresponding tag and closes token construction. */
void got_token(enum Tag tag) {
CATCH_OVERFLOW(token_chan.token, token_chan.len);
Token *token = &token_chan.token[token_chan.len];
token->val[token->len] = '\0';
token->tag = tag;
TRACE("got_token(%s) = \"%s\"", TOKEN_TAG[tag], token->val);
++token_chan.len;
}
/* Stackless coroutine-version of the decompress-routine. */
int co_decompress(void) {
static int pc, l, c;
switch (pc) {
case 0: while (1) {
c = getchar();
if (c == EOF)
return EOF;
if (c == 0xFF) {
l = getchar();
c = getchar();
while (l--) {
TRACE("nextchar() = '%c'", c);
pc = 1;
return c;
case 1:;
}
} else {
TRACE("nextchar() = '%c'", c);
pc = 2;
return c;
case 2:;
}
}}
}
/* Stackless coroutine-version of the tokenize-routine. */
void co_tokenize(int c) {
static int pc = 1;
switch (pc) {
case 0: while (1) {
pc = 1;
return;
case 1:;
TRACE("emit('%c')", c);
if (c == EOF)
return;
if (isalpha(c)) {
do {
add_to_token(c);
pc = 2;
return;
case 2:;
TRACE("emit('%c')", c);
} while (isalpha(c));
got_token(WORD);
}
add_to_token(c);
got_token(PUNCT);
}}
}
/* Decodes RLE-encoded input and pushes it into the tokenizer coroutine. */
void decompress(void) {
while (1) {
int c = getchar();
if (c == EOF)
break;
if (c == 0xFF) {
int l = getchar();
c = getchar();
while (l--) {
co_tokenize(c);
}
} else
co_tokenize(c);
}
co_tokenize(EOF);
}
/* Calls the decompressor-coroutine for decoding RLE-encoded input and
* constructs token. */
void tokenize(void) {
while (1) {
int c = co_decompress();
if (c == EOF)
break;
if (isalpha(c)) {
do {
add_to_token(c);
c = co_decompress();
} while (isalpha(c));
got_token(WORD);
}
add_to_token(c);
got_token(PUNCT);
}
}
/* Prints all token currently present in the token channel. */
void printToken(void) {
for (size_t i = 0; i < token_chan.len; ++i) {
Token *token = &token_chan.token[i];
TRACE(
"Token: {\n"
"\ttag: %s,\n"
"\tval: \"%s\"\n"
"}",
TOKEN_TAG[token->tag],
token->val
);
token->len = 0;
}
token_chan.len = 0;
}
/* Program entry. */
int main() {
#if defined(TOKENIZE_COROUTINE) && defined(DECOMPRESS_COROUTINE)
fprintf(stderr, "Decompress Coroutine, Tokenize Coroutine\n");
for (int c; (c = co_decompress()) != EOF;) {
co_tokenize(c);
printToken();
}
#elif defined(TOKENIZE_COROUTINE)
fprintf(stderr, "Tokenize Routine, Decompress Coroutine\n");
tokenize();
#elif defined(DECOMPRESS_COROUTINE)
fprintf(stderr, "Decompress Routine, Tokenize Coroutine\n");
decompress();
#else
#error "At least one (or both) of TOKENIZE_COROUTINE or DECOMPRESS_COROUTINE should be defined."
#endif
return 0;
}
//! A discrete event simulation of a barbershop using `simcore_rs`, the
//! simulator core developed as part of my (Dorian Weber) dissertation.
//!
//! This module models a simple barbershop scenario where customers arrive at
//! random intervals, wait if the barber is busy, receive service, and then
//! depart. The simulation tracks customer wait times and provides statistical
//! analysis over the simulated period.
//!
//! # Notes
//! - The simulation assumes a continuous operation of the barbershop over the
//! specified duration.
//! - Customers arriving after the shop closes are not admitted; however, the
//! barber will finish servicing any remaining customers.
use rand::{rngs::SmallRng, Rng};
use simcore_rs::{
util::{Facility, RandomVariable},
Process, Sim, Time,
};
use std::cell::RefCell;
// Helper constants for random number generator seeds.
const SEED_A: u64 = 100_000;
const SEED_S: u64 = 200_000;
/// Globally shared data for the barbershop simulation.
struct Barbershop {
/// Random number generator for customer arrival times.
rng_a: RefCell<SmallRng>,
/// Random number generator for service times.
rng_s: RefCell<SmallRng>,
/// Facility representing the barber (Joe).
joe: Facility,
/// Statistical accumulator for customer wait times.
wait_time: RandomVariable,
}
/// Represents a customer in the barbershop simulation.
struct Customer;
impl Customer {
/// Defines the actions performed by a customer in the simulation.
///
/// The customer arrives at the barbershop, waits for the barber to be
/// available, gets a haircut (spends time being serviced), and then leaves.
pub async fn actions(self, sim: Sim<'_, Barbershop>) {
// Record the arrival time for wait time calculation.
let arrival_time = sim.now();
// Seize the barber (wait if not available).
sim.global().joe.seize().await;
// Calculate and record the customer's wait time.
sim.global().wait_time.tabulate(sim.now() - arrival_time);
// Simulate the time taken for the haircut.
let cut = sim.global().rng_s.borrow_mut().random_range(12.0..18.0);
sim.advance(cut).await;
// Release the barber for the next customer.
sim.global().joe.release();
}
}
/// The main simulation function.
///
/// This function initializes the customer arrival process, runs the simulation
/// for the specified duration, and ensures that all customers are serviced
/// before the simulation ends.
async fn sim_main(sim: Sim<'_, Barbershop>, duration: Time) {
// Activate a process to generate customers at random intervals.
sim.activate(async move {
loop {
// Wait for a random time before the next customer arrives.
let wait_time = sim.global().rng_a.borrow_mut().random_range(12.0..24.0);
sim.advance(wait_time).await;
// If the simulation time exceeds the duration, stop generating customers.
if sim.now() >= duration {
return;
}
// Activate a new customer process.
sim.activate(Customer.actions(sim));
}
});
// Run the simulation until the store closes.
sim.advance(duration).await;
// Ensure the barber finishes servicing any remaining customers.
sim.global().joe.seize().await;
}
/// Entry point for the barbershop simulation.
///
/// Initializes the simulation environment and runs the simulation for a
/// specified duration.
fn main() {
use rand::SeedableRng;
// Run the simulation and collect the result.
let result = simcore_rs::simulation(
// Initialize the global data for the simulation.
Barbershop {
rng_a: RefCell::new(SmallRng::seed_from_u64(SEED_A)),
rng_s: RefCell::new(SmallRng::seed_from_u64(SEED_S)),
joe: Facility::new(),
wait_time: RandomVariable::new(),
},
// Simulation entry point.
|sim| Process::new(sim, sim_main(sim, 3.0 * 7.0 * 24.0 * 60.0)),
);
// Print statistics after the simulation ends.
println!("wait_time: {:#.3?}", result.wait_time);
}
//! A discrete event simulation of a car-ferry system using `simcore_rs`, the
//! simulator core developed as part of my (Dorian Weber) dissertation.
//!
//! This module models a ferry system where cars arrive at multiple harbors and
//! wait to be transported by ferries to other harbors. The simulation tracks
//! various statistics such as car wait times, ferry cargo lengths, and ferry
//! load times over the simulated period.
//!
//! # Notes
//!
//! - The simulation assumes continuous operation over the specified duration.
//! - Cars that are not picked up by the end of the simulation are accounted for
//! in the wait time statistics.
//! - The ferry routes are set up in such a way that each ferry starts at a
//! different harbor and cycles through all harbors.
use rand::{rngs::SmallRng, Rng, SeedableRng};
use rand_distr::{Distribution, Exp, Normal};
use simcore_rs::{
util::{channel, select, RandomVariable, Receiver, Sender},
Process, Sim, Time,
};
use std::cell::RefCell;
// Constants for simulation parameters.
const SEED: u64 = 100_000;
const FERRY_COUNT: usize = 2;
const HARBOR_COUNT: usize = 4;
const HARBOR_DISTANCE: Time = 10.0;
const FERRY_TIMEOUT: Time = 5.0;
const FERRY_CAPACITY: usize = 5;
/// Shared global data for the ferry simulation.
struct Ferries {
/// Master random number generator for seeding.
master_rng: RefCell<SmallRng>,
/// Statistical accumulator for ferry cargo lengths.
ferry_cargo_len: RandomVariable,
/// Statistical accumulator for ferry load times.
ferry_load_time: RandomVariable,
/// Statistical accumulator for car wait times.
car_wait_time: RandomVariable,
}
/// Represents a car in the simulation.
#[derive(Debug)]
struct Car {
/// Time when the car arrives at the pier.
arrival_time: Time,
/// Duration required to load the car onto the ferry.
load_duration: Time,
}
/// Represents a pier (harbor) where cars arrive and wait for ferries.
struct Pier {
/// Random number generator specific to this pier.
rng: SmallRng,
/// Channel to send cars to the ferry.
landing_site: Sender<Car>,
}
/// Represents a ferry that transports cars between harbors.
struct Ferry {
/// Cargo hold containing the cars on the ferry.
cargo: Vec<Car>,
/// Timeout duration for ferry departure.
timeout: Time,
/// Time taken to travel between harbors.
travel_time: Time,
/// Receivers from piers to accept arriving cars.
piers: Vec<Receiver<Car>>,
}
impl Pier {
/// Simulates the actions of a pier in the simulation.
///
/// Cars arrive at the pier following an exponential distribution
/// and are sent to the ferry when it arrives.
async fn actions(mut self, sim: Sim<'_, Ferries>) {
// Arrival and loading time distributions.
let arrival_delay = Exp::new(0.1).unwrap();
let loading_delay = Normal::new(0.5, 0.2).unwrap();
loop {
// Wait for the next car to arrive.
sim.advance(arrival_delay.sample(&mut self.rng)).await;
// Create a new car with arrival and load times.
self.landing_site
.send(Car {
arrival_time: sim.now(),
load_duration: loading_delay
.sample_iter(&mut self.rng)
.find(|&val| val >= 0.0)
.unwrap(),
})
.await
.expect("No ferries in the simulation");
}
}
}
impl Ferry {
/// Simulates the actions of a ferry in the simulation.
///
/// The ferry travels between harbors, loads cars, waits for a timeout or
/// until it reaches capacity, and then moves to the next harbor.
async fn actions(mut self, sim: Sim<'_, Ferries>) {
loop {
for pier in self.piers.iter() {
// Unload the cars at the current pier.
for car in self.cargo.drain(..) {
sim.advance(car.load_duration).await;
}
let begin_loading = sim.now();
// Load cars until capacity is reached or timeout occurs.
while self.cargo.len() < self.cargo.capacity() {
match select(sim, pier.recv(), async {
sim.advance(self.timeout).await;
None
})
.await
{
// A car arrived before timeout.
Some(car) => {
sim.global()
.car_wait_time
.tabulate(sim.now() - car.arrival_time);
sim.advance(car.load_duration).await;
self.cargo.push(car);
}
// Timeout occurred; depart to next harbor.
None => break,
}
}
// Record ferry loading statistics.
sim.global()
.ferry_load_time
.tabulate(sim.now() - begin_loading);
sim.global()
.ferry_cargo_len
.tabulate(self.cargo.len() as f64);
// Travel to the next harbor.
sim.advance(self.travel_time).await;
}
}
}
}
/// The main simulation function.
///
/// Sets up the piers and ferries, activates their processes, and runs the simulation
/// for the specified duration.
async fn sim_main(sim: Sim<'_, Ferries>, duration: Time, ferries: usize, harbors: usize) {
let mut ports = Vec::with_capacity(harbors);
// Create all the harbors (piers).
for _ in 0..harbors {
let (sx, rx) = channel();
let harbor = Pier {
rng: SmallRng::from_seed(sim.global().master_rng.borrow_mut().random()),
landing_site: sx,
};
sim.activate(harbor.actions(sim));
ports.push(rx);
}
// Create all the ferries.
for i in 0..ferries {
let ferry = Ferry {
cargo: Vec::with_capacity(FERRY_CAPACITY),
timeout: FERRY_TIMEOUT,
travel_time: HARBOR_DISTANCE,
piers: ports
.iter()
.skip(i)
.chain(ports.iter().take(i))
.cloned()
.collect(),
};
sim.activate(ferry.actions(sim));
}
// Run the simulation for the specified duration.
sim.advance(duration).await;
// Handle any cars that weren't picked up by a ferry.
for port in ports {
for _ in 0..port.len() {
let car = port.recv().await.unwrap();
sim.global()
.car_wait_time
.tabulate(sim.now() - car.arrival_time);
}
}
}
/// Entry point for the ferry simulation.
///
/// Initializes the simulation environment and runs the simulation for one week.
fn main() {
let result = simcore_rs::simulation(
// Global shared data.
Ferries {
master_rng: RefCell::new(SmallRng::seed_from_u64(SEED)),
ferry_cargo_len: RandomVariable::new(),
ferry_load_time: RandomVariable::new(),
car_wait_time: RandomVariable::new(),
},
// Simulation entry point.
|sim| {
Process::new(
sim,
sim_main(sim, 24.0 * 60.0 * 365.0, FERRY_COUNT, HARBOR_COUNT),
)
},
);
// Output simulation statistics.
println!("Number of harbors: {}", HARBOR_COUNT);
println!("Number of ferries: {}", FERRY_COUNT);
println!("Car wait time: {:#.3?}", result.car_wait_time);
println!("Ferry cargo len: {:#.3?}", result.ferry_cargo_len);
println!("Ferry load time: {:#.3?}", result.ferry_load_time);
}
//! A discrete event simulation of the Dining Philosophers problem using
//! `simcore_rs`, the simulator core developed as part of my (Dorian Weber)
//! dissertation.
//!
//! This module models the classic Dining Philosophers problem, where multiple
//! philosophers sit around a table and alternate between thinking and eating.
//! They need two forks to eat and must coordinate to avoid deadlocks. The
//! simulation tracks the time until a deadlock occurs and collects statistical
//! data over multiple runs.
//!
//! # Notes
//!
//! - The simulation detects deadlocks when all philosophers are waiting for
//! forks and none can proceed.
//! - The statistical data collected includes the time until deadlock over
//! multiple simulation runs.
use rand::{rngs::SmallRng, Rng, SeedableRng};
use rand_distr::{Exp, Normal, Uniform};
use rayon::prelude::*;
use simcore_rs::{
util::{until, Control, RandomVariable},
Process, Sim, Time,
};
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
// Constants for simulation parameters.
const PHILOSOPHER_COUNT: usize = 5;
const SEED: u64 = 100_000;
/// Shared global data for the simulation.
struct Philosophers {
/// Master random number generator for seeding.
master_rng: RefCell<SmallRng>,
/// Cell to store the simulation duration until deadlock.
sim_duration: Cell<Time>,
}
/// Represents the shared table with forks for the philosophers.
struct Table {
/// Controls for each fork, indicating whether it is held.
forks: Vec<Control<bool>>,
/// Counter for the number of forks currently held.
forks_held: Control<usize>,
/// Counter for the number of forks currently awaited.
forks_awaited: Control<usize>,
}
impl Table {
/// Creates a new table with a specified number of forks.
fn new(forks: usize) -> Self {
Table {
forks: (0..forks).map(|_| Control::new(false)).collect(),
forks_held: Control::default(),
forks_awaited: Control::default(),
}
}
/// Acquires a fork at a given position, blocking until it is available.
async fn acquire_fork(&self, i: usize) {
let num = i % self.forks.len();
self.forks_awaited.set(self.forks_awaited.get() + 1);
until(&self.forks[num], |fork| !fork.get()).await;
self.forks[num].set(true);
self.forks_awaited.set(self.forks_awaited.get() - 1);
self.forks_held.set(self.forks_held.get() + 1);
}
/// Releases a fork at a given position back to the table.
fn release_fork(&self, i: usize) {
self.forks[i % self.forks.len()].set(false);
self.forks_held.set(self.forks_held.get() - 1);
}
}
/// Represents a philosopher in the simulation.
struct Philosopher {
/// Reference to the shared table.
table: Rc<Table>,
/// The seat index of the philosopher at the table.
seat: usize,
/// Random number generator for this philosopher.
rng: SmallRng,
}
impl Philosopher {
/// Simulates the actions of a philosopher.
///
/// The philosopher alternates between thinking and eating, and tries to
/// acquire forks.
async fn actions(mut self, sim: Sim<'_, Philosophers>) {
let thinking_duration = Exp::new(1.0).unwrap();
let artificial_delay = Uniform::new(0.1, 0.2).unwrap();
let eating_duration = Normal::new(0.5, 0.2).unwrap();
let rng = &mut self.rng;
loop {
// Spend some time pondering the nature of things.
sim.advance(rng.sample(thinking_duration)).await;
// Acquire the first fork.
self.table.acquire_fork(self.seat).await;
// Introduce an artificial delay to leave room for deadlocks.
sim.advance(rng.sample(artificial_delay)).await;
// Acquire the second fork.
self.table.acquire_fork(self.seat + 1).await;
// Spend some time eating.
sim.advance(
rng.sample_iter(eating_duration).find(|&v| v >= 0.0).unwrap(),
)
.await;
// Release the forks.
self.table.release_fork(self.seat + 1);
self.table.release_fork(self.seat);
}
}
}
/// Runs a single simulation instance of the Dining Philosophers problem.
///
/// Initializes the table and philosophers, and waits until a deadlock occurs.
async fn sim_main(sim: Sim<'_, Philosophers>, count: usize) {
let table = Rc::new(Table::new(count));
// Create the philosopher processes and seat them.
for i in 0..count {
sim.activate(
Philosopher {
table: table.clone(),
seat: i,
rng: SmallRng::from_seed(sim.global().master_rng.borrow_mut().random()),
}
.actions(sim),
);
}
// Wait for the precise configuration indicating a deadlock.
until(
(&table.forks_held, &table.forks_awaited),
|(held, awaited)| held.get() == count && awaited.get() == count,
)
.await;
// Record the current simulation time.
sim.global().sim_duration.set(sim.now());
}
/// Runs multiple simulation instances in parallel and collects statistics.
///
/// # Arguments
///
/// * `count` - The number of philosophers (and forks) in each simulation.
/// * `reruns` - The number of simulation runs to perform.
///
/// # Returns
///
/// A `RandomVar` containing statistical data of simulation durations until
/// deadlock.
fn philosophers(count: usize, reruns: usize) -> RandomVariable {
// Use thread-based parallelism to concurrently run simulation models.
(1..=reruns)
.into_par_iter()
.map(|i| {
simcore_rs::simulation(
// Global data.
Philosophers {
master_rng: RefCell::new(SmallRng::seed_from_u64(i as u64 * SEED)),
sim_duration: Cell::default(),
},
// Simulation entry point.
|sim| Process::new(sim, sim_main(sim, count)),
)
.sim_duration
.get()
})
.fold(RandomVariable::new, |var, duration| {
var.tabulate(duration);
var
})
.reduce(RandomVariable::new, |var_a, var_b| {
var_a.merge(&var_b);
var_a
})
}
/// Entry point for the Dining Philosophers simulation.
///
/// Runs multiple simulations and prints out the statistical results.
fn main() {
const EXPERIMENT_COUNT: usize = 500;
let sim_duration = philosophers(PHILOSOPHER_COUNT, EXPERIMENT_COUNT);
println!("Simulation duration until deadlock: {:#?}", sim_duration);
}
hard_tabs=true
barbershop.slx: SLX-64 AN206 Lines: 7,086 Errors: 0 Warnings: 0 Lines/Second: 680,384 Peak Memory: 111 MB
Execution begins
[100000] mean 0.514 ms, stddev 0.052 ms, half-width 0.001 ms
[200000] mean 1.034 ms, stddev 0.069 ms, half-width 0.002 ms
[300000] mean 1.552 ms, stddev 0.073 ms, half-width 0.002 ms
[400000] mean 2.069 ms, stddev 0.089 ms, half-width 0.002 ms
[500000] mean 2.592 ms, stddev 0.116 ms, half-width 0.003 ms
[600000] mean 3.106 ms, stddev 0.121 ms, half-width 0.003 ms
[700000] mean 3.618 ms, stddev 0.127 ms, half-width 0.003 ms
[800000] mean 4.136 ms, stddev 0.150 ms, half-width 0.004 ms
Execution complete
Objects created: 44 passive and 1,999,959,827 active Pucks created: 2,000,039,828 Peak Memory: 111 MB Time: 3.10 Minutes
import <h7>
// enable this macro for the benchmark
#define BENCH
module barbershop {
facility joe;
queue joeq;
rn_stream rng_a seed = 100000;
rn_stream rng_s seed = 200000;
constant double STOP_TIME = 480000.0;
constant int BENCH_SAMPLES = 10000;
constant int BENCH_RANGE = 8;
constant int BENCH_STEP = 100000;
class Customer {
actions {
#ifndef BENCH
enqueue joeq;
#endif
seize joe;
#ifndef BENCH
depart joeq;
#endif
advance rv_uniform(rng_s, 12.0, 18.0);
release joe;
}
}
procedure run(double stop_time) {
arrivals: Customer
iat = rv_uniform(rng_a, 12.0, 24.0)
until_time = stop_time;
advance stop_time;
wait until FNU(joe);
}
procedure main() {
#ifdef BENCH
double samples[BENCH_SAMPLES];
double real_start, real_end;
double stop_time;
double mean, stddev, half_width;
int i, j;
for (j = 1; j <= BENCH_RANGE; j += 1) {
stop_time = BENCH_STEP*j;
for (i = 1; i <= BENCH_SAMPLES; ++i) {
real_start = real_time();
run(stop_time);
real_end = real_time();
samples[i] = (real_end - real_start)*1000.0;
clear joe;
clear system;
}
if (BENCH_SAMPLES > 1) {
build_mean_ci(samples, BENCH_SAMPLES, 0.99, mean, stddev, half_width);
print(stop_time, mean, stddev, half_width) "[_] mean _.___ ms, stddev _.___ ms, half-width _.___ ms\n";
} else {
print(stop_time, samples[1]) "[_] sample _.___ ms\n";
}
}
#else
run(STOP_TIME);
report queue_set;
report joe;
#endif
}
}
\ No newline at end of file
use simcore_rs::{Time, SimContext, Facility, RandomVar, simulation};
use rand::{distributions::Uniform, rngs::SmallRng, SeedableRng, Rng};
// helper constants
const STOP_TIME: Time = 480000.0;
const SEED_A : u64 = 100000;
const SEED_S : u64 = 200000;
fn main() {
// pseudo random number generators referenced from within the main process
let mut rng_a = SmallRng::seed_from_u64(SEED_A);
let mut rng_s = SmallRng::seed_from_u64(SEED_S);
// the lifetimes are automatically extended until the end of the scope
let joe = &Facility::new();
let rv = &RandomVar::new();
// the main process
simulation(|sim| async move {
// activate a process to generate the customers
sim.activate(async move {
let dist = Uniform::new(12.0, 24.0);
// wait some time before activating the first customer
sim.advance(rng_a.sample(dist)).await;
// generate new customers until the store closes officially
while sim.now() < STOP_TIME {
// activate the next customer
sim.activate(Customer {
joe, rv, rng: SmallRng::from_seed(rng_s.gen())
}.actions(sim));
// wait some time before activating the next customer
sim.advance(rng_a.sample(dist)).await;
}
});
// wait until the store closes
sim.advance(STOP_TIME).await;
// finish processing the queue (no more customers arrive)
joe.seize().await;
});
println!("Stats: {:.3}", rv);
}
/// Customer process with access to the barber and a random number generator.
struct Customer<'j> { joe: &'j Facility, rv: &'j RandomVar, rng: SmallRng }
impl Customer<'_> {
pub async fn actions(mut self, sim: SimContext<'_>) {
// access the barber and record the time for the report
let time = sim.now();
self.joe.seize().await;
self.rv.tabulate(sim.now() - time);
// spend time
sim.advance(self.rng.gen_range(12.0, 18.0)).await;
// release the barber
self.joe.release();
}
}
use self::Token::*;
use std::rc::Rc;
#![forbid(unsafe_code)]
use std::{pin::pin, rc::Rc};
async fn decompress(mut input: impl Iterator<Item=u8>,
output: Sender<char>) {
while let Some(c) = input.next() {
#[derive(Clone, Debug)]
#[allow(dead_code)]
enum Token {
Word(String),
Punct(char),
}
async fn decompress<I>(mut inp: I, out: Sender<char>)
where
I: Iterator<Item = u8>,
{
while let Some(c) = inp.next() {
if c == 0xFF {
let len = input.next().unwrap();
let c = input.next().unwrap();
let len = inp.next().unwrap();
let c = inp.next().unwrap();
for _ in 0..len {
output.send(c as char).await;
out.send(c as char).await;
}
} else {
output.send(c as char).await;
out.send(c as char).await;
}
}
}
#[derive(Clone, Debug)]
enum Token { WORD(String), PUNCT(char) }
async fn tokenize(input: Receiver<char>, output: Sender<Token>) {
while let Some(mut c) = input.recv().await {
async fn tokenize(inp: Receiver<char>, out: Sender<Token>) {
while let Some(mut c) = inp.recv().await {
if c.is_alphabetic() {
let mut text = c.to_string();
while let Some(new_c) = input.recv().await {
while let Some(new_c) = inp.recv().await {
if new_c.is_alphabetic() {
text.push(new_c);
} else {
c = new_c; break;
c = new_c;
break;
}
}
output.send(WORD(text)).await;
out.send(Token::Word(text)).await;
}
output.send(PUNCT(c)).await;
out.send(Token::Punct(c)).await;
}
}
fn main() {
let exec = Executor::new();
let spawner = exec.spawner();
let input = b"He\xff\x02lo SAM!".iter().cloned();
let input = b"F\xff\x02o!".iter().cloned();
exec.run(async {
let (s1, r1) = channel();
let (s2, r2) = channel();
let (s1, r1) = channel::<char>();
let (s2, r2) = channel::<Token>();
spawner.spawn(decompress(input, s1));
spawner.spawn(tokenize(r1, s2));
while let Some(token) = r2.recv().await {
println!("{:?}", token);
}
......@@ -60,65 +67,56 @@ fn main() {
// additional imports for the necessary execution environment
use std::{
pin::Pin,
cell::RefCell,
future::Future,
task::{self, Poll, Context},
mem::swap
mem::swap,
pin::Pin,
task::{self, Context, Poll},
};
/* ************************** executor environment ************************** */
// heterogeneous, pinned list of futures
type FutureQ = Vec<Pin<Box<dyn Future<Output=()>>>>;
type FutureQ = Vec<Pin<Box<dyn Future<Output = ()>>>>;
/// A simple executor that drives the event loop.
struct Executor {
sched: RefCell<FutureQ>
sched: RefCell<FutureQ>,
}
impl Executor {
/// Constructs a new executor.
pub fn new() -> Self {
Executor {
sched: RefCell::new(vec![])
sched: RefCell::new(vec![]),
}
}
/// Creates and returns a Spawner that can be used to insert new futures
/// into the executors future queue.
pub fn spawner(&self) -> Spawner {
Spawner { sched: &self.sched }
}
/// Runs a future to completion.
pub fn run<F: Future>(&self, mut future: F) -> F::Output {
// construct a custom context to pass into the poll()-method
let waker = unsafe {
task::Waker::from_raw(task::RawWaker::new(
std::ptr::null(),
&EXECUTOR_WAKER_VTABLE
))
};
let mut cx = task::Context::from_waker(&waker);
// pin the passed future for the duration of this function;
// note: this is safe since we're not moving it around
let mut future = unsafe {
Pin::new_unchecked(&mut future)
};
let mut cx = Context::from_waker(task::Waker::noop());
// pin the passed future for the duration of this function
let mut future = pin!(future);
let mut sched = FutureQ::new();
// implement a busy-wait event loop for simplicity
loop {
// poll the primary future, allowing secondary futures to spawn
if let Poll::Ready(val) = future.as_mut().poll(&mut cx) {
break val;
}
// swap the scheduled futures with an empty queue
swap(&mut sched, &mut self.sched.borrow_mut());
// iterate over all secondary futures presently scheduled
for mut future in sched.drain(..) {
// if they are not completed, reschedule
......@@ -131,115 +129,107 @@ impl Executor {
}
/// A handle to the executors future queue that can be used to insert new tasks.
struct Spawner<'a> { sched: &'a RefCell<FutureQ> }
impl<'a> Spawner<'a> {
pub fn spawn(&self, future: impl Future<Output=()> + 'static) {
self.sched.borrow_mut().push(Box::pin(future));
}
struct Spawner<'a> {
sched: &'a RefCell<FutureQ>,
}
/* **************************** specialized waker *************************** */
/// Simple waker that isn't really used (yet), except to show the mechanism.
struct TrivialWaker;
impl TrivialWaker {
unsafe fn clone(_this: *const ()) -> task::RawWaker {
unimplemented!()
}
unsafe fn wake(_this: *const ()) {
unimplemented!()
}
unsafe fn wake_by_ref(_this: *const ()) {
unimplemented!()
impl Spawner<'_> {
pub fn spawn(&self, future: impl Future<Output = ()> + 'static) {
self.sched.borrow_mut().push(Box::pin(future));
}
unsafe fn drop(_this: *const ()) {}
}
/// Virtual function table for the simple waker.
static EXECUTOR_WAKER_VTABLE: task::RawWakerVTable = task::RawWakerVTable::new(
TrivialWaker::clone,
TrivialWaker::wake,
TrivialWaker::wake_by_ref,
TrivialWaker::drop
);
/* ************************** asynchronous wrappers ************************* */
/// Simple channel with space for only one element.
struct Channel<T> {
slot: RefCell<Option<T>>
slot: RefCell<Option<T>>,
}
/// Creates a channel and returns a pair of read and write ends.
fn channel<T>() -> (Sender<T>, Receiver<T>) {
let chan = Rc::new(Channel {
slot: RefCell::new(None)
let channel = Rc::new(Channel {
slot: RefCell::new(None),
});
(Sender { chan: chan.clone() }, Receiver { chan })
(
Sender {
channel: channel.clone(),
},
Receiver { channel },
)
}
/// Write-end of a channel.
struct Sender<T> { chan: Rc<Channel<T>> }
struct Sender<T> {
channel: Rc<Channel<T>>,
}
/// Read-end of a channel.
struct Receiver<T> { chan: Rc<Channel<T>> }
struct Receiver<T> {
channel: Rc<Channel<T>>,
}
impl<T: Clone> Sender<T> {
/// Method used to push an element into the channel.
/// Blocks until the previous element has been consumed.
pub fn send<'s>(&'s self, elem: T) -> impl Future<Output = ()> + 's {
SendFuture { chan: &self.chan, elem }
pub fn send(&self, elem: T) -> impl Future<Output = ()> + '_ {
SendFuture {
channel: &self.channel,
elem,
}
}
}
impl<T: Clone> Receiver<T> {
/// Method used to consume an element from the channel.
/// Blocks until an element can be consumed.
pub fn recv<'r>(&'r self) -> impl Future<Output = Option<T>> + 'r {
ReceiveFuture { chan: &self.chan }
pub fn recv(&self) -> impl Future<Output = Option<T>> + '_ {
ReceiveFuture {
channel: &self.channel,
}
}
}
/// A future that pushes an element into a channel.
struct SendFuture<'c,T> { chan: &'c Rc<Channel<T>>, elem: T }
struct SendFuture<'c, T> {
channel: &'c Rc<Channel<T>>,
elem: T,
}
/// A future that consumes an element from a channel.
struct ReceiveFuture<'c,T> { chan: &'c Rc<Channel<T>> }
struct ReceiveFuture<'c, T> {
channel: &'c Rc<Channel<T>>,
}
impl<T: Clone> Future for SendFuture<'_,T> {
impl<T: Clone> Future for SendFuture<'_, T> {
type Output = ();
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
// check if there is space for the element
if self.chan.slot.borrow().is_none() {
if self.channel.slot.borrow().is_none() {
// replace the empty element with ours
self.chan.slot.replace(Some(self.elem.clone()));
self.channel.slot.replace(Some(self.elem.clone()));
Poll::Ready(())
} else {
// check back at a later time
// try again at a later time
Poll::Pending
}
}
}
impl<T> Future for ReceiveFuture<'_,T> {
impl<T> Future for ReceiveFuture<'_, T> {
type Output = Option<T>;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
// check if there is an element in the channel
if let Some(c) = self.chan.slot.borrow_mut().take() {
if let Some(c) = self.channel.slot.borrow_mut().take() {
// return it
Poll::Ready(Some(c))
} else if Rc::strong_count(self.chan) == 1 {
} else if Rc::strong_count(self.channel) == 1 {
// check if the sender disconnected
Poll::Ready(None)
} else {
// check back at a later time
// try again at a later time
Poll::Pending
}
}
......
use simcore_rs::{Time, SimContext, Facility, RandomVar, simulation};
use rand::{distributions::Uniform, rngs::SmallRng, SeedableRng, Rng};
// helper constants
const STOP_TIME: Time = 480000.0;
const SEED_A : u64 = 100000;
const SEED_S : u64 = 200000;
fn main() {
// pseudo random number generators referenced from within the main process
let mut rng_a = SmallRng::seed_from_u64(SEED_A);
let mut rng_s = SmallRng::seed_from_u64(SEED_S);
// the lifetimes are automatically extended
let joe = &Facility::new();
let rv = &RandomVar::new();
// the main process
simulation(|sim| async move {
// activate a process to generate the customers
sim.activate(async move {
let dist = Uniform::new(12.0, 24.0);
// wait some time before activating the first customer
sim.advance(rng_a.sample(dist)).await;
// generate new customers until the store closes officially
while sim.now() < STOP_TIME {
// activate the next customer
sim.activate(Customer {
joe, rv, rng: SmallRng::from_seed(rng_s.gen())
}.actions(sim));
// wait some time before activating the next customer
sim.advance(rng_a.sample(dist)).await;
}
});
// wait until the store closes
sim.advance(STOP_TIME).await;
// finish processing the queue (no more customers arrive)
joe.seize().await;
joe.release();
});
println!("Stats: {:.3}", rv);
}
/// Customer process with access to the barber and a random number generator.
struct Customer<'j> { joe: &'j Facility, rv: &'j RandomVar, rng: SmallRng }
impl Customer<'_> {
pub async fn actions(mut self, sim: SimContext<'_>) {
// access the barber and record the time for the report
let time = sim.now();
self.joe.seize().await;
self.rv.tabulate(sim.now() - time);
// spend time
sim.advance(self.rng.gen_range(12.0, 18.0)).await;
// release the barber
self.joe.release();
}
}
///! Mini-library for the SAM 2020 paper.
use std::{
collections::{BinaryHeap, VecDeque},
task::{self, Context, Poll},
fmt::{Display, Formatter},
cell::{RefCell, Cell},
future::Future,
cmp::Ordering,
pin::Pin,
rc::Rc
};
// simple time type
pub type Time = f64;
// priority queue of time-process-pairs using time as the key
type EventQ<'p> = BinaryHeap<NextEvent<'p>>;
/// The (private) scheduler for processes.
struct Scheduler<'s> {
/// The current simulation time.
now: Cell<Time>,
/// The event-calendar organized chronologically.
calendar: RefCell<EventQ<'s>>,
/// The currently active process.
active: RefCell<Process<'s>>
}
// these allow us to *move* the context without invalidating it
#[derive(Copy, Clone)]
/// A light-weight handle to the scheduler.
pub struct SimContext<'s> { handle: *const Scheduler<'s> }
/// Performs a single simulation run.
/// Input is a function that takes a simulation context and returns the future
/// that is used to initialize the first process.
pub fn simulation<'s,G,F>(main: G)
where G: FnOnce(SimContext<'s>) -> F,
F: Future<Output = ()> + 's
{
// (this function's signature could be better but there seems to be a
// bug with higher-order trait-bounds right now that prevents me from
// referring to the same lifetime in multiple type parameters)
use std::task::{Waker, RawWaker};
// create a fresh scheduler and a handle to it
let sched = Scheduler::new();
let sim = SimContext { handle: &sched };
// construct a custom context to pass into the poll()-method
let event_waker = Box::leak(Box::new(StateEventWaker {
process: Process::default(),
context: sim
}));
let waker = unsafe {
Waker::from_raw(RawWaker::new(
event_waker as *const _ as *const (),
&WAKER_VTABLE
))
};
let mut cx = Context::from_waker(&waker);
// evaluate the passed function for the main process and schedule it
let root = Process::new(main(sim));
sched.schedule(root.clone());
// pop processes until empty or the main process terminates
while let Some(process) = sched.next_event() {
// register the next process with the context
event_waker.process = process.clone();
if process.poll(&mut cx).is_ready() && process == root { break; }
}
// clear the scheduler before it is dropped to break the dependency
// cycle between the processes and the scheduler that we made possible
// by using a raw pointer in the simulation context
sched.clear();
}
impl<'s> Scheduler<'s> {
/// Creates a new scheduler.
fn new() -> Self {
Self {
now: Cell::default(),
calendar: RefCell::default(),
active: RefCell::default()
}
}
/// Clears the scheduler.
fn clear(&self) {
self.active.replace(Process::default());
self.calendar.replace(EventQ::default());
}
/// Schedules a process at the current simulation time.
fn schedule(&self, process: Process<'s>) {
self.schedule_in(Time::default(), process);
}
/// Schedules a process at a later simulation time.
fn schedule_in(&self, dt: Time, process: Process<'s>) {
self.calendar.borrow_mut().push(
NextEvent(self.now.get() + dt, process)
);
}
/// Removes the process with the next event time from the calendar and
/// activates it.
fn next_event(&self) -> Option<Process<'s>> {
let NextEvent(now, process) = self.calendar.borrow_mut().pop()?;
self.now.set(now);
self.active.replace(process.clone());
Some(process)
}
}
impl<'s> SimContext<'s> {
/// Returns a (reference-counted) copy of the currently active process.
pub fn active(&self) -> Process<'s> {
self.sched().active.borrow().clone()
}
/// Activates a new process with the given future.
pub fn activate(&self, fut: impl Future<Output = ()> + 's) {
self.sched().schedule(Process::new(fut));
}
/// Reactivates a process that has been suspended with wait().
pub fn reactivate(&self, process: Process<'s>) {
self.sched().schedule(process);
}
/// Unconditionally suspends execution until reactivation.
pub fn wait(&self) -> impl Future<Output = ()> {
Wait { ready: false }
}
/// Reactivates the currently active process after some time has passed.
pub async fn advance(&self, dt: Time) {
self.sched().schedule_in(dt, self.active());
self.wait().await
}
/// Returns the current simulation time.
pub fn now(&self) -> Time {
self.sched().now.get()
}
/// Private function to get a safe reference to the scheduler.
fn sched(&self) -> &Scheduler<'s> {
// This is safe if no simulation context escapes from the closure
// passed to simulation() which, in theory, should be enforceable
// through the use of higher-order trait-bounds. However, right now
// they seem to not work correctly or I'm missing something.
// Long story short: this is really not memory safe at the moment
// but it's good enough for our purposes and *can* be made safe.
unsafe { &*self.handle }
}
}
#[derive(Clone)]
/// Bare-bone process.
pub struct Process<'p>(Pin<Rc<RefCell<dyn Future<Output = ()> + 'p>>>);
impl<'p> Process<'p> {
/// Creates a new process from a future.
pub fn new(fut: impl Future<Output = ()> + 'p) -> Self {
Process(Rc::pin(RefCell::new(fut)))
}
/// Private function for polling the future.
fn poll(&self, cx: &mut Context) -> Poll<()> {
// this is safe because the process is already pinned
unsafe {
Pin::new_unchecked(&mut *self.0.borrow_mut())
}.poll(cx)
}
}
// implementation of Rust's version of a default constructor
impl Default for Process<'_> {
fn default() -> Self {
Process::new(Wait { ready: false })
}
}
// allows processes to be compared for equality
impl PartialEq for Process<'_> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(&*self.0, &*other.0)
}
}
// marks the equality-relation as total
impl Eq for Process<'_> {}
/// Time-process-pair that has a total order defined based on the time.
struct NextEvent<'p>(Time, Process<'p>);
impl PartialEq for NextEvent<'_> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for NextEvent<'_> {}
impl PartialOrd for NextEvent<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NextEvent<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.partial_cmp(&other.0)
.expect("illegal event time NaN").reverse()
}
}
/* **************************** specialized waker *************************** */
#[derive(Clone)]
/// Complex waker that is used to implement state events.
struct StateEventWaker<'s> {
process: Process<'s>,
context: SimContext<'s>
}
impl StateEventWaker<'_> {
unsafe fn clone(this: *const ()) -> task::RawWaker {
let waker = &*(this as *const Self);
task::RawWaker::new(
Box::into_raw(Box::new(waker.clone())) as *const (),
&WAKER_VTABLE
)
}
unsafe fn wake(this: *const ()) {
let waker = Box::from_raw(this as *const Self as *mut Self);
waker.context.reactivate(waker.process);
}
unsafe fn wake_by_ref(this: *const ()) {
let waker = &*(this as *const Self);
waker.context.reactivate(waker.process.clone());
}
unsafe fn drop(this: *const ()) {
Box::from_raw(this as *const Self as *mut Self);
}
}
/// Virtual function table for the waker.
static WAKER_VTABLE: task::RawWakerVTable = task::RawWakerVTable::new(
StateEventWaker::clone,
StateEventWaker::wake,
StateEventWaker::wake_by_ref,
StateEventWaker::drop
);
/* *************************** specialized futures ************************** */
/// Future that blocks on the first call and returns on the second one.
struct Wait { ready: bool }
impl Future for Wait {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> {
if self.ready {
Poll::Ready(())
} else {
self.ready = true;
Poll::Pending
}
}
}
/// Future that returns immediately with a cloned waker.
struct Waker;
impl Future for Waker {
type Output = task::Waker;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
Poll::Ready(cx.waker().clone())
}
}
// this is the end of the simulator core, the remainder is used in the example
/* *********************** synchronization structures *********************** */
/// GPSS-like facility that houses one exclusive ressources to be used by
/// arbitrary many processes.
pub struct Facility {
queue: RefCell<VecDeque<task::Waker>>,
in_use: Cell<bool>
}
impl Facility {
/// Creates a new facility.
pub fn new() -> Self {
Facility {
queue: RefCell::new(VecDeque::new()),
in_use: Cell::new(false)
}
}
/// Resets the facility to its beginning state.
pub fn reset(&self) {
self.in_use.set(false);
self.queue.borrow_mut().clear();
}
/// Attempts to seize the facility, blocking until it's possible.
pub async fn seize(&self) {
if self.in_use.replace(true) {
self.queue.borrow_mut().push_back(Waker.await);
Wait { ready: false }.await;
}
}
/// Releases the facility and activates the next waiting process.
pub fn release(&self) {
if let Some(process) = self.queue.borrow_mut().pop_front() {
process.wake();
} else {
self.in_use.set(false);
}
}
}
/// Complex channel with space for infinitely many elements of arbitrary
/// (non-process) type.
///
/// This channel supports arbitrary many readers and writers and uses wakers
/// to reactivate suspended processes.
struct Channel<T> {
store: RefCell<VecDeque<T>>,
waiting: RefCell<VecDeque<task::Waker>>,
readers: Cell<u32>,
writers: Cell<u32>
}
/// Creates a channel and returns a pair of read and write ends.
pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let chan = Rc::new(Channel {
store: RefCell::default(),
waiting: RefCell::default(),
readers: Cell::new(1),
writers: Cell::new(1)
});
(Sender { chan: chan.clone() }, Receiver { chan })
}
/// Write-end of a channel.
pub struct Sender<T> { chan: Rc<Channel<T>> }
/// Read-end of a channel.
pub struct Receiver<T> { chan: Rc<Channel<T>> }
// Allow senders to be duplicated.
impl<T> Clone for Sender<T> {
fn clone(&self) -> Self {
self.chan.writers.set(self.chan.writers.get() + 1);
Self { chan: self.chan.clone() }
}
}
// Allow receivers to be duplicated.
impl<T> Clone for Receiver<T> {
fn clone(&self) -> Self {
self.chan.readers.set(self.chan.readers.get() + 1);
Self { chan: self.chan.clone() }
}
}
impl<T: Unpin> Sender<T> {
/// Returns a future that can be used to send a message.
pub fn send<'s>(&'s self, elem: T) -> impl Future<Output = Result<(),T>> + 's {
SendFuture { chan: &self.chan, elem: Some(elem) }
}
}
impl<T: Unpin> Receiver<T> {
/// Returns a future that can be used to receive a message.
pub fn recv<'s>(&'s self) -> impl Future<Output = Option<T>> + 's {
ReceiveFuture { chan: &self.chan }
}
}
impl<T> Channel<T> {
/// Private method enqueuing a process into the waiting list.
fn enqueue(&self, process: task::Waker) {
self.waiting.borrow_mut().push_back(process);
}
/// Private method dequeueing a process from the waiting list.
fn dequeue(&self) -> Option<task::Waker> {
self.waiting.borrow_mut().pop_front()
}
/// Private method inserting a message into the queue.
fn send(&self, value: T) {
self.store.borrow_mut().push_back(value);
}
/// Private method extracting a message from the queue non-blocking.
fn recv(&self) -> Option<T> {
self.store.borrow_mut().pop_front()
}
}
/// Future for the send() operation on a channel sender.
struct SendFuture<'c,T> { chan: &'c Rc<Channel<T>>, elem: Option<T> }
/// Future for the recv() operation on a channel receiver.
struct ReceiveFuture<'c,T> { chan: &'c Rc<Channel<T>> }
impl<T: Unpin> Future for SendFuture<'_,T> {
type Output = Result<(),T>;
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
let elem = self.elem.take().unwrap();
// test if there are still readers left to listen
if self.chan.readers.get() >= 1 {
self.chan.send(elem);
// wake-up a waiting process
if let Some(process) = self.chan.dequeue() {
process.wake();
}
Poll::Ready(Ok(()))
} else {
Poll::Ready(Err(elem))
}
}
}
impl<T: Unpin> Future for ReceiveFuture<'_,T> {
type Output = Option<T>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// check if there is an element in the channel
if let Some(c) = self.chan.recv() {
// return it
Poll::Ready(Some(c))
} else if self.chan.writers.get() == 0 {
Poll::Ready(None)
} else {
// check back at a later time
self.chan.enqueue(cx.waker().clone());
Poll::Pending
}
}
}
/* ************************* statistical facilities ************************* */
/// A simple collector for statistical data, inspired by SLX's random_variable.
pub struct RandomVar {
total: Cell<u32>, sum: Cell<f64>, sqr: Cell<f64>,
min: Cell<f64>, max: Cell<f64>
}
impl RandomVar {
/// Creates a new random variable.
pub fn new() -> Self {
RandomVar::default()
}
/// Resets all stored statistical data.
pub fn clear(&self) {
self.total.set(0);
self.sum.set(0.0);
self.sqr.set(0.0);
self.min.set(f64::INFINITY);
self.max.set(f64::NEG_INFINITY);
}
/// Adds another value to the statistical collection.
pub fn tabulate<T: Into<f64>>(&self, val: T) {
let val: f64 = val.into();
self.total.set(self.total.get() + 1);
self.sum.set(self.sum.get() + val);
self.sqr.set(self.sqr.get() + val*val);
if self.min.get() > val {
self.min.set(val);
} else if self.max.get() < val {
self.max.set(val);
}
}
}
impl Default for RandomVar {
fn default() -> Self {
RandomVar {
total: Cell::default(),
sum: Cell::default(),
sqr: Cell::default(),
min: Cell::new(f64::INFINITY),
max: Cell::new(f64::NEG_INFINITY)
}
}
}
impl Display for RandomVar {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let total = self.total.get();
let mean = self.sum.get() / f64::from(total);
let variance = self.sqr.get() / f64::from(total) - mean*mean;
let stddev = variance.sqrt();
f.debug_struct("RandomVar")
.field("total", &total)
.field("mean", &mean)
.field("stddev", &stddev)
.field("min", &self.min.get())
.field("max", &self.max.get())
.finish()
}
}
//! A minimal discrete-event simulation framework using Rust's async/await.
//!
//! This module provides a simple discrete-event simulation environment where
//! processes are represented as asynchronous tasks (`Future`s). The simulation
//! advances in discrete time steps, executing processes scheduled at specific
//! simulation times.
//!
//! Key components:
//!
//! - **`simulation`**: Runs a single simulation given a shared global state
//! and a main process function. It sets up the scheduler (`Calendar`),
//! initializes a handle to the simulation context (`Sim`), and manages the
//! event loop until the main process terminates.
//!
//! - **`Simulator`**: The simulation context, containing the event calendar,
//! an owning registry for all processes, and a reference to the shared data.
//!
//! - **`Sim`**: A lightweight handle to the simulation context, providing
//! methods for processes to interact with the simulation. Processes can use
//! it to schedule themselves or other processes, advance simulation time,
//! retrieve the current model-time, and access shared global data.
//!
//! - **`Process`**: Wraps a `Future` to represent a process in the simulation.
//! It can be scheduled to run at specific model times and can also act as a
//! waker to resume suspended processes.
//!
//! - **`Calendar`**: The scheduler that maintains the current model time and a
//! priority queue of processes scheduled to run at future times.
//!
//! - **Utility functions**:
//! - `sleep()`: A future that processes can await to suspend execution until
//! reactivated.
//! - `waker()`: A future that returns the current waker, allowing processes
//! to interact with the task system.
//!
//! **Example usage:**
//!
//! ```
//! use simcore_rs::*;
//!
//! async fn sim_main(sim: Sim<'_>) {
//! // Main Process code here
//! println!("Process started at time {}", sim.now());
//! sim.advance(5.0).await; // Suspend for 5 units of time
//! println!("Process resumed at time {}", sim.now());
//! }
//!
//! // Run the simulation with empty shared data
//! let shared_data = simulation((), |sim| Process::new(sim, sim_main(sim)));
//! ```
//!
//! **Notes:**
//!
//! - The simulation is designed for single-threaded execution and is not `Send`
//! or `Sync` safe.
//! - Processes manage their own scheduling and can be reactivated by the
//! simulation context.
//! - Unsafe code is used internally, but safety is maintained as long as
//! strongly owned processes do not escape the closure passed to `simulation`.
//! - Wakers are customized to integrate with the simulation's process
//! scheduling.
//!
//! This framework is intended for educational purposes as part of my (Dorian
//! Weber) dissertation to illustrate how asynchronous Rust can be used to
//! implement a discrete-event simulation framework. It may not cover all edge
//! cases or be suitable for production use.
use std::{
cell::{Cell, RefCell},
cmp::Ordering,
collections::{BinaryHeap, HashSet},
future::{Future, poll_fn},
hash::{Hash, Hasher},
mem::ManuallyDrop,
pin::Pin,
rc::{Rc, Weak},
task::{self, Context, Poll},
};
pub mod util;
// simple time type
pub type Time = f64;
/// Performs a single simulation run.
///
/// Input is a function that takes a simulation context and returns the first
/// process.
pub fn simulation<G, F>(shared: G, main: F) -> G
where
F: for<'s> FnOnce(Sim<'s, G>) -> Process<'s, G>,
{
let simulator;
let registry = Rc::default();
// create a fresh simulation context and a handle to it
simulator = Simulator::new(&shared, Rc::downgrade(&registry));
let sim = Sim { handle: &simulator };
// construct a custom context to pass into the poll()-method
let event_waker = StateEventWaker::new(sim);
let waker = unsafe { event_waker.as_waker() };
let mut cx = Context::from_waker(&waker);
// evaluate the passed function for the main process and schedule it
let root = main(sim);
simulator.schedule(root.clone());
// pop processes until empty or the main process terminates
while let Some(process) = simulator.next_process() {
if process.poll(&mut cx).is_ready() && process == root {
break;
}
// drop the active process if only the weak reference of the
// `active` field in the event calendar remains
if Rc::weak_count(&process.0) <= 1 {
registry.borrow_mut().remove(&process);
}
}
// Here we drop `registry` first and `simulator` after.
// Dropping the processes individually ensures that:
// 1. all processes are dropped without panicking, or
// 2. one process panics but all other processes are still dropped due to
// the iterator dropping, or
// 3. more than one process panics during its drop, leading to an abort.
//
// Note: it is not sufficient to simply drop the `registry` here, since the
// current `HashSet` implementation doesn't drop any elements after the
// first destructor panics. By moving the panic outside the drop impl, we
// can be sure that any weak process wakers leaked out of the `simulation`
// function can no longer be upgraded.
for process in registry.take() {
drop(process);
}
drop(registry);
// return the global data
shared
}
/// Private simulation context that holds a reference to the shared data, the
/// event calendar, and the owning container for all processes.
struct Simulator<'s, G> {
/// A reference to the globally shared data.
shared: &'s G,
/// The scheduler for processes.
calendar: RefCell<Calendar<'s, G>>,
/// The container that has strong ownership of the processes during a
/// simulation run.
registry: Weak<RefCell<HashSet<StrongProcess<'s, G>>>>,
}
impl<'s, G> Simulator<'s, G> {
/// Create a new simulator instance referencing shared data and using the
/// registry as owner of the processes.
fn new(shared: &'s G, registry: Weak<RefCell<HashSet<StrongProcess<'s, G>>>>) -> Self {
Simulator {
shared,
calendar: RefCell::new(Calendar::default()),
registry,
}
}
/// Returns a (reference-counted) copy of the currently active process.
fn active(&self) -> Process<'s, G> {
self.calendar
.borrow()
.active
.as_ref()
.expect("no active process")
.clone()
}
/// Returns the current simulation time.
fn now(&self) -> Time {
self.calendar.borrow().now
}
/// Schedules a process at the current simulation time.
fn schedule(&self, process: Process<'s, G>) {
self.schedule_in(Time::default(), process);
}
/// Schedules a process at a later simulation time.
fn schedule_in(&self, dt: Time, process: Process<'s, G>) {
self.calendar.borrow_mut().schedule_in(dt, process);
}
/// Returns the next process according to the event calendar.
fn next_process(&self) -> Option<StrongProcess<'s, G>> {
self.calendar.borrow_mut().next_process()
}
}
/// The (private) scheduler for processes.
struct Calendar<'s, G> {
/// The current simulation time.
now: Time,
/// The event-calendar organized chronologically.
events: BinaryHeap<NextEvent<'s, G>>,
/// The currently active process.
active: Option<Process<'s, G>>,
}
impl<G> Default for Calendar<'_, G> {
fn default() -> Self {
Self {
now: Time::default(),
events: Default::default(),
active: Default::default(),
}
}
}
impl<'s, G> Calendar<'s, G> {
/// Schedules a process at a later simulation time.
fn schedule_in(&mut self, dt: Time, process: Process<'s, G>) {
let calender = &mut self.events;
let strong_process = process.upgrade().expect("already terminated");
let is_scheduled = &strong_process.0.is_scheduled;
assert!(!is_scheduled.get(), "already scheduled");
is_scheduled.set(true);
calender.push(NextEvent {
move_time: self.now + dt,
process,
});
}
/// Removes the process with the next event time from the calendar and
/// activates it.
fn next_process(&mut self) -> Option<StrongProcess<'s, G>> {
loop {
let NextEvent {
move_time: now,
process,
} = self.events.pop()?;
let Some(strong_process) = process.upgrade() else {
// process was terminated after being queued
continue;
};
strong_process.0.is_scheduled.set(false);
self.now = now;
self.active = Some(process);
return Some(strong_process);
}
}
}
/// A light-weight handle to the simulation context.
pub struct Sim<'s, G = ()> {
handle: &'s Simulator<'s, G>,
}
// this allows the creation of copies
impl<G> Clone for Sim<'_, G> {
fn clone(&self) -> Self {
*self
}
}
// this allows moving the context without invalidating it (copy semantics)
impl<G> Copy for Sim<'_, G> {}
impl<'s, G> Sim<'s, G> {
/// Returns a (reference-counted) copy of the currently active process.
pub fn active(self) -> Process<'s, G> {
self.handle.active()
}
/// Activates a new process with the given future.
pub fn activate(self, f: impl Future<Output = ()> + 's) {
self.reactivate(Process::new(self, f));
}
/// Reactivates a process that has been suspended with wait().
pub fn reactivate(self, process: Process<'s, G>) {
self.handle.schedule(process);
}
/// Reactivates the currently active process after some time has passed.
pub async fn advance(self, dt: Time) {
self.handle.schedule_in(dt, self.handle.active());
sleep().await
}
/// Returns the current simulation time.
pub fn now(self) -> Time {
self.handle.now()
}
/// Returns a shared reference to the global data.
pub fn global(self) -> &'s G {
self.handle.shared
}
}
/// A bare-bone process type that can also be used as a waker.
pub struct Process<'s, G>(Weak<ProcessInner<'s, G>>);
/// A private accessor to the inner parts of a [`Process`].
///
/// **WARNING**: This must never leak into user code! Forgetting a
/// `StrongProcess` (`std::mem::forget`) causes *undefined behavior* because
/// it will keep the processes alive past the simulation, leading to dangling
/// pointers due to the lifetime erasure experienced by the wakers.
struct StrongProcess<'s, G>(Rc<ProcessInner<'s, G>>);
/// The private details of the [`Process`](struct.Process.html) type.
struct ProcessInner<'s, G> {
/// The simulation context needed to implement the `Waker` interface.
context: Sim<'s, G>,
/// A boolean flag indicating whether this process is currently scheduled.
is_scheduled: Cell<bool>,
/// `Some` [`Future`] associated with this process or `None` if it has been
/// terminated externally.
///
/// [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html
state: RefCell<Option<Pin<Box<dyn Future<Output = ()> + 's>>>>,
}
impl<'s, G> Process<'s, G> {
/// Combines a future and a simulation context to a process.
pub fn new(sim: Sim<'s, G>, fut: impl Future<Output = ()> + 's) -> Self {
let strong_process = StrongProcess(Rc::new(ProcessInner {
context: sim,
is_scheduled: Cell::new(false),
state: RefCell::new(Some(Box::pin(fut))),
}));
let process = strong_process.downgrade();
let registry = sim
.handle
.registry
.upgrade()
.expect("attempted to create process after simulation ended");
registry.borrow_mut().insert(strong_process);
process
}
/// Releases the [`Future`] contained in this process.
///
/// This will also execute all the destructors for the local variables
/// initialized by this process.
pub fn terminate(&self) {
let Some(strong_process) = self.upgrade() else {
// ok, we're already terminated
return;
};
let sim = strong_process.0.context;
assert!(
sim.handle
.calendar
.borrow()
.active
.as_ref()
.is_none_or(|active| active != self),
"attempted to terminate active process"
);
if let Some(registry) = sim.handle.registry.upgrade() {
registry.borrow_mut().remove(&strong_process);
} else {
// This can happen if we're dropping the `registry` and the
// destructor of another process tries to terminate this process.
// In this case, we need to manually drop our future now to ensure
// that there are no dangling references from our future to their
// future.
*strong_process.0.state.borrow_mut() = None;
};
drop(strong_process);
assert!(self.is_terminated(), "failed to terminate process");
}
/// Returns a `Waker` for this process.
pub fn waker(self) -> task::Waker {
unsafe { task::Waker::from_raw(self.raw_waker()) }
}
/// Returns whether this process has finished its life-cycle.
pub fn is_terminated(&self) -> bool {
self.upgrade()
.is_none_or(|strong_process| strong_process.0.state.borrow().is_none())
}
/// Gets private access to the process.
///
/// # Safety
///
/// The caller must ensure that the [`StrongProcess`] is not leaked.
/// In particular, it must not be passed to user code.
fn upgrade(&self) -> Option<StrongProcess<'s, G>> {
self.0.upgrade().map(StrongProcess)
}
}
impl<'s, G> StrongProcess<'s, G> {
/// Private function for polling the process.
fn poll(&self, cx: &mut Context<'_>) -> Poll<()> {
self.0
.state
.borrow_mut()
.as_mut()
.expect("attempted to poll terminated task")
.as_mut()
.poll(cx)
}
/// Converts the owning process into a weaker referencing process.
fn downgrade(&self) -> Process<'s, G> {
Process(Rc::downgrade(&self.0))
}
/// Returns whether this process is currently scheduled.
fn is_scheduled(&self) -> bool {
self.0.is_scheduled.get()
}
}
// Increases the reference counter of this process.
impl<G> Clone for Process<'_, G> {
fn clone(&self) -> Self {
Process(self.0.clone())
}
}
// allows processes to be compared for equality
impl<G> PartialEq for Process<'_, G> {
fn eq(&self, other: &Self) -> bool {
Weak::ptr_eq(&self.0, &other.0)
}
}
// marks the equality-relation as total
impl<G> Eq for Process<'_, G> {}
// hash processes for pointer equality
impl<G> Hash for Process<'_, G> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_ptr().hash(state);
}
}
// allows processes to be compared for equality
impl<G> PartialEq for StrongProcess<'_, G> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl<'s, G> PartialEq<Process<'s, G>> for StrongProcess<'s, G> {
fn eq(&self, other: &Process<'s, G>) -> bool {
Rc::as_ptr(&self.0) == other.0.as_ptr()
}
}
// marks the equality-relation as total
impl<G> Eq for StrongProcess<'_, G> {}
// hash processes for pointer equality
impl<G> Hash for StrongProcess<'_, G> {
fn hash<H: Hasher>(&self, state: &mut H) {
Rc::as_ptr(&self.0).hash(state);
}
}
/// Time-process-pair that has a total order defined based on the time.
struct NextEvent<'p, G> {
move_time: Time,
process: Process<'p, G>,
}
impl<G> PartialEq for NextEvent<'_, G> {
fn eq(&self, other: &Self) -> bool {
self.move_time == other.move_time
}
}
impl<G> Eq for NextEvent<'_, G> {}
impl<G> PartialOrd for NextEvent<'_, G> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<G> Ord for NextEvent<'_, G> {
fn cmp(&self, other: &Self) -> Ordering {
self.move_time
.partial_cmp(&other.move_time)
.expect("illegal event time NaN")
.reverse()
}
}
/* **************************** specialized waker *************************** */
impl<G> Process<'_, G> {
/// Virtual function table for the waker.
const VTABLE: task::RawWakerVTable =
task::RawWakerVTable::new(Self::clone, Self::wake, Self::wake_by_ref, Self::drop);
/// Constructs a raw waker from a simulation context and a process.
fn raw_waker(self) -> task::RawWaker {
task::RawWaker::new(Weak::into_raw(self.0) as *const (), &Self::VTABLE)
}
unsafe fn clone(this: *const ()) -> task::RawWaker {
let waker_ref =
&*ManuallyDrop::new(unsafe { Weak::from_raw(this as *const ProcessInner<'_, G>) });
// increase the reference counter once
// this is technically unsafe because Wakers are Send + Sync and so this
// call might be executed from a different thread, creating a data race
// hazard; we leave preventing this as an exercise to the reader!
let waker = waker_ref.clone();
task::RawWaker::new(Weak::into_raw(waker) as *const (), &Self::VTABLE)
}
unsafe fn wake(this: *const ()) {
let waker = unsafe { Weak::from_raw(this as *const ProcessInner<'_, G>) };
let process = Process(waker);
process.wake_impl();
}
unsafe fn wake_by_ref(this: *const ()) {
// keep the waker alive by incrementing the reference count
let waker = unsafe { Weak::from_raw(this as *const ProcessInner<'_, G>) };
let process = &*ManuallyDrop::new(Process(waker));
// this is technically unsafe because Wakers are Send + Sync and so this
// call might be executed from a different thread, creating a data race
// hazard; we leave preventing this as an exercise to the reader!
process.clone().wake_impl();
}
fn wake_impl(self) {
// this is technically unsafe because Wakers are Send + Sync and so this
// call might be executed from a different thread, creating a data race
// hazard; we leave preventing this as an exercise to the reader!
let Some(strong_process) = self.upgrade() else {
// this can happen if a synchronization structure forgets to clean
// up registered Waker objects on destruct; this would lead to
// hard-to-diagnose bugs if we were to ignore it
panic!("attempted to wake a terminated process");
};
if !strong_process.is_scheduled() {
strong_process.0.context.reactivate(self);
}
}
unsafe fn drop(this: *const ()) {
// this is technically unsafe because Wakers are Send + Sync and so this
// call might be executed from a different thread, creating a data race
// hazard; we leave preventing this as an exercise to the reader!
unsafe {
Weak::from_raw(this as *const RefCell<ProcessInner<'_, G>>);
}
}
}
/// Complex waker that is used to implement state events.
///
/// This is the shallow version that is created on the stack of the function
/// running the event loop. It creates the deep version when it is cloned.
struct StateEventWaker<'s, G> {
context: Sim<'s, G>,
}
impl<'s, G> StateEventWaker<'s, G> {
/// Virtual function table for the waker.
const VTABLE: task::RawWakerVTable =
task::RawWakerVTable::new(Self::clone, Self::wake, Self::wake_by_ref, Self::drop);
/// Creates a new (shallow) waker using only the simulation context.
fn new(sim: Sim<'s, G>) -> Self {
StateEventWaker { context: sim }
}
/// Constructs a new waker using only a reference.
///
/// # Safety
///
/// This function is unsafe because it is up to the caller to ensure that
/// the waker doesn't outlive the reference.
unsafe fn as_waker(&self) -> task::Waker {
unsafe {
task::Waker::from_raw(task::RawWaker::new(
self as *const _ as *const (),
&Self::VTABLE,
))
}
}
unsafe fn clone(this: *const ()) -> task::RawWaker {
// return the currently active process as a raw waker
unsafe { &*(this as *const Self) }
.context
.active()
.raw_waker()
}
unsafe fn wake(_this: *const ()) {
// waking the active process can safely be ignored
}
unsafe fn wake_by_ref(_this: *const ()) {
// waking the active process can safely be ignored
}
unsafe fn drop(_this: *const ()) {
// memory is released in the main event loop
}
}
/* *************************** specialized futures ************************** */
/// Returns a future that unconditionally puts the calling process to sleep.
pub fn sleep() -> impl Future<Output = ()> {
Sleep { ready: false }
}
/// Custom structure that implements [`Future`] by suspending on the first call
/// and returning on the second one.
///
/// # Note
/// This can be expressed more succinctly using the `poll_fn` function, but it
/// is easier to see what's happening in this version.
struct Sleep {
ready: bool,
}
impl Future for Sleep {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.ready {
Poll::Ready(())
} else {
self.ready = true;
Poll::Pending
}
}
}
/// Returns a future that can be awaited to produce the currently set waker.
pub fn waker() -> impl Future<Output = task::Waker> {
poll_fn(|cx| Poll::Ready(cx.waker().clone()))
}
#[cfg(test)]
mod tests {
use super::*;
use std::{panic::{AssertUnwindSafe, catch_unwind}, thread::panicking};
/// Test that waking a leaked waker does not cause UB on the happy path.
#[test]
#[should_panic = "attempted to wake a terminated process"]
fn leak_waker_simple() {
let shared = RefCell::new(None);
// Create one process and leak its Waker.
let shared = simulation(shared, |sim| {
Process::new(sim, async move {
*sim.global().borrow_mut() = Some(waker().await);
})
});
// This Waker must not be upgradeable.
shared.take().unwrap().wake();
}
/// Test that a leaked waker does not cause UB in presence of user panic.
#[test]
fn leak_waker_after_user_panic() {
let shared = RefCell::new([None, None]);
// Create two processes, leak their Wakers, and then panic.
catch_unwind(AssertUnwindSafe(|| simulation(&shared, |sim| {
Process::new(sim, async move {
sim.activate(async move {
sim.global().borrow_mut()[0] = Some(waker().await);
panic!();
});
sim.global().borrow_mut()[1] = Some(waker().await);
sim.advance(1.0).await;
})
}))).unwrap_err();
// Both Wakers must not be upgradeable.
catch_unwind(AssertUnwindSafe(
|| shared.borrow_mut()[0].take().unwrap().wake()
)).unwrap_err();
catch_unwind(AssertUnwindSafe(
|| shared.borrow_mut()[1].take().unwrap().wake()
)).unwrap_err();
}
/// Test that a leaked waker does not cause UB in presence of drop panic.
#[test]
fn leak_waker_after_drop_panic() {
let shared = RefCell::new([None, None]);
// Create processes, leak their Wakers, panic during drop.
catch_unwind(AssertUnwindSafe(|| simulation(&shared, |sim| {
Process::new(sim, async move {
struct Bomb;
impl Drop for Bomb {
fn drop(&mut self) {
// Only the first process panics, no double panic.
if !panicking() {
panic!();
}
}
}
for i in 0..2 {
sim.activate(async move {
let _boom = Bomb;
sim.global().borrow_mut()[i] = Some(waker().await);
sleep().await;
});
}
sim.advance(1.0).await;
})
}))).unwrap_err();
// Both Wakers must not be upgradeable.
for i in 0..2 {
catch_unwind(AssertUnwindSafe(
|| shared.borrow_mut()[i].take().unwrap().wake()
)).unwrap_err();
}
}
/// Test that all processes are dropped even if a destructor of a process unwinds.
/// This is required to properly invalidate all wakers that may be leaked.
#[test]
#[should_panic = "attempted to wake a terminated process"]
fn leak_waker_unwind() {
struct PanicOnDrop;
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("PanicOnDrop");
}
}
struct PushDropOrderOnDrop<'s> {
me: i32,
drop_order: &'s RefCell<Vec<i32>>,
}
impl Drop for PushDropOrderOnDrop<'_> {
fn drop(&mut self) {
self.drop_order.borrow_mut().push(self.me);
}
}
#[derive(Default)]
struct Shared {
channel: RefCell<Option<task::Waker>>,
drop_order: RefCell<Vec<i32>>,
}
let shared = Shared::default();
catch_unwind(AssertUnwindSafe(|| {
simulation(&shared, |sim| {
Process::new(sim, async move {
// This process is dropped 2nd and will begin the unwind.
sim.activate(async move {
let _guard = PushDropOrderOnDrop {
me: 2,
drop_order: &sim.global().drop_order,
};
let _guard = PanicOnDrop;
sim.advance(2.0).await;
unreachable!();
});
// This process is dropped 3rd, after the unwind started.
// We test that this process cannot be woken from a leaked waker.
sim.activate(async move {
let _guard = PushDropOrderOnDrop {
me: 3,
drop_order: &sim.global().drop_order,
};
poll_fn(|cx| {
*sim.global().channel.borrow_mut() = Some(cx.waker().clone());
Poll::Ready(())
})
.await;
sim.advance(2.0).await;
unreachable!();
});
let _guard: PushDropOrderOnDrop = PushDropOrderOnDrop {
me: 1,
drop_order: &sim.global().drop_order,
};
sim.advance(1.0).await;
// Finish the main process here, this will drop the other processes.
})
});
}))
.unwrap_err();
shared.drop_order.borrow_mut().sort();
assert_eq!(*shared.drop_order.borrow(), [1, 2, 3]);
shared.channel.take().unwrap().wake();
}
}
use simcore_rs::util::channel;
use simcore_rs::*;
fn main() {
simulation((), |sim| {
Process::new(sim, async move {
let (sx, rx) = channel();
sim.activate(async move {
rx.recv().await;
});
sim.advance(1.0).await;
sx.send(1).await.unwrap();
})
});
}
//! This module provides a collection of utilities for asynchronous simulation
//! and concurrency control, inspired by GPSS (General Purpose Simulation
//! System). It includes:
//!
//! - **Facility**: A synchronization primitive representing an exclusive
//! resource that can be seized and released by multiple processes. Processes
//! can queue and wait for access to the resource.
//!
//! - **Promise**: A one-shot, writable container that awakens a pre-registered
//! process when a value is written to it. Useful for coordinating between
//! asynchronous tasks.
//!
//! - **select** function: An asynchronous function that takes two futures and
//! returns the result of the first one to complete, canceling the other.
//! Useful for implementing race conditions where the first completion
//! determines the outcome.
//!
//! - **Channel**, **Sender**, and **Receiver**: An unbounded channel
//! implementation supporting multiple readers and writers. Processes can send
//! and receive messages asynchronously, using wakers to reactivate suspended
//! processes when messages become available.
//!
//! - **Controlled** trait and **Control** struct: The trait signifies that a
//! type can broadcast state changes to wakers, allowing processes to wait on
//! arbitrary conditions. The **Control** struct is a state variable that can
//! be used with the `until()` function to suspend execution until a specified
//! condition is met.
//!
//! - **until** function: Returns a future that suspends until a given boolean
//! condition involving controlled expressions evaluates to `true`.
//!
//! - **RandomVar**: A statistical data collector that records observations,
//! computes statistics like mean and standard deviation, and can merge data
//! from other collectors. Inspired by SLX's `random_variable`.
//!
//! These utilities are designed to facilitate the development of simulations
//! and asynchronous systems by providing essential components for process
//! synchronization, inter-process communication, and statistical analysis.
use crate::{Process, Sim, sleep, waker};
use std::{
cell::{Cell, RefCell},
collections::VecDeque,
fmt,
future::Future,
pin::Pin,
rc::{Rc, Weak},
task::{self, Context, Poll},
};
/// GPSS-inspired facility that houses one exclusive resource to be used by
/// arbitrary many processes.
#[derive(Default)]
pub struct Facility {
/// A queue of waiting-to-be-activated processes.
queue: RefCell<VecDeque<task::Waker>>,
/// A boolean indicating whether the facility is in use or not.
in_use: Cell<bool>,
}
impl Facility {
/// Creates a new facility.
pub fn new() -> Self {
Facility {
queue: RefCell::new(VecDeque::new()),
in_use: Cell::new(false),
}
}
/// Attempts to seize the facility, blocking until it's possible.
pub async fn seize(&self) {
if self.in_use.replace(true) {
let waker = waker().await;
self.queue.borrow_mut().push_back(waker);
sleep().await;
}
}
/// Releases the facility and activates the next waiting process.
pub fn release(&self) {
if let Some(process) = self.queue.borrow_mut().pop_front() {
process.wake();
} else {
self.in_use.set(false);
}
}
}
/// A one-shot, writable container type that awakens a pre-registered process
/// on write.
pub struct Promise<T> {
/// The waker of the process to awaken on write.
caller: task::Waker,
/// The written value to be extracted by the caller.
result: Cell<Option<T>>,
}
impl<T> Promise<T> {
/// Creates a new promise with an unwritten result.
pub fn new(waker: task::Waker) -> Self {
Self {
caller: waker,
result: Cell::new(None),
}
}
/// Writes the result value and reawakens the caller.
pub fn fulfill(&self, result: T) {
if self.result.replace(Some(result)).is_none() {
self.caller.wake_by_ref();
}
}
/// Extracts the written value and resets the state of the promise.
pub fn redeem(&self) -> Option<T> {
self.result.replace(None)
}
}
/// Returns a future that takes two other futures and completes as soon as
/// one of them returns, canceling the other future before returning.
///
/// This design guarantees that the two futures passed as input to this
/// function cannot outlive its returned future, enabling us to allow
/// references to variables in the local scope. The passed futures may
/// compute a value, as long as the return type is identical in both cases.
pub async fn select<'s, 'u, G, E, O, R>(sim: Sim<'s, G>, either: E, or: O) -> R
where
E: Future<Output = R> + 'u,
O: Future<Output = R> + 'u,
R: 'u,
{
use std::mem::transmute;
// create a one-shot channel that reactivates the caller on write
let promise = Promise::new(sim.active().waker());
// this unsafe block shortens the guaranteed lifetimes of the processes
// contained in the scheduler; it is safe because the constructed future
// ensures that both futures are terminated before the promise and
// itself are terminated, thereby preventing references to the lesser
// constrained processes to survive the call to select()
let sim = unsafe { transmute::<Sim<'s, G>, Sim<'_, G>>(sim) };
{
// create the two competing processes
// a future optimization would be to keep them on the stack
let p1 = Process::new(sim, async {
promise.fulfill(either.await);
});
let p2 = Process::new(sim, async {
promise.fulfill(or.await);
});
struct TerminateOnDrop<'s, G>(Process<'s, G>);
impl<G> Drop for TerminateOnDrop<'_, G> {
fn drop(&mut self) {
self.0.terminate();
}
}
// `p1` and `p2` borrow from `promise` and must therefore be dropped
// before `promise` is dropped. In particular, we must ensure that the
// future returned by the outer `async fn` outlives the futures crated
// by the `async` blocks above.
let _g1 = TerminateOnDrop(p1.clone());
let _g2 = TerminateOnDrop(p2.clone());
// activate them
sim.reactivate(p1);
sim.reactivate(p2);
// wait for reactivation; the promise will wake us on fulfillment
sleep().await;
}
// extract the promised value
promise.redeem().unwrap()
}
/// Complex channel with space for infinitely many elements of arbitrary type.
///
/// This channel supports arbitrary many readers and writers and uses wakers
/// to reactivate suspended processes.
struct Channel<T> {
/// A queue of messages.
store: VecDeque<T>,
/// A queue of processes waiting to receive a message.
waiting: VecDeque<(task::Waker, usize)>,
/// The number of unique wakers waiting so far.
ids: usize,
}
/// Creates a channel and returns a pair of read and write ends.
pub fn channel<T>() -> (Sender<T>, Receiver<T>) {
let channel = Rc::new(RefCell::new(Channel {
store: VecDeque::new(),
waiting: VecDeque::new(),
ids: 0,
}));
(Sender(Rc::downgrade(&channel)), Receiver(channel))
}
/// Write-end of a channel.
pub struct Sender<T>(Weak<RefCell<Channel<T>>>);
/// Read-end of a channel.
pub struct Receiver<T>(Rc<RefCell<Channel<T>>>);
// Allow senders to be duplicated.
impl<T> Clone for Sender<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
// Allow receivers to be duplicated.
impl<T> Clone for Receiver<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T: Unpin> Sender<T> {
/// Returns a future that can be awaited to send a message.
pub fn send(&self, elem: T) -> SendFuture<'_, T> {
SendFuture(&self.0, Some(elem))
}
}
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
// check if there are still receivers
if let Some(chan) = self.0.upgrade() {
// check if we're the last sender to drop
if Rc::weak_count(&chan) == 1 {
// awake all waiting receivers so that they get to return
for (process, _) in chan.borrow_mut().waiting.drain(..) {
process.wake();
}
}
}
}
}
impl<T: Unpin> Receiver<T> {
/// Returns a future that can be awaited to receive a message.
pub fn recv(&self) -> ReceiveFuture<'_, T> {
ReceiveFuture(&self.0, 0)
}
/// Returns the number of elements that can be received before model time
/// has to be consumed.
pub fn len(&self) -> usize {
self.0.borrow().len()
}
/// Returns if the receiver is empty at the current model time.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<T> Channel<T> {
/// Private method returning the number of elements left in the channel.
fn len(&self) -> usize {
self.store.len()
}
/// Private method enqueuing a process into the waiting list.
fn enqueue(&mut self, process: task::Waker) -> usize {
self.ids = self.ids.checked_add(1).expect("ID overflow");
self.waiting.push_back((process, self.ids));
self.ids
}
/// Private method removing a process from the waiting list.
fn dequeue(&mut self) -> Option<task::Waker> {
self.waiting.pop_front().map(|(p, _)| p)
}
/// Private method that unregisters a previously registered waker.
fn unregister(&mut self, id: usize) {
if let Some(pos) = self.waiting.iter().position(|(_, tid)| *tid == id) {
self.waiting.remove(pos);
}
}
/// Private method inserting a message into the queue.
fn send(&mut self, value: T) {
self.store.push_back(value);
}
/// Private method extracting a message from the queue non-blocking.
fn recv(&mut self) -> Option<T> {
self.store.pop_front()
}
}
/// Future for the [`send()`] operation on a channel sender.
///
/// [`send()`]: struct.Sender.html#method.send
pub struct SendFuture<'c, T>(&'c Weak<RefCell<Channel<T>>>, Option<T>);
/// Future for the [`recv()`] operation on a channel receiver.
///
/// [`recv()`]: struct.Receiver.html#method.recv
pub struct ReceiveFuture<'c, T>(&'c Rc<RefCell<Channel<T>>>, usize);
impl<T: Unpin> Future for SendFuture<'_, T> {
type Output = Result<(), T>;
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
let elem = self.1.take().unwrap();
// test if there are still readers left to listen
if let Some(channel) = self.0.upgrade() {
let mut channel = channel.borrow_mut();
channel.send(elem);
// awake a waiting process
if let Some(process) = channel.dequeue() {
process.wake();
}
Poll::Ready(Ok(()))
} else {
Poll::Ready(Err(elem))
}
}
}
impl<T: Unpin> Future for ReceiveFuture<'_, T> {
type Output = Option<T>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut channel = self.0.borrow_mut();
// clear the local copy of our waker to prevent unnecessary
// de-registration attempts in our destructor
self.1 = 0;
if let Some(c) = channel.recv() {
// there are elements in the channel
Poll::Ready(Some(c))
} else if Rc::weak_count(self.0) == 0 {
// no elements in the channel and no potential senders exist
Poll::Ready(None)
} else {
// no elements, but potential senders exist: check back later
let waker = cx.waker().clone();
self.1 = channel.enqueue(waker);
Poll::Pending
}
}
}
impl<T> Drop for ReceiveFuture<'_, T> {
fn drop(&mut self) {
// take care to unregister our waker from the channel
if self.1 > 0 {
self.0.borrow_mut().unregister(self.1);
}
}
}
/// A trait signifying that the type implementing it is able to broadcast state
/// changes to [wakers].
///
/// Types implementing it can participate in [`until()`] expressions, the most
/// commonly-used of them being [`Control`] variables.
///
/// [wakers]: https://doc.rust-lang.org/std/task/struct.Waker.html
/// [`until()`]: fn.until.html
/// [`Control`]: struct.Control.html
pub trait Publisher {
/// Allows a [waker] to subscribe to the controlled expression to be
/// informed about any and all state changes.
///
/// # Safety
/// This method is unsafe, because the controlled expression will call the
/// [`wake_by_ref()`] method on the waker during every change in state. The caller
/// is responsible to ensure the validity of all subscribed wakers at
/// the time of the state change.
///
/// The [`span()`] method provides a safe alternative.
///
/// [waker]: https://doc.rust-lang.org/std/task/struct.Waker.html
/// [`span()`]: trait.Controlled.html#method.span
/// [`wake_by_ref()`]: https://doc.rust-lang.org/std/task/struct.Waker.html#method.wake_by_ref
unsafe fn subscribe(&self, waker: &task::Waker);
/// Unsubscribes a previously subscribed [waker] from the controlled
/// expression.
///
/// # Safety
/// This method is unsafe, because it asserts that the waker that it
/// receives has been registered using [subscribe] previously, and has not
/// been unsubscribed from yet.
///
/// [waker]: https://doc.rust-lang.org/std/task/struct.Waker.html
/// [subscribe]: Self::subscribe
unsafe fn unsubscribe(&self, waker: &task::Waker);
}
/// Guarding structure that ensures that waker and controlled expression are
/// valid and fixed in space.
pub struct WakerSpan<'s, C: ?Sized + Publisher> {
/// The controlled expression.
cv: &'s C,
/// The waker.
waker: &'s task::Waker,
}
// implement a constructor for the guard
impl<'s, C: ?Sized + Publisher> WakerSpan<'s, C> {
/// Subscribes a [waker] to a controlled expression for a certain duration.
///
/// This method subscribes the waker to the controlled expression and
/// returns an object that unsubscribes the waker from the same controlled
/// expression upon drop. The borrow-checker secures the correct lifetimes
/// of waker and controlled expression so that this method can be safe.
///
/// [waker]: https://doc.rust-lang.org/std/task/struct.Waker.html
pub fn new(cv: &'s C, waker: &'s task::Waker) -> Self {
// this is safe because we bind the lifetime of the waker to the guard
unsafe {
cv.subscribe(waker);
}
WakerSpan { cv, waker }
}
}
// implement drop for the guard
impl<C: ?Sized + Publisher> Drop for WakerSpan<'_, C> {
fn drop(&mut self) {
// this is safe because we subscribed in the only public constructor
unsafe {
self.cv.unsubscribe(self.waker);
}
}
}
// a reference of a controlled expression is also a controlled expression
impl<T: Publisher> Publisher for &'_ T {
unsafe fn subscribe(&self, waker: &task::Waker) {
unsafe {
Publisher::subscribe(*self, waker);
}
}
unsafe fn unsubscribe(&self, waker: &task::Waker) {
unsafe {
Publisher::unsubscribe(*self, waker);
}
}
}
/// Marks a variable as a state variable which can be used in conjunction with
/// the [`until()`] function.
///
/// Whenever a control variable changes its value, the runtime system checks
/// whether any other parts of the model are currently waiting for the variable
/// to attain a certain value.
///
/// [`until()`]: fn.until.html
pub struct Control<T> {
/// The inner value.
value: Cell<T>,
/// A list of waiting processes, identified by their [wakers].
///
/// [wakers]: https://doc.rust-lang.org/std/task/struct.Waker.html
waiting: RefCell<Vec<task::Waker>>,
}
impl<T> Control<T> {
/// Creates a new control variable and initializes its value.
pub const fn new(value: T) -> Self {
Self {
value: Cell::new(value),
waiting: RefCell::new(Vec::new()),
}
}
/// Assigns a new value to the control variable.
///
/// This can lead to potential activations of waiting processes.
pub fn set(&self, val: T) {
self.notify();
self.value.set(val);
}
/// Extracts the current value from the control variable.
pub fn get(&self) -> T
where
T: Copy,
{
self.value.get()
}
/// Notifies all the waiting processes to re-check their state condition.
///
/// This action is usually performed automatically when the value of the
/// control variable is changed through the [`set()`]-method but may be
/// triggered manually when this mechanism is bypassed somehow.
///
/// [`set()`]: #method.set
pub fn notify(&self) {
for waker in self.waiting.borrow().iter() {
waker.wake_by_ref();
}
}
}
// implement the trait marking control variables as controlled expressions
impl<T: Copy> Publisher for Control<T> {
unsafe fn subscribe(&self, waker: &task::Waker) {
self.waiting.borrow_mut().push(waker.clone());
}
unsafe fn unsubscribe(&self, waker: &task::Waker) {
let mut waiting = self.waiting.borrow_mut();
let pos = waiting.iter().position(|w| w.will_wake(waker));
if let Some(pos) = pos {
waiting.remove(pos);
} else {
panic!("attempt to unsubscribe waker that isn't subscribed to");
}
}
}
impl<T: Default> Default for Control<T> {
fn default() -> Self {
Self {
value: Cell::new(T::default()),
waiting: RefCell::new(Vec::new()),
}
}
}
/// An internal macro to generate implementations of the [`Publisher`] trait
/// for tuples of expressions.
macro_rules! controlled_tuple_impl {
// base rule generating an implementation for a concrete tuple
($($T:ident -> $ID:tt),* .) => {
impl<$($T:Publisher),*> Publisher for ($($T,)*) {
unsafe fn subscribe(&self, _waker: &task::Waker) {
unsafe {
$(self.$ID.subscribe(_waker);)*
}
}
unsafe fn unsubscribe(&self, _waker: &task::Waker) {
unsafe {
$(self.$ID.unsubscribe(_waker);)*
}
}
}
};
($($T:ident -> $ID:tt),* . $HEAD:ident -> $HID:tt $(, $TAIL:ident -> $TID:tt)*) => {
controlled_tuple_impl!($($T -> $ID),* .);
controlled_tuple_impl!($($T -> $ID,)* $HEAD -> $HID . $($TAIL -> $TID),*);
};
($HEAD:ident -> $HID:tt $(, $TAIL:ident -> $TID:tt)* $(,)?) => {
controlled_tuple_impl!($HEAD -> $HID . $($TAIL -> $TID),*);
};
}
controlled_tuple_impl! {
A -> 0, B -> 1, C -> 2, D -> 3, E -> 4, F -> 5, G -> 6, H -> 7,
I -> 8, J -> 9, K ->10, L ->11, M ->12, N ->13, O ->14, P ->15,
}
/// Returns a future that suspends until an arbitrary boolean condition
/// involving control expressions evaluates to `true`.
pub async fn until<C: Publisher>(set: C, pred: impl Fn(&C) -> bool) {
if !pred(&set) {
let waker = waker().await;
let _span = WakerSpan::new(&set, &waker);
loop {
sleep().await;
if pred(&set) {
break;
}
}
}
}
/* ************************* statistical facilities ************************* */
/// A simple collector for statistical data, inspired by SLX's random_variable.
///
/// # Warning
/// This implementation is not numerically stable, since it keeps sums of the
/// passed values and of the squared values. It would be better to use a
/// numerically stable online algorithm instead.
#[derive(Clone)]
pub struct RandomVariable {
total: Cell<u32>,
sum: Cell<f64>,
sqr: Cell<f64>,
min: Cell<f64>,
max: Cell<f64>,
}
impl RandomVariable {
/// Creates a new random variable.
pub fn new() -> Self {
RandomVariable::default()
}
/// Resets all stored statistical data.
pub fn clear(&self) {
self.total.set(0);
self.sum.set(0.0);
self.sqr.set(0.0);
self.min.set(f64::INFINITY);
self.max.set(f64::NEG_INFINITY);
}
/// Adds another value to the statistical collection.
pub fn tabulate<T: Into<f64>>(&self, val: T) {
let val: f64 = val.into();
self.total.set(self.total.get() + 1);
self.sum.set(self.sum.get() + val);
self.sqr.set(self.sqr.get() + val * val);
if self.min.get() > val {
self.min.set(val);
}
if self.max.get() < val {
self.max.set(val);
}
}
/// Combines the statistical collection of two random variables into one.
pub fn merge(&self, other: &Self) {
self.total.set(self.total.get() + other.total.get());
self.sum.set(self.sum.get() + other.sum.get());
self.sqr.set(self.sqr.get() + other.sqr.get());
if self.min.get() > other.min.get() {
self.min.set(other.min.get());
}
if self.max.get() < other.max.get() {
self.max.set(other.max.get());
}
}
}
impl Default for RandomVariable {
fn default() -> Self {
RandomVariable {
total: Cell::default(),
sum: Cell::default(),
sqr: Cell::default(),
min: Cell::new(f64::INFINITY),
max: Cell::new(f64::NEG_INFINITY),
}
}
}
impl fmt::Debug for RandomVariable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let total = self.total.get();
let mean = self.sum.get() / f64::from(total);
let variance = self.sqr.get() / f64::from(total) - mean * mean;
let std_dev = variance.sqrt();
f.debug_struct("RandomVariable")
.field("n", &total)
.field("min", &self.min.get())
.field("max", &self.max.get())
.field("mean", &mean)
.field("sdev", &std_dev)
.finish()
}
}