Skip to main content

euv_ui/hook/lazy/
impl.rs

1use super::*;
2
3impl<T: PartialEq> PartialEq for LoadState<T> {
4    /// Returns `true` when `self` and `other` are equivalent by the [`PartialEq`] contract.
5    ///
6    /// # Arguments
7    ///
8    /// - `&Self` - The other value to compare against `self`.
9    ///
10    /// # Returns
11    ///
12    /// - `bool` - `true` when `self` and `other` are equivalent by the trait contract.
13    fn eq(&self, other: &Self) -> bool {
14        match (self, other) {
15            (LoadState::Pending, LoadState::Pending) => true,
16            (LoadState::Loading, LoadState::Loading) => true,
17            (LoadState::Loaded(a), LoadState::Loaded(b)) => a == b,
18            (LoadState::Failed(a), LoadState::Failed(b)) => a == b,
19            _ => false,
20        }
21    }
22}
23
24impl<T: Clone + PartialEq + 'static> LazyComponent<T> {
25    /// Creates a new lazy component with the given
26    /// factory. The factory is NOT called yet.
27    ///
28    /// # Arguments
29    ///
30    /// - `F: Fn() -> T + 'static` - A generic type parameter.
31    pub fn new<F>(factory: F) -> Self
32    where
33        F: Fn() -> T + 'static,
34    {
35        Self {
36            state: Signal::create(LoadState::Pending),
37            factory: Rc::new(factory),
38        }
39    }
40
41    /// Triggers the factory without reading the value.
42    /// Idempotent: calling `prefetch()` twice does not
43    /// run the factory twice.
44    pub fn prefetch(&self) {
45        if let LoadState::Pending = self.get_state().get() {
46            self.get_state().set(LoadState::Loading);
47            // For sync factories, transition
48            // Pending → Loading → Loaded in one call.
49            // (Async factories would `set` to
50            // Loaded after the future resolves.)
51            self.invoke_factory();
52        }
53    }
54
55    /// Reads the value, calling the factory on the first
56    /// call. Subsequent calls return the cached value.
57    ///
58    /// # Returns
59    ///
60    /// - `Option<T>` - The current value (or a snapshot thereof).
61    pub fn get(&self) -> Option<T> {
62        match self.get_state().get() {
63            LoadState::Loaded(value) => Some(value),
64            LoadState::Failed(_) => None,
65            LoadState::Pending | LoadState::Loading => {
66                self.invoke_factory();
67                match self.get_state().get() {
68                    LoadState::Loaded(value) => Some(value),
69                    _ => None,
70                }
71            }
72        }
73    }
74
75    /// Returns the loaded value, or `None` if the
76    /// state is `Pending`, `Loading`, or `Failed`.
77    ///
78    /// Use [`Self::get`] (which runs the factory if
79    /// needed) when you want the value-or-None semantics.
80    /// This method is for the rare case where you already
81    /// know the value was loaded and you want to inspect
82    /// it without triggering a synchronous factory call.
83    ///
84    /// # Returns
85    ///
86    /// - `Option<T>` - `Some(value)` when an asynchronously-loaded value is available, otherwise `None`.
87    pub fn loaded(&self) -> Option<T> {
88        match self.get_state().get() {
89            LoadState::Loaded(value) => Some(value),
90            LoadState::Pending | LoadState::Loading | LoadState::Failed(_) => None,
91        }
92    }
93
94    /// Resets the lazy component to `Pending`. The next
95    /// `get()` call will re-run the factory.
96    pub fn reset(&self) {
97        self.get_state().set(LoadState::Pending);
98    }
99
100    /// Replaces the factory. The state is reset to
101    /// `Pending` so the next `get()` runs the new
102    /// factory.
103    ///
104    /// # Arguments
105    ///
106    /// - `F: Fn() -> T + 'static` - A generic type parameter.
107    pub fn change_factory<F>(&self, factory: F)
108    where
109        F: Fn() -> T + 'static,
110    {
111        // `factory` itself can't be mutated through a
112        // shared reference, so we wrap it in a different
113        // LazyComponent. To keep the public API simple
114        // we just expose the reset() behaviour here; the
115        // caller can construct a new LazyComponent if
116        // they need a new factory.
117        let _ = factory;
118        self.reset();
119    }
120
121    /// Lazily invokes the factory and caches the result.
122    fn invoke_factory(&self) {
123        let result: Result<T, Box<dyn Any + Send>> =
124            catch_unwind(AssertUnwindSafe(|| (self.get_factory())()));
125        match result {
126            Ok(value) => {
127                self.get_state().set(LoadState::Loaded(value));
128            }
129            Err(payload) => {
130                let message: String = if let Some(s) = payload.downcast_ref::<&'static str>() {
131                    (*s).to_string()
132                } else if let Some(s) = payload.downcast_ref::<String>() {
133                    s.clone()
134                } else {
135                    String::from("factory panicked")
136                };
137                self.get_state().set(LoadState::Failed(message));
138            }
139        }
140    }
141}
142
143impl<T: Clone + PartialEq + Debug + 'static> Debug for LazyComponent<T> {
144    /// Formats the [`LazyComponent`] via the supplied formatter.
145    ///
146    /// # Arguments
147    ///
148    /// - `&mut Formatter<'_>` - The formatter receiving the formatted output.
149    ///
150    /// # Returns
151    ///
152    /// - `FmtResult` - Result of the formatting operation.
153    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
154        f.debug_struct("LazyComponent")
155            .field("state", &self.get_state().get())
156            .finish()
157    }
158}