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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! Async-data primitive — runs an `async` fetcher on Whisker's
//! single-threaded task pool ([`crate::tasks`]) and exposes the
//! loading / ready / error state through a [`ReadSignal`]-shaped
//! handle.
//!
//! The fetcher runs on the TASM thread under
//! [`futures_executor::LocalPool`]. For blocking sync IO (`ureq`,
//! `std::fs`, …) inside the fetcher, wrap the call in
//! [`crate::tasks::run_blocking`] which offloads to a fresh worker
//! thread and marshals the result back via [`run_on_main_thread`]:
//!
//! ```ignore
//! use whisker::runtime::tasks::run_blocking;
//!
//! let stories = resource(|| async {
//! run_blocking(|| {
//! ureq::get("https://hn.algolia.com/...")
//! .call()
//! .map_err(|e| e.to_string())?
//! .into_string()
//! .map_err(|e| e.to_string())
//! })
//! .await
//! .and_then(|body| parse(&body))
//! });
//! ```
//!
//! For purely-async fetchers (a non-blocking HTTP client, a
//! pre-computed value, etc.) you can just write `async move { ... }`
//! and skip the `run_blocking` step.
use Future;
use cratespawn_local;
use RwSignal;
/// Three-state machine the [`Resource`] cycles through. `Clone` so
/// reads inside effects can take owned copies without borrowing the
/// underlying signal slot.
/// Copy handle to a deferred value. Wraps an [`RwSignal`] whose slot
/// the worker thread writes into once the fetch completes; consumer
/// code reads through the accessors below.
// Hand-written Copy/Clone — `derive(Copy)` would require `T: Copy`
// which is unnecessarily strict (the resource only holds a u32-ish
// signal handle, not the T itself).
/// Fire-and-forget async fetch. Drives `fetcher` (an `async fn` or
/// `async move {…}` block) on Whisker's task pool and writes the
/// resolved [`Result`] into the returned [`Resource`]'s signal.
///
/// `fetcher` is called once on the TASM thread to obtain the
/// `Future`, which is then spawned onto [`crate::tasks::spawn_local`]
/// and polled by every tick. The future runs cooperatively — `await`
/// points yield back to the runtime so the UI stays responsive.
///
/// For blocking sync work inside the fetcher (e.g. `ureq::get(...)`,
/// `std::fs::read(...)`), wrap the call in
/// [`crate::tasks::run_blocking`] which moves it to a worker thread
/// and resumes the awaiting task on the main thread once the result
/// is back.
///
/// Returns immediately with a `Resource<T>` in
/// [`ResourceState::Loading`].
///
/// Owner discipline: the underlying [`RwSignal`] is registered with
/// whatever owner is current at call time. If that owner is disposed
/// before the future completes, the eventual write is a no-op (the
/// signal node is gone), so no stale write hits a re-mounted owner.
///
/// For tests, prefer [`resource_sync`] — it runs the fetcher inline
/// and doesn't depend on the executor having been ticked.
/// Synchronous-fetch variant. Runs `fetcher` inline on the calling
/// thread and writes the result directly into the resource's signal.
/// No worker thread, no main-thread dispatcher needed — useful for
/// tests, for cases where the value is already in memory, and for
/// computed pseudo-resources (e.g. derive from a context value).
///
/// The returned `Resource` is in [`ResourceState::Ready`] or
/// [`ResourceState::Error`] *immediately* — never `Loading`.