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