bombs/blocking/
multi.rs

1use super::{ Fuse, Bomb, Flame, spin, SPIN_ITERATIONS };
2
3/// A type alias for [`MultiFuse<T>`].
4pub type MultiFuze<T> = MultiFuse<T>;
5
6/// A reusable bomb.
7/// *How did they manufacture this?*
8///
9/// The [`MultiFuse<T>`] for this `Bomb` can be lit multiple times,
10/// to transfer multiple data values sequentially.
11/// The `Bomb` remains in an exploded state for one call per
12/// data value sent. This means you can only retrieve sent data once,
13/// as then the `Bomb` will wait for the next data value. In contrast,
14/// a one-time use [`Bomb<T>`] instead repeatedly returns the same one
15/// data value for subsequent calls to [`exploded()`] after the [`Fuse`]
16/// has been lit.
17///
18/// Like one-time use [`Bomb`]s, `MultiBomb`s can be safely cloned and sent between threads.
19///
20/// [`exploded()`]: ./struct.Bomb.html#method.exploded
21#[derive(Clone)]
22pub struct MultiBomb<T: Clone> {
23    inner: Bomb<(MultiBomb<T>, T)>
24}
25
26/// A reusable fuse.
27/// *It just grows back.*
28///
29/// This `MultiFuse` can be lit multiple times, and will explode all
30/// [`MultiBomb<T>`] instances associated with it.
31///
32/// It is not necessary, but may be useful for your application, to wait for
33/// all of the [`MultiBomb`]s to extinguish before lighting the `MultiFuse` again.
34/// Slow [`MultiBomb`]s may be in an exploded state again immediately after
35/// they have finished processing data, if the `MultiFuse` was lit again in between processing.
36///
37/// Like one-time use `Fuse`s, `MultiFuse`s cannot be cloned, but may be moved between threads.
38///
39/// # Dropping
40///
41/// Currently, [`MultiBomb`]s do not have internal logic to detect `MultiFuse` drops.
42/// If the `MultiFuse` is dropped without any additional logic, all active [`MultiBomb`]s will wait
43/// indefinitely to explode.
44pub struct MultiFuse<T: Clone> {
45    // TODO: Implement proper drop behaviour, read above for further info.
46    inner: Fuse<(MultiBomb<T>, T)>
47}
48
49impl<T: Clone> MultiBomb<T> {
50    /// Creates a new single but reusable producer [`MultiFuse<T>`], and a multi-consumer `MultiBomb<T>`.
51    ///
52    /// Instances of `MultiBomb<T>` may be safely cloned and sent between threads.
53    pub fn new() -> (MultiFuse<T>, MultiBomb<T>) {
54        let fuse = MultiFuse::new();
55
56        let bomb = Self {
57            inner: Bomb {
58                data: fuse.inner.data.clone(),
59            }
60        };
61
62        (fuse, bomb)
63    }
64
65    /// Returns `Some` if the `MultiBomb` has exploded.
66    ///
67    /// Once the `MultiBomb` has exploded, this function
68    /// will return the value only once, and subsequent
69    /// calls will check for the next explosion/data value sent.
70    ///
71    /// # Dropping
72    ///
73    /// If the `MultiFuse` is dropped, this function will
74    /// **always** return `None`, which indistinguishable from
75    /// waiting for data. You must implement your own drop
76    /// behaviour to avoid this.
77    ///
78    /// See [`MultiFuse`] for more details.
79    pub fn exploded(&mut self) -> Option<T> {
80        let (new_bomb, value) = match self.inner.exploded() {
81            None => return None,
82
83            // Clone MultiBomb to increment strong counter.
84            // Clone data value to properly construct/deconstruct
85            // on this thread.
86            Some(data) => data.clone()
87        };
88
89        self.inner = new_bomb.inner;
90
91        Some(value)
92    }
93
94    /// Blocks the current thread and waits to receive data from explosion.
95    ///
96    /// If the `Bomb` has already exploded, this method returns immediately, and
97    /// does not block.
98    ///
99    /// # Dropping
100    ///
101    /// If the `MultiFuse` is dropped, this function will
102    /// block **indefinitely**, and is indistinguishable from
103    /// waiting for data. You must implement your own drop
104    /// behaviour to avoid this.
105    ///
106    /// See [`MultiFuse`] for more details.
107    pub fn wait_for_explosion(&mut self) -> T {
108        // Spin a couple times to wait and check for explosion.
109        // Return the value if it has exploded while spinning.
110        if let Some(value) = spin(SPIN_ITERATIONS, || self.exploded()) {
111            return value;
112        }
113
114        let (new_bomb, value) = self.inner.wait_for_explosion().clone();
115
116        self.inner = new_bomb.inner;
117
118        value
119    }
120}
121
122impl<T: Clone> MultiFuse<T> {
123    fn new() -> Self {
124        Self {
125            inner: Fuse::new(),
126        }
127    }
128
129    /// Ignites the fuse.
130    ///
131    /// Explodes all [`MultiBomb`]s associated to this `MultiFuse`.
132    /// Each [`MultiBomb`] receives `value`.
133    ///
134    /// Alias to [`light`](#method.light)
135    pub fn ignite(&mut self, value: T) -> Flame {
136        self.light(value)
137    }
138
139    /// Lights the fuse.
140    ///
141    /// Explodes all [`MultiBomb`]s associated to this `MultiFuse`.
142    /// Each [`MultiBomb`] receives `value`.
143    pub fn light(&mut self, value: T) -> Flame {
144        // Create new fuse and bomb.
145        let (fuse, bomb) = MultiBomb::new();
146
147        // Replace old fuse with new.
148        let old_fuse = std::mem::replace(&mut self.inner, fuse.inner);
149
150        // Light old fuse, passing a new bomb with it.
151        old_fuse.light((bomb, value))
152    }
153}