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