Skip to main content

tessera_shard/state/
handle.rs

1use std::hash::{Hash, Hasher};
2
3use tessera_ui::State;
4
5/// Failure while accessing state after its owning UI lifetime ends.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum ShardStateError {
8    /// The controller or its UI session has been released.
9    ControllerDropped,
10    /// The route that owned this value has left the stack.
11    Stale,
12}
13
14/// Reactive state owned by a route or router controller.
15///
16/// Reads subscribe the current UI phase to changes. Handles remain valid while
17/// their owner lives and must be accessed on the owning UI session.
18pub struct ShardState<T> {
19    value: State<T>,
20    controller: State<()>,
21}
22
23impl<T> Copy for ShardState<T> {}
24impl<T> Clone for ShardState<T> {
25    fn clone(&self) -> Self {
26        *self
27    }
28}
29impl<T> PartialEq for ShardState<T> {
30    fn eq(&self, other: &Self) -> bool {
31        self.value == other.value && self.controller == other.controller
32    }
33}
34impl<T> Eq for ShardState<T> {}
35impl<T> Hash for ShardState<T> {
36    fn hash<H: Hasher>(&self, state: &mut H) {
37        self.value.hash(state);
38        self.controller.hash(state);
39    }
40}
41
42impl<T: Send + Sync + 'static> ShardState<T> {
43    pub(crate) fn new(value: State<T>, controller: State<()>) -> Self {
44        Self { value, controller }
45    }
46
47    fn check(&self) -> Result<(), ShardStateError> {
48        if !self.controller.is_alive() {
49            Err(ShardStateError::ControllerDropped)
50        } else if !self.value.is_alive() {
51            Err(ShardStateError::Stale)
52        } else {
53            Ok(())
54        }
55    }
56
57    /// Reads the value and subscribes the active UI phase to its changes.
58    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
59        self.try_with(f)
60            .unwrap_or_else(|error| panic!("ShardState access failed: {error:?}"))
61    }
62
63    /// Updates the value and invalidates its UI consumers.
64    pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
65        self.try_with_mut(f)
66            .unwrap_or_else(|error| panic!("ShardState access failed: {error:?}"))
67    }
68
69    /// Reads live state, reporting an expired controller or route.
70    pub fn try_with<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R, ShardStateError> {
71        self.check()?;
72        Ok(self.value.with(f))
73    }
74
75    /// Updates live state, reporting an expired controller or route.
76    pub fn try_with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R, ShardStateError> {
77        self.check()?;
78        Ok(self.value.with_mut(f))
79    }
80
81    /// Returns a cloned value.
82    pub fn get(&self) -> T
83    where
84        T: Clone,
85    {
86        self.with(Clone::clone)
87    }
88
89    /// Replaces the value and invalidates its UI consumers.
90    pub fn set(&self, value: T) {
91        self.with_mut(|current| *current = value);
92    }
93}