Skip to main content

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}