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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
//! 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 Cell;
use Future;
use Pin;
use Rc;
use ;
use cratespawn_local;
use NodeId;
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).
/// Reactive 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 — then
/// **re-runs the fetcher whenever any signal it read changes**.
///
/// Reactivity: the fetcher is wrapped in a reactive [`effect`] and the
/// spawned future re-installs that effect node as the current observer
/// on every `poll`. As a result, signals read **anywhere** in the
/// fetcher are tracked as dependencies of the resource — both in the
/// synchronous prefix (before the first `.await`) and after any
/// `.await` point. When any tracked signal changes, the fetcher runs
/// again from scratch and the resource updates.
///
/// While a (re)fetch is in flight the resource returns to
/// [`ResourceState::Loading`]. Only the latest run's result is
/// committed: a monotonically-increasing generation counter guards the
/// write, so if a newer run starts before an older in-flight fetch
/// resolves, the stale result is discarded rather than clobbering the
/// fresh state. In-flight stale fetches are abandoned *cooperatively*
/// (the superseded future stops at its next `poll` boundary) — there is
/// no hard cancellation, and any worker thread spawned via
/// [`crate::tasks::run_blocking`] runs to completion with its result
/// dropped.
///
/// Dynamic-dependency caveat: dependencies are rebuilt on every run, so
/// a signal that is only read on *some* code path is only a dependency
/// on the runs where that path actually executes. A signal read after
/// an `.await` is only tracked once the future advances past that
/// suspension point.
///
/// 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`]; the first fetch is spawned during the
/// effect's synchronous initial run.
///
/// Owner discipline: the underlying [`RwSignal`] and the driving effect
/// are registered with whatever owner is current at call time. If that
/// owner is disposed, the effect stops re-running and any eventual
/// write is a no-op (the signal node is gone), so no stale write hits a
/// re-mounted owner.
///
/// For tests / already-in-memory values, prefer [`resource_sync`] — it
/// runs the fetcher inline once, untracked, and doesn't depend on the
/// executor having been ticked.
/// A spawned fetch future that re-installs its resource's effect node
/// as the current reactive observer on every `poll`, so signal reads
/// after `.await` points are tracked as dependencies of the resource.
/// A generation stamp lets a superseded run abandon itself
/// cooperatively without clobbering a fresher result.
/// 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`.