1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
// SPDX-FileCopyrightText: Copyright (c) Siemens 2026 contributed by Christoph Kuhmuench christoph.kuhmuench@gmail.com
//
// SPDX-License-Identifier: MIT OR Apache-2.0
//! **simu in 10 minutes** — a guided tour, one concept per chapter.
//!
//! This tutorial mirrors the structure of SimPy's
//! ["SimPy in 10 minutes"](https://simpy.readthedocs.io/en/latest/simpy_intro/)
//! so that readers who know SimPy can map their knowledge directly, and
//! newcomers get the gentlest possible on-ramp. Every code block is a doc-test:
//! it compiles and runs on every `cargo test`, so the tutorial cannot drift out
//! of date.
//!
//! | Chapter | You will learn |
//! |---------|----------------|
//! | [`ch01_basic_concepts`] | What a process is; spawning; timeouts; running the clock |
//! | [`ch02_waiting_for_processes`] | One process waiting for another to finish |
//! | [`ch03_events_and_cancellation`] | Signalling between processes; cancelling work early |
//! | [`ch04_shared_resources`] | Queuing for limited resources |
//! | [`ch05_how_to_proceed`] | The rest of the toolbox, and where to go next |
//!
//! Each chapter's example also exists as a runnable program in `examples/`
//! (`cargo run --example intro_car`, etc.).
//!
//! This module contains no code — only documentation.
/// Chapter 1: Basic concepts — processes, timeouts, and the clock.
///
/// A discrete-event simulation models a system as **processes** that do
/// something, wait, and do something again. While a process waits, simulated
/// time jumps directly to the next interesting moment — nothing "runs" in
/// between, which is why a simulated year can take milliseconds of wall-clock
/// time.
///
/// In simu, a process is an ordinary `async` block. Waiting is `.await`ing a
/// [`Timeout`](crate::Timeout): the executor suspends the process and resumes
/// it when the simulated clock reaches the deadline. There is no tokio and no
/// threads — one [`SimEnv`](crate::SimEnv) owns the clock and drives
/// everything.
///
/// Our first process models a car that alternately parks and drives:
///
/// ```
/// use simu::SimEnv;
///
/// let mut env = SimEnv::with_seed(42);
/// let h = env.handle(); // cheap Clone handle, moved into the process
///
/// env.spawn(async move {
/// loop {
/// println!("Start parking at {}", h.now());
/// h.timeout(5.0).await; // park for 5 time units
///
/// println!("Start driving at {}", h.now());
/// h.timeout(2.0).await; // drive for 2 time units
/// }
/// });
///
/// env.run_until(15.0); // drive the event loop until t = 15
/// assert_eq!(env.now(), 15.0);
/// ```
///
/// Output:
///
/// ```text
/// Start parking at 0
/// Start driving at 5
/// Start parking at 7
/// Start driving at 12
/// Start parking at 14
/// ```
///
/// Things worth noticing:
///
/// - [`SimEnv::with_seed`](crate::SimEnv::with_seed) makes the run
/// reproducible; same seed + same logic = identical results, always.
/// - The process gets an [`EnvHandle`](crate::EnvHandle) (`env.handle()`),
/// not the env itself: the env stays outside driving the loop, the handle
/// goes inside for `now()` / `timeout()` / `spawn()`.
/// - The process loops forever; that is fine.
/// [`run_until`](crate::SimEnv::run_until) stops the world at t = 15, and
/// dropping the env reclaims the still-suspended process.
/// - Time is `f64` and unit-less — *you* decide whether 1.0 means a second or
/// a day.
/// Chapter 2: Waiting for another process.
///
/// Processes can start other processes and wait for them — the building block
/// for "do this sub-task, then continue". In SimPy you `yield env.process(...)`;
/// in simu, [`spawn`](crate::EnvHandle::spawn) returns a
/// [`ProcessHandle`](crate::ProcessHandle) which is itself a future: awaiting
/// it suspends you until the child process finishes and hands you its return
/// value.
///
/// Our car is now electric. After every trip it must charge before it can
/// drive again — and charging is its own process:
///
/// ```
/// use simu::SimEnv;
///
/// let mut env = SimEnv::with_seed(42);
/// let h = env.handle();
///
/// env.spawn(async move {
/// loop {
/// println!("Start driving at {}", h.now());
/// h.timeout(2.0).await;
///
/// println!("Start charging at {}", h.now());
/// let hc = h.clone();
/// let charging = h.spawn(async move {
/// hc.timeout(5.0).await;
/// 42.0 // a process can return a value, e.g. the kWh charged
/// });
/// let kwh = charging.await; // suspend until charging finishes
/// assert_eq!(kwh, 42.0);
/// }
/// });
///
/// env.run_until(15.0);
/// ```
///
/// Output:
///
/// ```text
/// Start driving at 0
/// Start charging at 2
/// Start driving at 7
/// Start charging at 9
/// Start driving at 14
/// ```
///
/// Two details:
///
/// - Handles are cheap clones sharing one env; clone freely
/// (`let hc = h.clone()`) whenever a child process needs its own.
/// - If you *don't* need the result, just drop the
/// [`ProcessHandle`](crate::ProcessHandle) — the child keeps running,
/// fire-and-forget.
/// Chapter 3: Events, and cancelling work early.
///
/// Timeouts model *known* waiting times. For "wait until something happens",
/// simu has manual events: [`env.event()`](crate::SimEnv::event) returns a
/// paired ([`EventTrigger`](crate::EventTrigger),
/// [`EventAwaitable`](crate::EventAwaitable)). Awaiting the awaitable suspends
/// a process until someone calls [`fire()`](crate::EventTrigger::fire) on the
/// trigger. The awaitable is `Clone`, so many processes can wait on one event;
/// firing after the fact is fine too — late awaiters resolve immediately.
///
/// SimPy's version of "stop what you're doing" is throwing an `Interrupt`
/// into a process. simu has no interrupt (it is on the roadmap — SPEC §6);
/// instead, cancellation is expressed by **racing futures** with
/// [`any_of!`](crate::any_of): await *either* the work finishing *or* a stop
/// signal, whichever comes first. The losing future is dropped — and dropping
/// *is* cancellation in Rust.
///
/// The driver gets impatient and stops a 5-unit charge after 3 units:
///
/// ```
/// use simu::{SimEnv, any_of};
///
/// let mut env = SimEnv::with_seed(42);
/// let (stop_charging, stop_signal) = env.event();
///
/// // The car: charge fully — unless told to stop.
/// let h = env.handle();
/// let car = env.spawn(async move {
/// println!("Start charging at {}", h.now());
/// any_of![h.timeout(5.0), stop_signal].await;
/// println!("Stop charging at {}", h.now());
/// h.now() // return when charging actually ended
/// });
///
/// // The driver: after 3 time units, wants to leave.
/// let h2 = env.handle();
/// env.spawn(async move {
/// h2.timeout(3.0).await;
/// stop_charging.fire(); // wake everyone awaiting the signal
/// });
///
/// let h3 = env.handle();
/// env.spawn(async move {
/// let stopped_at = car.await;
/// assert_eq!(stopped_at, 3.0); // the event won the race, not the timeout
/// let _ = h3; // (nothing else to do)
/// });
///
/// env.run();
/// ```
///
/// Notes:
///
/// - `fire()` **consumes** the trigger — an event fires at most once. A
/// dropped, never-fired trigger simply means the signal never arrives
/// (waiters stay suspended until the run ends), which is a normal
/// discrete-event outcome, not an error.
/// - After the race, the abandoned `timeout(5.0)` still has a queue entry;
/// its wakeup at t = 5 is a benign no-op. That is why `env.run()` above
/// ends at t = 5, not t = 3 — use `run_until` if the end time matters.
/// - For being kicked off a *resource* by higher-priority work, see
/// [`PreemptiveResource`](crate::PreemptiveResource) — same racing pattern,
/// built in.
/// Chapter 4: Shared resources — queuing for limited capacity.
///
/// Real systems have contention: two charging spots, one doctor, three beds.
/// A [`Resource`](crate::Resource) models a pool of identical units.
/// [`request()`](crate::Resource::request) resolves immediately if a unit is
/// free, otherwise the process suspends in a FIFO queue. The resolved value is
/// an RAII [`ResourceGuard`](crate::ResourceGuard): the unit is released when
/// the guard drops — no explicit `release()` call, and no way to forget it.
///
/// Sharing works by **cloning the handle** — every clone is the same pool.
/// (No `Arc`, no `Mutex`: the whole simulation is single-threaded by design.)
///
/// Four cars arrive, staggered, at a two-spot battery charging station:
///
/// ```
/// use simu::{SimEnv, Resource};
///
/// let mut env = SimEnv::with_seed(42);
/// let bcs = Resource::new(2); // battery charging station, 2 spots
///
/// for i in 0..4u32 {
/// let h = env.handle();
/// let station = bcs.clone(); // same pool, cheap Rc clone
/// env.spawn(async move {
/// h.timeout(f64::from(i) * 2.0).await; // drive to the station
/// println!("Car {i} arriving at {}", h.now());
///
/// let _spot = station.request().await; // queue for a spot (FIFO)
/// println!("Car {i} starting to charge at {}", h.now());
///
/// h.timeout(5.0).await; // charge
/// println!("Car {i} leaving at {}", h.now());
/// }); // _spot drops here → spot handed to the next car in line
/// }
///
/// env.run();
/// assert_eq!(env.now(), 12.0); // last car: arrives t=6, waits, charges 7→12
/// ```
///
/// Output:
///
/// ```text
/// Car 0 arriving at 0
/// Car 0 starting to charge at 0
/// Car 1 arriving at 2
/// Car 1 starting to charge at 2
/// Car 2 arriving at 4
/// Car 0 leaving at 5
/// Car 2 starting to charge at 5
/// Car 3 arriving at 6
/// Car 1 leaving at 7
/// Car 3 starting to charge at 7
/// Car 2 leaving at 10
/// Car 3 leaving at 12
/// ```
///
/// Cars 0 and 1 charge immediately; cars 2 and 3 queue and take over spots the
/// moment earlier cars leave. Release-to-next-waiter is a **direct handoff**:
/// a freshly released unit can never be stolen by a same-instant new request
/// jumping the queue.
/// Chapter 5: How to proceed.
///
/// You now know the core loop of every simu model: spawn processes, await
/// timeouts / events / resources, run, read out results. The rest of the
/// toolbox, in the order you are likely to need it:
///
/// - [`PriorityResource`](crate::PriorityResource) — like
/// [`Resource`](crate::Resource), but `request(priority)` serves lower
/// numbers first (FIFO within a level). Triage queues, VIP lanes.
/// - [`PreemptiveResource`](crate::PreemptiveResource) — a priority pool where
/// an urgent request can *evict* a lower-priority holder mid-service; the
/// victim observes it via `guard.preempted()`. See the type docs for the
/// full pattern.
/// - [`Container`](crate::Container) — continuous quantity instead of discrete
/// units: tanks, silos, blood banks. `put(amount)` / `get(amount)` with
/// strict FIFO waiters.
/// - [`all_of!`](crate::all_of) — the dual of
/// [`any_of!`](crate::any_of): wait for *every* sub-future (barrier /
/// fork-join).
/// - **Randomness** — [`h.rng()`](crate::EnvHandle::rng) borrows the env's
/// seeded RNG; combine with [`rng::sample`](crate::rng::sample) or
/// `rand_distr` for stochastic arrival/service times. Sample *before*
/// `.await` — the borrow cannot be held across a suspension point.
/// - **Monte Carlo** — [`monte_carlo::run`](crate::monte_carlo::run) executes
/// one full, independent simulation per seed in parallel threads:
///
/// ```
/// use simu::{SimEnv, monte_carlo};
///
/// let end_times = monte_carlo::run(0..8u64, |seed| {
/// let mut env = SimEnv::with_seed(seed);
/// let h = env.handle();
/// env.spawn(async move { h.timeout(1.0).await; });
/// env.run();
/// env.now()
/// });
/// assert_eq!(end_times.len(), 8); // results arrive in seed order
/// ```
///
/// When you are ready for full models, three commented showcases combine
/// everything above, each with a walkthrough document in `examples/`:
///
/// - `cargo run --example hospital` — ER with priority triage, bed eviction,
/// and a blood bank (`PriorityResource`, `PreemptiveResource` precursor
/// patterns, `Container`).
/// - `cargo run --example brewery` — a fermentation line with contamination
/// events and cleanup priorities (`EventTrigger`, `PriorityResource`).
/// - `cargo run --example warehouse` — a forklift fleet shared between
/// receiving and shipping (`PreemptiveResource` end-to-end).
///
/// Coming from SimPy? The repository root has `llms.txt` with a complete
/// SimPy → simu translation table.