Skip to main content

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}