simu/combinator.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::future::Future;
6use std::pin::Pin;
7use std::task::{Context, Poll};
8
9// ---------------------------------------------------------------------------
10// AnyOf — resolves when the first sub-future resolves
11// ---------------------------------------------------------------------------
12
13/// A future that resolves when **any one** of its sub-futures resolves.
14///
15/// All sub-futures must have `Output = ()`, which is the common output type
16/// of all simu event primitives (`Timeout`, `EventAwaitable`, etc.).
17///
18/// When a sub-future resolves, the remaining ones are dropped. Any wakers they
19/// registered may still fire later; the executor handles such spurious wakeups
20/// gracefully.
21///
22/// Prefer the [`any_of!`](crate::any_of) macro over constructing this directly.
23pub struct AnyOf {
24 futures: Vec<Pin<Box<dyn Future<Output = ()>>>>,
25}
26
27impl std::fmt::Debug for AnyOf {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_struct("AnyOf")
30 .field("pending", &self.futures.len())
31 .finish()
32 }
33}
34
35impl AnyOf {
36 /// Create an `AnyOf` combinator from a list of futures.
37 ///
38 /// # Panics
39 ///
40 /// Panics if `futures` is empty — waiting for "any of nothing" is a logic
41 /// error.
42 #[must_use = "futures do nothing unless awaited"]
43 pub fn new(futures: Vec<Pin<Box<dyn Future<Output = ()>>>>) -> Self {
44 assert!(!futures.is_empty(), "AnyOf requires at least one future");
45 AnyOf { futures }
46 }
47}
48
49impl Future for AnyOf {
50 type Output = ();
51
52 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
53 for fut in self.get_mut().futures.iter_mut() {
54 if fut.as_mut().poll(cx).is_ready() {
55 return Poll::Ready(());
56 }
57 }
58 Poll::Pending
59 }
60}
61
62// ---------------------------------------------------------------------------
63// AllOf — resolves when all sub-futures have resolved
64// ---------------------------------------------------------------------------
65
66/// A future that resolves when **all** of its sub-futures have resolved.
67///
68/// All sub-futures must have `Output = ()`. Completed sub-futures are dropped
69/// eagerly so they are not polled again after returning `Ready`.
70///
71/// Resolves immediately if constructed with an empty list (vacuously true).
72///
73/// Prefer the [`all_of!`](crate::all_of) macro over constructing this directly.
74pub struct AllOf {
75 futures: Vec<Pin<Box<dyn Future<Output = ()>>>>,
76}
77
78impl std::fmt::Debug for AllOf {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 f.debug_struct("AllOf")
81 .field("pending", &self.futures.len())
82 .finish()
83 }
84}
85
86impl AllOf {
87 /// Create an `AllOf` combinator from a list of futures.
88 ///
89 /// Resolves immediately if `futures` is empty.
90 #[must_use = "futures do nothing unless awaited"]
91 pub fn new(futures: Vec<Pin<Box<dyn Future<Output = ()>>>>) -> Self {
92 AllOf { futures }
93 }
94}
95
96impl Future for AllOf {
97 type Output = ();
98
99 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
100 // Retain only futures that are still pending; completed ones are dropped.
101 // Both AnyOf and AllOf are Unpin (Vec and Pin<Box<...>> are Unpin),
102 // so get_mut() is safe here.
103 let this = self.as_mut().get_mut();
104 this.futures
105 .retain_mut(|fut| fut.as_mut().poll(cx).is_pending());
106
107 if this.futures.is_empty() {
108 Poll::Ready(())
109 } else {
110 Poll::Pending
111 }
112 }
113}
114
115// ---------------------------------------------------------------------------
116// Convenience macros
117// ---------------------------------------------------------------------------
118
119/// Wait for the **first** of several futures to resolve.
120///
121/// Each expression is automatically pinned in a `Box`. All futures must have
122/// `Output = ()`.
123///
124/// # Example
125///
126/// ```
127/// use simu::{SimEnv, any_of};
128/// let mut env = SimEnv::with_seed(0);
129/// let h = env.handle();
130/// let (trigger, signal) = env.event();
131/// env.spawn(async move { h.timeout(3.0).await; trigger.fire(); });
132/// let h2 = env.handle();
133/// env.spawn(async move {
134/// // resolves at t=3 (the event) rather than t=10 (the timeout)
135/// any_of![h2.timeout(10.0), signal.clone()].await;
136/// });
137/// env.run();
138/// ```
139#[macro_export]
140macro_rules! any_of {
141 ($($fut:expr),+ $(,)?) => {
142 $crate::AnyOf::new(
143 vec![$(::std::boxed::Box::pin($fut)),+]
144 )
145 };
146}
147
148/// Wait for **all** of several futures to resolve.
149///
150/// Each expression is automatically pinned in a `Box`. All futures must have
151/// `Output = ()`.
152///
153/// # Example
154///
155/// ```
156/// use simu::{SimEnv, all_of};
157/// let mut env = SimEnv::with_seed(0);
158/// let h = env.handle();
159/// env.spawn(async move {
160/// // resolves at t=5, when the slowest sub-future completes
161/// all_of![h.timeout(1.0), h.timeout(3.0), h.timeout(5.0)].await;
162/// });
163/// env.run();
164/// ```
165#[macro_export]
166macro_rules! all_of {
167 ($($fut:expr),+ $(,)?) => {
168 $crate::AllOf::new(
169 vec![$(::std::boxed::Box::pin($fut)),+]
170 )
171 };
172}