relm4/shared_state/
state.rs

1use std::{
2    ops::{Deref, DerefMut},
3    sync::{RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError},
4};
5
6use once_cell::sync::Lazy;
7
8use crate::Sender;
9
10use super::SubscriberFn;
11
12/// A type that allows you to share information across your
13/// application easily.
14/// Get immutable and mutable access to the data and subscribe to changes.
15///
16/// # Panics
17///
18/// [`SharedState`] uses a [`RwLock`] internally.
19/// If you use [`Self::read()`] and [`Self::write()`] in the same scope
20/// your code might be stuck in a deadlock or panic.
21pub struct SharedState<Data> {
22    data: Lazy<RwLock<Data>>,
23    subscribers: Lazy<RwLock<Vec<SubscriberFn<Data>>>>,
24}
25
26impl<Data: std::fmt::Debug> std::fmt::Debug for SharedState<Data> {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        f.debug_struct("SharedState")
29            .field("data", &self.data)
30            .field("subscribers", &self.subscribers.try_read().map(|s| s.len()))
31            .finish()
32    }
33}
34
35impl<Data> Default for SharedState<Data>
36where
37    Data: Default,
38{
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl<Data> SharedState<Data>
45where
46    Data: Default,
47{
48    /// Create a new [`SharedState`] variable.
49    ///
50    /// The data will be initialized lazily on the first access.
51    ///
52    /// # Example
53    ///
54    /// ```
55    /// # #[derive(Default)]
56    /// # struct MyData;
57    /// use relm4::SharedState;
58    /// static STATE: SharedState<MyData> = SharedState::new();
59    /// ```
60    #[must_use]
61    pub const fn new() -> Self {
62        Self {
63            data: Lazy::new(RwLock::default),
64            subscribers: Lazy::new(RwLock::default),
65        }
66    }
67
68    /// Subscribe to a shared state type.
69    /// Any subscriber will be notified with a message every time
70    /// you modify the shared state using [`Self::get_mut()`].
71    ///
72    /// ```
73    /// use relm4::SharedState;
74    /// static STATE: SharedState<u8> = SharedState::new();
75    ///
76    /// let (sender, receiver) = relm4::channel();
77    ///
78    /// // Every time we modify the data, we will receive
79    /// // the updated value as a message.
80    /// STATE.subscribe(&sender, |data| *data);
81    ///
82    /// {
83    ///     let mut data = STATE.write();
84    ///     *data += 1;
85    /// }
86    ///
87    /// assert_eq!(receiver.recv_sync().unwrap(), 1);
88    /// ```
89    pub fn subscribe<Msg, F>(&self, sender: &Sender<Msg>, f: F)
90    where
91        F: Fn(&Data) -> Msg + 'static + Send + Sync,
92        Msg: Send + 'static,
93    {
94        let sender = sender.clone();
95        self.subscribers
96            .write()
97            .unwrap()
98            .push(Box::new(move |data: &Data| {
99                let msg = f(data);
100                sender.send(msg).is_ok()
101            }));
102    }
103
104    /// An alternative version of [`subscribe()`](Self::subscribe()) that only send a message if
105    /// the closure returns [`Some`].
106    pub fn subscribe_optional<Msg, F>(&self, sender: &Sender<Msg>, f: F)
107    where
108        F: Fn(&Data) -> Option<Msg> + 'static + Send + Sync,
109        Msg: Send + 'static,
110    {
111        let sender = sender.clone();
112        self.subscribers
113            .write()
114            .unwrap()
115            .push(Box::new(move |data: &Data| {
116                if let Some(msg) = f(data) {
117                    sender.send(msg).is_ok()
118                } else {
119                    true
120                }
121            }));
122    }
123
124    /// Get immutable access to the shared data.
125    ///
126    /// Returns a RAII guard which will release this thread’s shared access
127    /// once it is dropped.
128    ///
129    /// The calling thread will be blocked until there are no more writers
130    /// which hold the lock (see [`RwLock`]).
131    ///
132    /// # Panics
133    ///
134    /// This function will panic if the internal [`RwLock`] is poisoned.
135    /// A [`RwLock`] is poisoned whenever a writer panics while holding an exclusive lock.
136    /// The failure will occur immediately after the lock has been acquired.
137    ///
138    /// Also, this function might panic when called if the lock is already
139    /// held by the current thread.
140    pub fn read(&self) -> SharedStateReadGuard<'_, Data> {
141        SharedStateReadGuard {
142            inner: self.data.read().unwrap(),
143        }
144    }
145
146    /// Get immutable access to the shared data.
147    ///
148    /// Similar to [`read`](Self::read), but doesn't block so this function simply
149    /// returns an [`Err`] if the data is already locked (or poisoned).
150    pub fn try_read(
151        &self,
152    ) -> Result<SharedStateReadGuard<'_, Data>, TryLockError<RwLockReadGuard<'_, Data>>> {
153        Ok(SharedStateReadGuard {
154            inner: self.data.try_read()?,
155        })
156    }
157
158    /// Get mutable access to the shared data.
159    ///
160    /// Returns a RAII guard which will **notify all subscribers** and
161    /// release this thread’s shared access once it is dropped.
162    ///
163    /// This function will not return while other writers or other readers
164    /// currently have access to the internal lock (see [`RwLock`]).
165    ///
166    /// # Panics
167    ///
168    /// This function will panic if the internal [`RwLock`] is poisoned.
169    /// A [`RwLock`] is poisoned whenever a writer panics while holding an exclusive lock.
170    /// The failure will occur immediately after the lock has been acquired.
171    ///
172    /// Also, this function might panic when called if the lock is already
173    /// held by the current thread.
174    ///
175    /// # Example
176    ///
177    /// ```
178    /// # use relm4::SharedState;
179    /// static STATE: SharedState<u8> = SharedState::new();
180    ///
181    /// // Overwrite the current value with 1
182    /// *STATE.write() = 1;
183    /// ```
184    ///
185    /// # Panic example
186    ///
187    /// ```no_run
188    /// # use relm4::SharedState;
189    /// static STATE: SharedState<u8> = SharedState::new();
190    ///
191    /// let read_guard = STATE.read();
192    ///
193    /// // This is fine
194    /// let another_read_guard = STATE.read();
195    ///
196    /// // This might panic or result in a dead lock
197    /// // because you cannot read and write at the same time.
198    /// // To solve this, drop all read guards on this thread first.
199    /// let another_write_guard = STATE.write();
200    /// ```
201    pub fn write(&self) -> SharedStateWriteGuard<'_, Data> {
202        let subscribers = self.subscribers.write().unwrap();
203        let data = self.data.write().unwrap();
204
205        SharedStateWriteGuard { data, subscribers }
206    }
207
208    /// Get mutable access to the shared data.
209    ///
210    /// Similar to [`write`](Self::write), but doesn't block so this function simply
211    /// returns an [`Err`] if the data is already locked (or poisoned).
212    pub fn try_write(
213        &self,
214    ) -> Result<SharedStateWriteGuard<'_, Data>, TryLockError<RwLockWriteGuard<'_, Data>>> {
215        let data = self.data.try_write()?;
216        let subscribers = self.subscribers.write().unwrap();
217
218        Ok(SharedStateWriteGuard { data, subscribers })
219    }
220
221    /// Get mutable access to the shared data.
222    /// Since this call borrows the [`SharedState`] mutably,
223    /// no actual locking needs to take place, but the mutable
224    /// borrow statically guarantees no locks exist.
225    ///
226    /// **This method will not notify any subscribers!**
227    ///
228    /// # Panics
229    ///
230    /// This function will panic if the internal [`RwLock`] is poisoned.
231    /// A [`RwLock`] is poisoned whenever a writer panics while holding an exclusive lock.
232    /// The failure will occur immediately after the lock has been acquired.
233    pub fn get_mut(&mut self) -> &mut Data {
234        self.data.get_mut().unwrap()
235    }
236
237    /// Get immutable access to the shared data.
238    ///
239    /// **This method will not notify any subscribers!**
240    ///
241    /// # Panics
242    ///
243    /// This function will panic if the internal [`RwLock`] is poisoned.
244    /// A [`RwLock`] is poisoned whenever a writer panics while holding an exclusive lock.
245    /// The failure will occur immediately after the lock has been acquired.
246    ///
247    /// Also, this function might panic when called if the lock is already
248    /// held by the current thread.
249    pub fn read_inner(&self) -> RwLockReadGuard<'_, Data> {
250        self.data.read().unwrap()
251    }
252
253    /// Get mutable access to the shared data.
254    ///
255    /// **This method will not notify any subscribers!**
256    ///
257    /// # Panics
258    ///
259    /// This function will panic if the internal [`RwLock`] is poisoned.
260    /// A [`RwLock`] is poisoned whenever a writer panics while holding an exclusive lock.
261    /// The failure will occur immediately after the lock has been acquired.
262    ///
263    /// Also, this function might panic when called if the lock is already
264    /// held by the current thread.
265    pub fn write_inner(&self) -> RwLockWriteGuard<'_, Data> {
266        self.data.write().unwrap()
267    }
268}
269
270#[derive(Debug)]
271/// A guard that immutably dereferences `Data`.
272pub struct SharedStateReadGuard<'a, Data> {
273    inner: RwLockReadGuard<'a, Data>,
274}
275
276impl<Data> Deref for SharedStateReadGuard<'_, Data> {
277    type Target = Data;
278
279    fn deref(&self) -> &Self::Target {
280        &self.inner
281    }
282}
283
284/// A guard that mutably dereferences `Data`.
285/// Once dropped all subscribers of the [`SharedState`] will be notified.
286pub struct SharedStateWriteGuard<'a, Data> {
287    data: RwLockWriteGuard<'a, Data>,
288    subscribers: RwLockWriteGuard<'a, Vec<SubscriberFn<Data>>>,
289}
290
291impl<Data: std::fmt::Debug> std::fmt::Debug for SharedStateWriteGuard<'_, Data> {
292    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293        f.debug_struct("SharedStateWriteGuard")
294            .field("data", &self.data)
295            .field("subscribers", &self.subscribers.len())
296            .finish()
297    }
298}
299
300impl<Data> Deref for SharedStateWriteGuard<'_, Data> {
301    type Target = Data;
302
303    fn deref(&self) -> &Self::Target {
304        &self.data
305    }
306}
307
308impl<Data> DerefMut for SharedStateWriteGuard<'_, Data> {
309    fn deref_mut(&mut self) -> &mut Self::Target {
310        &mut self.data
311    }
312}
313
314impl<Data> Drop for SharedStateWriteGuard<'_, Data> {
315    // Notify subscribers
316    fn drop(&mut self) {
317        let data = &*self.data;
318        // Remove all elements which had their senders dropped.
319        self.subscribers.retain(|subscriber| subscriber(data));
320    }
321}
322
323#[cfg(test)]
324mod test {
325    use super::SharedState;
326
327    static STATE: SharedState<u8> = SharedState::new();
328
329    #[test]
330    fn shared_state() {
331        assert_eq!(*STATE.read(), 0);
332
333        {
334            let mut data = STATE.write();
335            *data += 1;
336        }
337
338        assert_eq!(*STATE.read(), 1);
339
340        let (sender, receiver) = crate::channel();
341
342        STATE.subscribe(&sender, |data| *data);
343
344        {
345            let mut data = STATE.write();
346            *data += 1;
347        }
348
349        assert_eq!(receiver.recv_sync().unwrap(), 2);
350        assert_eq!(*STATE.read(), 2);
351    }
352}