simu/event.rs
1// SPDX-FileCopyrightText: Copyright (c) Siemens 2026 contributed by Christoph Kuhmuench christoph.kuhmuench@gmail.com
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use std::cell::RefCell;
6use std::future::Future;
7use std::pin::Pin;
8use std::rc::Rc;
9use std::task::{Context, Poll, Waker};
10
11#[derive(Debug)]
12struct EventState {
13 fired: bool,
14 waiters: Vec<Waker>,
15}
16
17/// The sending half of a manual event. Call [`fire`](EventTrigger::fire) to
18/// wake all processes currently waiting on the paired [`EventAwaitable`], and
19/// to make any *future* awaits on that same awaitable resolve immediately.
20///
21/// **Dropping a trigger without firing it strands its waiters.** If an
22/// `EventTrigger` is dropped without `fire()` being called, the event never
23/// fires: any process currently awaiting the paired `EventAwaitable` — and any
24/// that awaits it later — will suspend forever (until the run ends and
25/// [`SimEnv`](crate::SimEnv)'s `Drop` reclaims the suspended processes). This is
26/// a normal discrete-event outcome (a signal that simply never arrives), not a
27/// panic; if a process must not block indefinitely, race the awaitable against a
28/// [`timeout`](crate::EnvHandle::timeout) via [`any_of!`](crate::any_of).
29///
30/// One trigger, many waiters — including one that only starts waiting *after*
31/// the fire (the latch makes it resolve immediately):
32///
33/// ```
34/// use simu::SimEnv;
35///
36/// let mut env = SimEnv::with_seed(0);
37/// let (trigger, ready) = env.event();
38///
39/// // Fires at t = 2.
40/// let h = env.handle();
41/// env.spawn(async move {
42/// h.timeout(2.0).await;
43/// trigger.fire(); // consumes the trigger — an event fires at most once
44/// });
45///
46/// // Suspends now, woken at t = 2.
47/// let r = ready.clone(); // Clone = same underlying event
48/// let h1 = env.handle();
49/// env.spawn(async move {
50/// r.await;
51/// assert_eq!(h1.now(), 2.0);
52/// });
53///
54/// // Starts waiting at t = 3 — after the fire — and resolves immediately.
55/// let h2 = env.handle();
56/// env.spawn(async move {
57/// h2.timeout(3.0).await;
58/// ready.await; // already fired: no suspension
59/// assert_eq!(h2.now(), 3.0);
60/// });
61///
62/// env.run();
63/// ```
64#[derive(Debug)]
65pub struct EventTrigger {
66 state: Rc<RefCell<EventState>>,
67}
68
69/// The receiving half of a manual event.
70///
71/// Implements `Future<Output = ()>`. If the paired [`EventTrigger`] has
72/// already fired, polling returns `Ready` immediately. Otherwise the calling
73/// process is suspended and woken when [`EventTrigger::fire`] is called.
74///
75/// `EventAwaitable` is `Clone`: every clone shares the same underlying event,
76/// so multiple processes can await the same trigger.
77#[derive(Clone, Debug)]
78pub struct EventAwaitable {
79 state: Rc<RefCell<EventState>>,
80}
81
82/// Create a paired `(EventTrigger, EventAwaitable)`.
83pub(crate) fn new_event() -> (EventTrigger, EventAwaitable) {
84 let state = Rc::new(RefCell::new(EventState {
85 fired: false,
86 waiters: Vec::new(),
87 }));
88 (
89 EventTrigger { state: Rc::clone(&state) },
90 EventAwaitable { state },
91 )
92}
93
94impl EventTrigger {
95 /// Fire the event.
96 ///
97 /// All processes currently suspended on the paired `EventAwaitable` are
98 /// woken immediately. Any process that awaits the event *after* this call
99 /// will also resolve without suspending.
100 pub fn fire(self) {
101 let mut state = self.state.borrow_mut();
102 state.fired = true;
103 for waker in state.waiters.drain(..) {
104 waker.wake();
105 }
106 }
107}
108
109impl Future for EventAwaitable {
110 type Output = ();
111
112 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
113 let mut state = self.state.borrow_mut();
114 if state.fired {
115 return Poll::Ready(());
116 }
117 // Register this waker only if not already present (avoid duplicates on
118 // repeated polls from the same task).
119 let waker = cx.waker();
120 if !state.waiters.iter().any(|w| w.will_wake(waker)) {
121 state.waiters.push(waker.clone());
122 }
123 Poll::Pending
124 }
125}