intuituive/task.rs
1//! Background work: run something slow off the app's thread, and write
2//! signals with its result when it is done.
3//!
4//! - [`spawn`] runs a closure on its own thread;
5//! - [`spawn_future`] drives a future on its own thread, for async code
6//! that does not need a particular runtime (a tokio app keeps its own
7//! runtime and sends results back with a [`Proxy`](crate::Proxy));
8//! - [`resource`] is a value that loads in the background, with
9//! [`Load::Loading`] until it arrives, and reloads on demand;
10//! [`resource_for`] loads again whenever a key changes;
11//! - [`latest`] and [`latest_every`] are the newest value a thread of
12//! their own makes.
13//!
14//! The `done` closure runs back on the app's thread, between frames, with a
15//! [`Ctx`]: it can write signals, open a screen, or quit. A cancelled
16//! [`Task`] still finishes its work (a thread cannot be stopped from
17//! outside), but its result is dropped.
18
19use std::cell::Cell;
20use std::collections::HashMap;
21use std::fmt::Display;
22use std::future::Future;
23use std::panic::Location;
24use std::rc::Rc;
25use std::sync::atomic::{AtomicBool, Ordering};
26use std::sync::{Arc, Condvar, Mutex, MutexGuard};
27#[cfg(not(target_arch = "wasm32"))]
28use std::task::Wake;
29use std::task::{Context, Poll, Waker};
30use std::time::Duration;
31
32use crate::app::Ctx;
33use crate::reactive::{Runtime, Signal, signal, watch};
34
35/// A running piece of background work.
36#[derive(Clone, Debug)]
37pub struct Task {
38 cancelled: Arc<AtomicBool>,
39 finished: Arc<AtomicBool>,
40}
41
42impl Task {
43 /// Drop the result when it arrives: `done` will not run.
44 pub fn cancel(&self) {
45 self.cancelled.store(true, Ordering::SeqCst);
46 }
47
48 /// Whether the work has finished (its result may still be on its way
49 /// to the app's thread).
50 pub fn is_finished(&self) -> bool {
51 self.finished.load(Ordering::SeqCst)
52 }
53}
54
55/// Run `work` on its own thread; when it returns, run `done` with the
56/// result on the app's thread. Call it while the app is built or running
57/// (in `App::new`'s closure, a handler, or a node's closure).
58///
59/// ```
60/// use intuituive::prelude::*;
61///
62/// let app = App::new(|| {
63/// let answer = signal(String::from("thinking…"));
64/// spawn(|| 6 * 7, move |n, _| answer.set(n.to_string()));
65/// text!("{answer}").on_key("q", |cx| cx.quit())
66/// })
67/// .wait_for_tasks(true);
68/// let screen = app.render_with(&["q"], 20, 1).unwrap();
69/// assert_eq!(screen[0].trim_end(), "42");
70/// ```
71///
72/// # In a browser
73/// On `wasm32-unknown-unknown` there are no threads: `work` runs at once,
74/// on the app's thread, and its result arrives on the next turn.
75///
76/// # Panics
77/// Outside a running or building app.
78#[track_caller]
79pub fn spawn<T: Send + 'static>(
80 work: impl FnOnce() -> T + Send + 'static,
81 done: impl FnOnce(T, &mut Ctx) + Send + 'static,
82) -> Task {
83 spawn_at(Location::caller(), work, done)
84}
85
86/// [`spawn`], saying in the inspector's trace that it was started at
87/// `site`.
88fn spawn_at<T: Send + 'static>(
89 site: &'static Location<'static>,
90 work: impl FnOnce() -> T + Send + 'static,
91 done: impl FnOnce(T, &mut Ctx) + Send + 'static,
92) -> Task {
93 let runtime = Runtime::current_for("spawn()");
94 let proxy = runtime.proxy();
95 let tasks = runtime.tasks.clone();
96 let task = Task {
97 cancelled: Arc::new(AtomicBool::new(false)),
98 finished: Arc::new(AtomicBool::new(false)),
99 };
100 let handle = task.clone();
101 tasks.fetch_add(1, Ordering::SeqCst);
102 crate::clock::off_thread(move || {
103 // Count the task delivered however the work ends, a panic included.
104 struct Delivered(Arc<std::sync::atomic::AtomicUsize>);
105 impl Drop for Delivered {
106 fn drop(&mut self) {
107 self.0.fetch_sub(1, Ordering::SeqCst);
108 }
109 }
110 let _delivered = Delivered(tasks);
111 let value = work();
112 handle.finished.store(true, Ordering::SeqCst);
113 let cancelled = handle.cancelled;
114 proxy.send(Box::new(move |cx| {
115 let dropped = cancelled.load(Ordering::SeqCst);
116 crate::reactive::traced(|| crate::inspect::Traced::Task { at: site, dropped });
117 if !dropped {
118 done(value, cx);
119 }
120 }));
121 });
122 task
123}
124
125/// [`spawn`] whose `done` need not be [`Send`]: it stays on the app's
126/// thread while `work` runs, so it can hold an `Rc`, a node's handle, or
127/// anything else that cannot cross threads. Only the value travels.
128///
129/// ```
130/// use intuituive::prelude::*;
131/// use intuituive::spawn_then;
132/// use std::rc::Rc;
133///
134/// let app = App::new(|| {
135/// let shown = signal(String::new());
136/// let prefix = Rc::new(String::from("answer: "));
137/// spawn_then(|| 6 * 7, move |n, _| shown.set(format!("{prefix}{n}")));
138/// text!("{shown}")
139/// })
140/// .wait_for_tasks(true);
141/// let mut driver = app.driver(12, 1);
142/// driver.settle();
143/// assert_eq!(driver.screen().plain()[0].trim_end(), "answer: 42");
144/// ```
145///
146/// # Panics
147/// Outside a running or building app.
148#[track_caller]
149pub fn spawn_then<T: Send + 'static>(
150 work: impl FnOnce() -> T + Send + 'static,
151 done: impl FnOnce(T, &mut Ctx) + 'static,
152) -> Task {
153 let runtime = Runtime::current_for("spawn_then()");
154 let id = runtime.next_local.get();
155 runtime.next_local.set(id + 1);
156 let done: Then<T> = Box::new(done);
157 runtime.locals.borrow_mut().insert(id, Box::new(done));
158 spawn_at(Location::caller(), work, move |value, cx| {
159 // The continuation waited on this thread, under its number.
160 let done = Runtime::current().locals.borrow_mut().remove(&id);
161 if let Some(done) = done.and_then(|d| d.downcast::<Then<T>>().ok()) {
162 done(value, cx);
163 }
164 })
165}
166
167/// Run `work` on a thread of its own with a [`Proxy`](crate::Proxy) to send
168/// results back as it goes (lines of a log, entries of a directory walk),
169/// counted as a [task](Task) until it returns: an app that
170/// [waits for its tasks](crate::App::wait_for_tasks) waits for the whole
171/// stream each turn, so a test sees all of it. The [`Task`] says when it
172/// has finished; [`cancel`](Task::cancel) sets a flag `work` can read from
173/// the [`Stream`] it is given, to stop early.
174///
175/// ```
176/// use intuituive::prelude::*;
177/// use intuituive::spawn_stream;
178///
179/// let app = App::new(|| {
180/// let lines = signal(Vec::<String>::new());
181/// spawn_stream(move |stream| {
182/// for n in 1..=3 {
183/// if stream.cancelled() {
184/// break;
185/// }
186/// stream.proxy().run(move || lines.update(|l| l.push(format!("line {n}"))));
187/// }
188/// });
189/// text(move || lines.get().join(","))
190/// })
191/// .wait_for_tasks(true);
192/// let mut driver = app.driver(30, 1);
193/// driver.settle();
194/// assert_eq!(driver.screen().plain()[0].trim_end(), "line 1,line 2,line 3");
195/// ```
196///
197/// # In a browser
198/// On `wasm32-unknown-unknown` there are no threads: `work` runs at once,
199/// on the app's thread, and what it sends arrives on the next turn.
200///
201/// # Panics
202/// Outside a running or building app.
203#[track_caller]
204pub fn spawn_stream(work: impl FnOnce(Stream) + Send + 'static) -> Task {
205 let runtime = Runtime::current_for("spawn_stream()");
206 let stream = Stream {
207 proxy: runtime.proxy(),
208 cancelled: Arc::new(AtomicBool::new(false)),
209 };
210 let task = Task {
211 cancelled: stream.cancelled.clone(),
212 finished: Arc::new(AtomicBool::new(false)),
213 };
214 let tasks = runtime.tasks.clone();
215 let finished = task.finished.clone();
216 tasks.fetch_add(1, Ordering::SeqCst);
217 crate::clock::off_thread(move || {
218 // Done however the work ends, a panic included. The count goes
219 // down after the last result is queued, so a waiting app takes
220 // them all.
221 struct Done(Arc<std::sync::atomic::AtomicUsize>, Arc<AtomicBool>);
222 impl Drop for Done {
223 fn drop(&mut self) {
224 self.1.store(true, Ordering::SeqCst);
225 self.0.fetch_sub(1, Ordering::SeqCst);
226 }
227 }
228 let _done = Done(tasks, finished);
229 work(stream);
230 });
231 task
232}
233
234/// What a [`spawn_stream`] thread sends its results with.
235#[derive(Clone)]
236pub struct Stream {
237 proxy: crate::reactive::Proxy,
238 cancelled: Arc<AtomicBool>,
239}
240
241impl Stream {
242 /// The proxy to the app: [`run`](crate::Proxy::run) a closure on the
243 /// app's thread with each result.
244 pub fn proxy(&self) -> &crate::reactive::Proxy {
245 &self.proxy
246 }
247
248 /// Whether the stream's [`Task`] was cancelled: stop then.
249 pub fn cancelled(&self) -> bool {
250 self.cancelled.load(Ordering::SeqCst)
251 }
252}
253
254/// A [`spawn_then`] continuation, kept on the app's thread.
255type Then<T> = Box<dyn FnOnce(T, &mut Ctx)>;
256
257/// Drive `future` to completion on its own thread, then run `done` with its
258/// output on the app's thread. The future is polled with a waker that
259/// parks and unparks that thread, so it suits futures that do not need a
260/// specific async runtime.
261#[track_caller]
262pub fn spawn_future<F>(future: F, done: impl FnOnce(F::Output, &mut Ctx) + Send + 'static) -> Task
263where
264 F: Future + Send + 'static,
265 F::Output: Send + 'static,
266{
267 spawn_at(Location::caller(), move || block_on(future), done)
268}
269
270/// Poll `future` on this thread until it is ready.
271#[cfg(not(target_arch = "wasm32"))]
272fn block_on<F: Future>(future: F) -> F::Output {
273 struct Unpark(std::thread::Thread);
274 impl Wake for Unpark {
275 fn wake(self: Arc<Self>) {
276 self.0.unpark();
277 }
278 }
279 let waker = Waker::from(Arc::new(Unpark(std::thread::current())));
280 let mut context = Context::from_waker(&waker);
281 let mut future = std::pin::pin!(future);
282 loop {
283 match future.as_mut().poll(&mut context) {
284 Poll::Ready(output) => return output,
285 Poll::Pending => std::thread::park(),
286 }
287 }
288}
289
290/// Poll `future` until it is ready. On wasm32 there is no other thread to
291/// wake it, so it is polled again at once: a future that waits on I/O or a
292/// timer never finishes there.
293#[cfg(target_arch = "wasm32")]
294fn block_on<F: Future>(future: F) -> F::Output {
295 let mut context = Context::from_waker(Waker::noop());
296 let mut future = std::pin::pin!(future);
297 loop {
298 if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
299 return output;
300 }
301 }
302}
303
304/// The state of a [`Resource`].
305#[derive(Clone, Debug, PartialEq, Eq)]
306pub enum Load<T> {
307 /// Not arrived yet.
308 Loading,
309 Ready(T),
310 /// The fetch failed, with its error.
311 Failed(String),
312}
313
314impl<T> Load<T> {
315 pub fn is_loading(&self) -> bool {
316 matches!(self, Load::Loading)
317 }
318
319 /// The value, if it arrived.
320 pub fn ready(&self) -> Option<&T> {
321 match self {
322 Load::Ready(value) => Some(value),
323 _ => None,
324 }
325 }
326}
327
328/// A value fetched in the background. `Copy`, like a signal: read it in a
329/// node and the node redraws when it arrives.
330pub struct Resource<T: 'static> {
331 pub(crate) state: Signal<Load<T>>,
332 start: Signal<Rc<dyn Fn()>>,
333 /// How long the last value stays while the next loads
334 /// ([`stale_after`](Resource::stale_after)).
335 stale: Signal<Option<Duration>>,
336}
337
338impl<T> Clone for Resource<T> {
339 fn clone(&self) -> Self {
340 *self
341 }
342}
343
344impl<T> Copy for Resource<T> {}
345
346impl<T: 'static> Resource<T> {
347 /// Read the state through `f`, subscribing the reader.
348 pub fn with<R>(&self, f: impl FnOnce(&Load<T>) -> R) -> R {
349 self.state.with(f)
350 }
351
352 /// Fetch again: the state goes back to [`Load::Loading`], and a result
353 /// still on its way from an earlier fetch is dropped.
354 pub fn reload(&self) {
355 let start = self.start.with_untracked(Rc::clone);
356 start();
357 }
358
359 /// When it fetches again (a [`reload`](Self::reload), or a new key of
360 /// [`resource_for`]), keep the last value (or error) for up to `wait`
361 /// rather than going back to [`Load::Loading`] at once: a preview pane
362 /// that does not blink between two quick loads, and clears once a slow
363 /// one has kept the old value on show for `wait`. The wait is on the
364 /// app's clock ([`after`](crate::after)), so a test steps through it
365 /// with [`Driver::advance`](crate::Driver::advance). `None` goes
366 /// back to loading at once, as without it.
367 #[must_use]
368 pub fn stale_after(self, wait: impl Into<Option<Duration>>) -> Resource<T> {
369 self.stale.set(wait.into());
370 self
371 }
372}
373
374impl<T: Clone + 'static> Resource<T> {
375 /// The state (a clone), subscribing the reader.
376 pub fn get(&self) -> Load<T> {
377 self.state.get()
378 }
379}
380
381/// Fetch a value in the background with `fetch` (on its own thread), now
382/// and on every [`Resource::reload`].
383///
384/// ```
385/// use intuituive::prelude::*;
386///
387/// let app = App::new(|| {
388/// let user = resource(|| Ok::<_, String>("Ada"));
389/// text(move || match user.get() {
390/// Load::Loading => "loading…".into(),
391/// Load::Ready(name) => format!("Hello, {name}"),
392/// Load::Failed(error) => format!("[red]{error}"),
393/// })
394/// .on_key("q", |cx| cx.quit())
395/// })
396/// .wait_for_tasks(true);
397/// let screen = app.render_with(&["q"], 20, 1).unwrap();
398/// assert_eq!(screen[0].trim_end(), "Hello, Ada");
399/// ```
400#[track_caller]
401pub fn resource<T, E>(fetch: impl Fn() -> Result<T, E> + Send + Sync + 'static) -> Resource<T>
402where
403 T: Send + 'static,
404 E: Display,
405{
406 let site = Location::caller();
407 let (state, generation, stale) = (signal(Load::<T>::Loading), signal(0u64), signal(None));
408 let fetch = Arc::new(fetch);
409 let start: Rc<dyn Fn()> = Rc::new(move || {
410 let fetch = fetch.clone();
411 load(site, state, generation, stale, move || {
412 fetch().map_err(|error| error.to_string())
413 });
414 });
415 start();
416 Resource {
417 state,
418 start: signal(start),
419 stale,
420 }
421}
422
423/// A [`resource`] that fetches for a key: `key` reads signals (the
424/// hovered file, the pane's width), and whenever what it returns changes,
425/// `fetch` runs for the new key on its own thread. A result for a key
426/// already left behind is dropped. With
427/// [`stale_after`](Resource::stale_after), the last key's value stays on
428/// show while the next one loads, for a while at most. The first fetch
429/// starts on the app's first turn; [`reload`](Resource::reload) fetches
430/// the current key again.
431///
432/// ```
433/// use intuituive::prelude::*;
434/// use intuituive::resource_for;
435///
436/// let app = App::new(|| {
437/// let id = signal(1);
438/// let user = resource_for(move || id.get(), |id| Ok::<_, String>(format!("user {id}")));
439/// text(move || match user.get() {
440/// Load::Ready(name) => name,
441/// _ => "…".into(),
442/// })
443/// .on_key("n", move |_| id.update(|i| *i += 1))
444/// })
445/// .wait_for_tasks(true);
446/// let mut driver = app.driver(10, 1);
447/// driver.press("n");
448/// assert_eq!(driver.screen().plain()[0].trim_end(), "user 2");
449/// ```
450///
451/// # Panics
452/// Outside a running or building app.
453#[track_caller]
454pub fn resource_for<K, T, E>(
455 key: impl Fn() -> K + 'static,
456 fetch: impl Fn(K) -> Result<T, E> + Send + Sync + 'static,
457) -> Resource<T>
458where
459 K: PartialEq + Clone + Send + 'static,
460 T: Send + 'static,
461 E: Display,
462{
463 let site = Location::caller();
464 let (state, generation, stale) = (signal(Load::<T>::Loading), signal(0u64), signal(None));
465 let current: Signal<Option<K>> = signal(None);
466 let fetch = Arc::new(fetch);
467 let start: Rc<dyn Fn()> = Rc::new(move || {
468 let Some(key) = current.get_untracked() else {
469 return;
470 };
471 let fetch = fetch.clone();
472 load(site, state, generation, stale, move || {
473 fetch(key).map_err(|error| error.to_string())
474 });
475 });
476 let start = signal(start);
477 watch(key, move |key, _| {
478 current.replace(Some(key));
479 start.with_untracked(Rc::clone)();
480 });
481 Resource {
482 state,
483 start,
484 stale,
485 }
486}
487
488/// Start a fetch of `state` with `work`: a result for an earlier one is
489/// dropped from now on. The state goes back to loading at once, or after
490/// `stale`'s wait if the fetch is still under way then.
491fn load<T: Send + 'static>(
492 site: &'static Location<'static>,
493 state: Signal<Load<T>>,
494 generation: Signal<u64>,
495 stale: Signal<Option<Duration>>,
496 work: impl FnOnce() -> Result<T, String> + Send + 'static,
497) {
498 generation.update(|g| *g += 1);
499 let mine = generation.get_untracked();
500 let arrived = Arc::new(AtomicBool::new(false));
501 match stale.get_untracked() {
502 Some(wait) if !state.with_untracked(Load::is_loading) => {
503 let arrived = arrived.clone();
504 // On the app's clock, so a test steps through the wait.
505 crate::app::after(wait, move |_| {
506 // Still waiting for this fetch: the old value goes.
507 if generation.get_untracked() == mine && !arrived.load(Ordering::SeqCst) {
508 state.update(|s| *s = Load::Loading);
509 }
510 });
511 }
512 _ => state.update(|s| *s = Load::Loading),
513 }
514 spawn_at(site, work, move |result, _| {
515 // A later fetch started: this result is stale.
516 if generation.get_untracked() == mine {
517 arrived.store(true, Ordering::SeqCst);
518 state.update(|s| {
519 *s = match result {
520 Ok(value) => Load::Ready(value),
521 Err(error) => Load::Failed(error),
522 }
523 });
524 }
525 });
526}
527
528/// The sending end of a [`latest`] signal, for its thread.
529pub struct Latest<T> {
530 /// The newest value not yet delivered.
531 slot: Arc<std::sync::Mutex<Option<T>>>,
532 /// A delivery is on its way to the app's thread; it takes the newest.
533 queued: Arc<AtomicBool>,
534 /// The app has gone: a delivery was dropped unrun.
535 closed: Arc<AtomicBool>,
536 proxy: crate::reactive::Proxy,
537 signal: Signal<Option<T>>,
538 /// The clock [`sleep`](Latest::sleep) keeps to, and the time of the
539 /// last beat it slept to.
540 clock: Arc<ThreadClock>,
541 due: Cell<Option<Duration>>,
542}
543
544/// Marks the app gone if the delivery it rides with is dropped unrun.
545struct Delivery {
546 /// `None` once it ran.
547 closed: Option<Arc<AtomicBool>>,
548}
549
550impl Delivery {
551 fn ran(&mut self) {
552 self.closed = None;
553 }
554}
555
556impl Drop for Delivery {
557 fn drop(&mut self) {
558 if let Some(closed) = &self.closed {
559 closed.store(true, Ordering::SeqCst);
560 }
561 }
562}
563
564impl<T: Send + 'static> Latest<T> {
565 /// Make `value` the signal's value: on the app's thread, between
566 /// frames, unless a newer one is sent before it gets there (nothing
567 /// queues: the newest wins). Returns `false` once the app has finished,
568 /// so the thread knows to stop.
569 pub fn send(&self, value: T) -> bool {
570 if self.closed.load(Ordering::SeqCst) {
571 return false;
572 }
573 *self.slot.lock().unwrap_or_else(|e| e.into_inner()) = Some(value);
574 if self.queued.swap(true, Ordering::SeqCst) {
575 // The delivery on its way takes this value.
576 return true;
577 }
578 let (slot, queued, signal) = (self.slot.clone(), self.queued.clone(), self.signal);
579 let mut delivery = Delivery {
580 closed: Some(self.closed.clone()),
581 };
582 let sent = self.proxy.send(Box::new(move |_: &mut Ctx| {
583 delivery.ran();
584 queued.store(false, Ordering::SeqCst);
585 let value = slot.lock().unwrap_or_else(|e| e.into_inner()).take();
586 if let Some(value) = value {
587 signal.update(|slot| *slot = Some(value));
588 }
589 }));
590 if !sent {
591 self.closed.store(true, Ordering::SeqCst);
592 }
593 sent
594 }
595
596 /// Wait until `period` after the last beat (after now, the first
597 /// time): a thread that makes a value every `period` keeps to its beat
598 /// however long each takes to make. One that fell behind does not
599 /// hurry through the beats it missed: as a timer made with
600 /// [`every`](crate::every), it goes on at once and then keeps to its
601 /// step. It waits on the wall clock; in an app
602 /// that [waits for its tasks](crate::App::wait_for_tasks), as a test
603 /// does, on the app's clock instead, so a headless script's
604 /// `wait(..)` moves the thread on as far as it moves the app. Returns
605 /// `false` once the app has finished, as [`send`](Self::send) does.
606 ///
607 /// ```
608 /// use intuituive::interact::headless::{Headless, Script};
609 /// use intuituive::prelude::*;
610 /// use std::time::Duration;
611 ///
612 /// let app = App::new(|| {
613 /// let tick = latest(|tx| {
614 /// let mut n = 0;
615 /// while tx.send(n) && tx.sleep(Duration::from_millis(100)) {
616 /// n += 1;
617 /// }
618 /// });
619 /// text!("{}", tick.get().unwrap_or(0))
620 /// })
621 /// .wait_for_tasks(true);
622 /// let script = Script::new().wait(Duration::from_millis(350)).keys("ctrl+c");
623 /// let mut backend = Headless::new(script, 10, 1);
624 /// let record = backend.record();
625 /// app.run_on(&mut backend).unwrap();
626 /// // Exactly: the thread kept to the app's clock.
627 /// assert_eq!(record.borrow().last_frame().trim_end(), "3");
628 /// ```
629 pub fn sleep(&self, period: Duration) -> bool {
630 if self.closed.load(Ordering::SeqCst) {
631 return false;
632 }
633 let now = self.clock.now();
634 // The shortest beat is a millisecond, as a timer's.
635 let period = period.max(Duration::from_millis(1));
636 let next = self.due.get().unwrap_or(now) + period;
637 let due = if next >= now {
638 next
639 } else {
640 let behind = (now - next).as_nanos() % period.as_nanos();
641 now + Duration::from_nanos((period.as_nanos() - behind) as u64)
642 };
643 self.due.set(Some(due));
644 self.clock.sleep_until(due) && !self.closed.load(Ordering::SeqCst)
645 }
646
647 /// Wait until `ready` is true, or `timeout` has passed (`None`: no
648 /// limit), looking again each time a [`Notifier`] from
649 /// [`notifier`](Self::notifier) is notified: a device's buffer filled,
650 /// a message in a channel. Whether `ready` was true; `false` on the
651 /// timeout, or once the app has finished.
652 ///
653 /// Waiting this way, the thread counts as asleep for an app that
654 /// [waits for its tasks](crate::App::wait_for_tasks), as a test does:
655 /// its driver does not hold each turn for it (as it would for a
656 /// thread blocked on its own), and the timeout is on the app's clock.
657 /// Once notified it counts as running until it sleeps or waits again,
658 /// so what it sends then is in the same turn on every run.
659 ///
660 /// ```
661 /// use intuituive::prelude::*;
662 /// use std::sync::atomic::{AtomicU32, Ordering};
663 /// use std::sync::Arc;
664 /// use std::time::Duration;
665 ///
666 /// let filled = Arc::new(AtomicU32::new(0));
667 /// let device = filled.clone();
668 /// let app = App::new(move || {
669 /// let filled = filled.clone();
670 /// let level = latest(move |tx| {
671 /// let notifier = tx.notifier();
672 /// // A device's own thread fills a buffer every 50 ms.
673 /// std::thread::spawn(move || {
674 /// for _ in 0..3 {
675 /// std::thread::sleep(Duration::from_millis(50));
676 /// device.fetch_add(1, Ordering::SeqCst);
677 /// notifier.notify();
678 /// }
679 /// });
680 /// let mut seen = 0;
681 /// while seen < 3 {
682 /// if !tx.wait_for(|| filled.load(Ordering::SeqCst) > seen, None) {
683 /// return;
684 /// }
685 /// seen = filled.load(Ordering::SeqCst);
686 /// tx.send(seen);
687 /// }
688 /// });
689 /// text!("{}", level.get().unwrap_or(0))
690 /// })
691 /// .wait_for_tasks(true);
692 /// let mut driver = app.driver(5, 1);
693 /// let start = std::time::Instant::now();
694 /// while driver.screen().plain()[0].trim_end() != "3" {
695 /// driver.advance(Duration::from_millis(10));
696 /// }
697 /// // No turn waited the five seconds a busy thread would hold it.
698 /// assert!(start.elapsed() < Duration::from_secs(4));
699 /// ```
700 pub fn wait_for(
701 &self,
702 mut ready: impl FnMut() -> bool,
703 timeout: impl Into<Option<Duration>>,
704 ) -> bool {
705 let timeout = timeout.into();
706 let due = timeout.map(|t| self.clock.now() + t);
707 self.clock.wait_for(&mut ready, due) && !self.closed.load(Ordering::SeqCst)
708 }
709
710 /// A handle any thread can [`notify`](Notifier::notify) a
711 /// [`wait_for`](Self::wait_for) with: `Clone` and `Send`, for the
712 /// device callback or the channel's sender.
713 pub fn notifier(&self) -> Notifier {
714 Notifier {
715 clock: self.clock.clone(),
716 }
717 }
718
719 /// Run `work`, which blocks on something else (a channel's `recv`, a
720 /// read from a pipe), with the thread counted as asleep for an app on
721 /// its own clock, as [`wait_for`](Self::wait_for) is: the driver's
722 /// turns do not wait for it. What it sends after is picked up when it
723 /// comes, not in a set turn; to keep a test exact, wait with
724 /// [`wait_for`](Self::wait_for) and a [`Notifier`] instead.
725 pub fn blocking<R>(&self, work: impl FnOnce() -> R) -> R {
726 self.clock.running(false);
727 let out = work();
728 self.clock.running(true);
729 out
730 }
731}
732
733/// Wakes a [`Latest::wait_for`] to look again: made by
734/// [`Latest::notifier`], sent to whatever thread makes the condition
735/// true.
736#[derive(Clone)]
737pub struct Notifier {
738 clock: Arc<ThreadClock>,
739}
740
741impl Notifier {
742 /// Wake every thread of the app waiting in
743 /// [`wait_for`](Latest::wait_for), to look at its condition again.
744 pub fn notify(&self) {
745 self.clock.notify();
746 }
747}
748
749/// The clock [`latest`] threads keep to with [`Latest::sleep`]: the wall
750/// clock, or, in an app that waits for its tasks, the app's own, which its
751/// driver moves on. Then the driver also waits, each turn, until every
752/// such thread is asleep for a later time or finished, so what they send
753/// is the same on every run.
754pub(crate) struct ThreadClock {
755 state: Mutex<ClockState>,
756 changed: Condvar,
757 /// Where the wall clock's time starts.
758 start: WallStart,
759}
760
761/// `Instant::now()` at the clock's start. On `wasm32-unknown-unknown` std
762/// has no clock (`Instant::now` panics) and no threads to keep to it, so
763/// the wall's time there stays at zero.
764#[cfg(not(target_arch = "wasm32"))]
765struct WallStart(std::time::Instant);
766#[cfg(target_arch = "wasm32")]
767struct WallStart;
768
769impl WallStart {
770 #[cfg(not(target_arch = "wasm32"))]
771 fn now() -> WallStart {
772 WallStart(std::time::Instant::now())
773 }
774 #[cfg(target_arch = "wasm32")]
775 fn now() -> WallStart {
776 WallStart
777 }
778 #[cfg(not(target_arch = "wasm32"))]
779 fn elapsed(&self) -> Duration {
780 self.0.elapsed()
781 }
782 #[cfg(target_arch = "wasm32")]
783 fn elapsed(&self) -> Duration {
784 Duration::ZERO
785 }
786}
787
788#[derive(Default)]
789struct ClockState {
790 /// Threads keep to the app's clock, not the wall's.
791 manual: bool,
792 /// The app's time, as its driver last set it.
793 now: Duration,
794 /// Threads started and neither asleep on the app's clock nor finished.
795 running: usize,
796 /// Threads asleep on the app's clock, by ticket, with when each wakes.
797 asleep: HashMap<u64, Duration>,
798 /// Threads in [`Latest::wait_for`], by ticket (with a timeout, also
799 /// in `asleep`), until notified or due.
800 waiting: std::collections::HashSet<u64>,
801 /// Bumped by each notify: a waiter that saw it change looks again.
802 notified: u64,
803 next: u64,
804 /// The app has finished: sleepers wake, and stop.
805 closed: bool,
806}
807
808impl ThreadClock {
809 pub(crate) fn new() -> Arc<ThreadClock> {
810 Arc::new(ThreadClock {
811 state: Mutex::new(ClockState::default()),
812 changed: Condvar::new(),
813 start: WallStart::now(),
814 })
815 }
816
817 fn lock(&self) -> MutexGuard<'_, ClockState> {
818 self.state.lock().unwrap_or_else(|e| e.into_inner())
819 }
820
821 /// Keep threads to the app's clock from now on (`manual`), or to the
822 /// wall's.
823 pub(crate) fn set_manual(&self, manual: bool) {
824 self.lock().manual = manual;
825 }
826
827 fn now(&self) -> Duration {
828 let state = self.lock();
829 if state.manual {
830 state.now
831 } else {
832 self.start.elapsed()
833 }
834 }
835
836 /// A thread starts (or ends, if not `started`).
837 fn running(&self, started: bool) {
838 let mut state = self.lock();
839 if started {
840 state.running += 1;
841 } else {
842 state.running = state.running.saturating_sub(1);
843 self.changed.notify_all();
844 }
845 }
846
847 /// Sleep until `due`; `false` once the app has finished.
848 fn sleep_until(&self, due: Duration) -> bool {
849 let mut state = self.lock();
850 if !state.manual {
851 drop(state);
852 let wait = due.saturating_sub(self.start.elapsed());
853 if !wait.is_zero() {
854 std::thread::sleep(wait);
855 }
856 return !self.lock().closed;
857 }
858 if due <= state.now || state.closed {
859 return !state.closed;
860 }
861 let ticket = state.next;
862 state.next += 1;
863 state.asleep.insert(ticket, due);
864 state.running = state.running.saturating_sub(1);
865 self.changed.notify_all();
866 // Woken by `advance`, which counts it running again, or the end.
867 while state.asleep.contains_key(&ticket) && !state.closed {
868 state = self.changed.wait(state).unwrap_or_else(|e| e.into_inner());
869 }
870 !state.closed
871 }
872
873 /// Wait until `ready`, or the app's time `due`, waking to look
874 /// again on each [`notify`](Self::notify); whether `ready` was true.
875 /// The thread counts as asleep meanwhile.
876 fn wait_for(&self, ready: &mut dyn FnMut() -> bool, due: Option<Duration>) -> bool {
877 loop {
878 let seen = self.lock().notified;
879 if ready() {
880 return true;
881 }
882 let mut state = self.lock();
883 if state.closed {
884 return false;
885 }
886 if state.notified != seen {
887 // Notified while looking: look again.
888 continue;
889 }
890 if !state.manual {
891 // The wall clock: a wait on the notifications, cut at `due`.
892 let left = due.map(|due| due.saturating_sub(self.start.elapsed()));
893 if left.is_some_and(|left| left.is_zero()) {
894 return false;
895 }
896 let wait = left.unwrap_or(Duration::from_secs(3600));
897 drop(
898 self.changed
899 .wait_timeout_while(state, wait, |s| s.notified == seen && !s.closed),
900 );
901 continue;
902 }
903 if due.is_some_and(|due| due <= state.now) {
904 return false;
905 }
906 let ticket = state.next;
907 state.next += 1;
908 state.waiting.insert(ticket);
909 if let Some(due) = due {
910 state.asleep.insert(ticket, due);
911 }
912 state.running = state.running.saturating_sub(1);
913 self.changed.notify_all();
914 // Woken by a notify or `advance`, which count it running again,
915 // or the end.
916 while state.waiting.contains(&ticket) && !state.closed {
917 state = self.changed.wait(state).unwrap_or_else(|e| e.into_inner());
918 }
919 }
920 }
921
922 /// Wake every [`wait_for`](Self::wait_for) to look again.
923 fn notify(&self) {
924 let mut state = self.lock();
925 state.notified += 1;
926 let woken: Vec<u64> = state.waiting.drain().collect();
927 for ticket in &woken {
928 state.asleep.remove(ticket);
929 }
930 state.running += woken.len();
931 self.changed.notify_all();
932 }
933
934 /// The app's clock is at `now`: wake the threads that are due.
935 pub(crate) fn advance(&self, now: Duration) {
936 let mut state = self.lock();
937 state.now = now;
938 let due: Vec<u64> = state
939 .asleep
940 .iter()
941 .filter(|(_, at)| **at <= now)
942 .map(|(ticket, _)| *ticket)
943 .collect();
944 for ticket in &due {
945 state.asleep.remove(ticket);
946 state.waiting.remove(ticket);
947 }
948 state.running += due.len();
949 self.changed.notify_all();
950 }
951
952 /// Wait (a while at most) until every thread is asleep on the app's
953 /// clock or finished. Only on the app's clock.
954 pub(crate) fn settle(&self) {
955 let deadline = crate::clock::Instant::now() + THREAD_SETTLE;
956 let mut state = self.lock();
957 while state.manual && state.running > 0 && !state.closed {
958 let left = deadline.saturating_duration_since(crate::clock::Instant::now());
959 if left.is_zero() {
960 return;
961 }
962 state = self
963 .changed
964 .wait_timeout(state, left)
965 .unwrap_or_else(|e| e.into_inner())
966 .0;
967 }
968 }
969
970 /// When the next thread asleep on the app's clock wakes; `None` on the
971 /// wall clock, or with none asleep.
972 pub(crate) fn next_due(&self) -> Option<Duration> {
973 let state = self.lock();
974 if !state.manual {
975 return None;
976 }
977 state.asleep.values().min().copied()
978 }
979
980 /// The app has finished.
981 pub(crate) fn close(&self) {
982 self.lock().closed = true;
983 self.changed.notify_all();
984 }
985}
986
987/// How long a turn of an app on its own clock waits, at most, for its
988/// `latest` threads to go to sleep: one that waits on something else (a
989/// pipe, a device) holds each turn up this long.
990const THREAD_SETTLE: Duration = Duration::from_secs(5);
991
992/// A signal holding the newest value a thread of its own produces: `run`
993/// gets the [`Latest`] to send them with, and runs until it returns.
994/// `None` until the first value arrives. Values are not queued: when the
995/// app is busy, the ones it had no time for are skipped, and the next
996/// frame shows the newest (an audio buffer, a sensor, a log's tail). Each
997/// delivery wakes the app's loop, which picks it up on its next turn.
998/// `send` returns `false` once the app has finished; stop then. The thread
999/// is not a [task](spawn): [`App::wait_for_tasks`](crate::App::wait_for_tasks)
1000/// does not wait for it; a thread that keeps to a beat with
1001/// [`Latest::sleep`] keeps to the app's clock under it. The thread starts
1002/// when the app starts running (or at once, when the app already is).
1003///
1004/// ```
1005/// use intuituive::prelude::*;
1006///
1007/// let app = App::new(|| {
1008/// let reading = latest(|tx| {
1009/// for sample in 0..=1000 {
1010/// if !tx.send(sample) {
1011/// return;
1012/// }
1013/// }
1014/// });
1015/// text(move || match reading.get() {
1016/// Some(n) => format!("last {n}"),
1017/// None => "waiting".into(),
1018/// })
1019/// });
1020/// let mut driver = app.driver(12, 1);
1021/// while driver.screen().plain()[0].trim_end() != "last 1000" {
1022/// std::thread::sleep(std::time::Duration::from_millis(1));
1023/// driver.settle();
1024/// }
1025/// ```
1026///
1027/// # In a browser
1028/// On `wasm32-unknown-unknown` there are no threads: `run` never starts
1029/// and the signal stays `None`. [`latest_every`] works there.
1030///
1031/// # Panics
1032/// Outside a running or building app.
1033#[track_caller]
1034pub fn latest<T: Send + 'static>(
1035 run: impl FnOnce(Latest<T>) + Send + 'static,
1036) -> Signal<Option<T>> {
1037 let runtime = Runtime::current_for("latest()");
1038 let value = signal(None);
1039 let clock = runtime.clock.clone();
1040 let sender = Latest {
1041 slot: Arc::new(std::sync::Mutex::new(None)),
1042 queued: Arc::new(AtomicBool::new(false)),
1043 closed: Arc::new(AtomicBool::new(false)),
1044 proxy: runtime.proxy(),
1045 signal: value,
1046 clock: clock.clone(),
1047 due: Cell::new(None),
1048 };
1049 // No threads on wasm32 (a browser tab): `run` never starts, and the
1050 // signal stays `None`. `latest_every` keeps to the app's timers there.
1051 #[cfg(target_arch = "wasm32")]
1052 let _ = (clock, sender, run);
1053 #[cfg(not(target_arch = "wasm32"))]
1054 runtime.start_thread(Box::new(move || {
1055 clock.running(true);
1056 std::thread::spawn(move || {
1057 // Counted finished however `run` ends, a panic included.
1058 struct Finished(Arc<ThreadClock>);
1059 impl Drop for Finished {
1060 fn drop(&mut self) {
1061 self.0.running(false);
1062 }
1063 }
1064 let _finished = Finished(clock);
1065 run(sender)
1066 });
1067 }));
1068 value
1069}
1070
1071/// A [`latest`] signal fed by `next`, called on a thread of its own once at
1072/// the start and then every `period`, keeping to the beat as
1073/// [`Latest::sleep`] does: on the app's clock in an app that
1074/// [waits for its tasks](crate::App::wait_for_tasks), so a test sees the
1075/// same values at the same times on every run; on the wall clock
1076/// otherwise. A generator, a sensor polled, a demo's signal.
1077///
1078/// ```
1079/// use intuituive::prelude::*;
1080/// use intuituive::latest_every;
1081/// use std::time::Duration;
1082///
1083/// let app = App::new(|| {
1084/// let mut n = 0;
1085/// let count = latest_every(Duration::from_millis(10), move || {
1086/// n += 1;
1087/// n
1088/// });
1089/// text!("{}", count.get().unwrap_or(0))
1090/// })
1091/// .wait_for_tasks(true);
1092/// let mut driver = app.driver(10, 1);
1093/// driver.advance(Duration::from_millis(45));
1094/// // Made at 0, 10, 20, 30 and 40 ms: each beat is a step of the driver.
1095/// assert_eq!(driver.screen().plain()[0].trim_end(), "5");
1096/// ```
1097///
1098/// Each value is delivered: [`Driver::advance`](crate::Driver::advance)
1099/// and a headless script's `wait` step to each beat, and in a terminal a
1100/// value wakes the loop at once, so a [`rate`](crate::rate) counts every
1101/// one (100 a second at 10 ms), however fast the beat.
1102///
1103/// # In a browser
1104/// On `wasm32-unknown-unknown` there are no threads: `next` runs on the
1105/// app's thread, once at the start and then on an [`every`](crate::every)
1106/// timer.
1107///
1108/// # Panics
1109/// Outside a running or building app.
1110#[track_caller]
1111pub fn latest_every<T: Send + 'static>(
1112 period: Duration,
1113 mut next: impl FnMut() -> T + Send + 'static,
1114) -> Signal<Option<T>> {
1115 #[cfg(not(target_arch = "wasm32"))]
1116 return latest(move |tx| while tx.send(next()) && tx.sleep(period) {});
1117 // No threads on wasm32 (a browser tab): `next` runs on the app's thread,
1118 // at the start and on an [`every`](crate::every) timer.
1119 #[cfg(target_arch = "wasm32")]
1120 {
1121 let value = signal(Some(next()));
1122 crate::every(period.max(Duration::from_millis(1)), move |_| {
1123 value.replace(Some(next()));
1124 });
1125 value
1126 }
1127}