inset-foundation 0.8.2

App, Handle, and Entity for Inset
Documentation
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
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
//! The cell the [`App`] lives in, and a task's view of it. Not a Dart file.
//!
//! Dart's isolate owns its heap and runs its microtask queue between events. Here the heap is
//! [`App`], the [`AppCell`] is what the shell and the tests hold, and a task reaches the heap
//! through [`AsyncApp::update`], borrowing the cell for one closure at a time — gpui's
//! `AppCell` and `AsyncApp`. Microtasks and task continuations run only through
//! [`AppCell::checkpoint`], with nothing borrowed.

use std::cell::{Ref, RefCell, RefMut};
use std::rc::{Rc, Weak};
use std::time::Duration;

use inset_embedder::{InertPlatform, PlatformRef};

use crate::app::App;
use crate::executor::{ExecutorHandle, ForegroundExecutor, Task};
use crate::timers::Timers;

/// Round budget for one checkpoint: a microtask and a task feeding each other forever must
/// fail loud, not starve the event loop; each round's own drain has its own budget.
const CHECKPOINT_BUDGET: usize = 100_000;

/// Owns the [`App`] and the one door through which its microtasks and tasks run.
pub struct AppCell {
    app: RefCell<App>,
}

impl AppCell {
    /// An `App` on an inert platform, for tests.
    pub fn new() -> Rc<AppCell> {
        AppCell::with_platform(Rc::new(InertPlatform))
    }

    /// The `App` of a running program, on the host's platform.
    pub fn with_platform(platform: PlatformRef) -> Rc<AppCell> {
        let executor = ForegroundExecutor::new(platform.dispatcher());
        Rc::new_cyclic(|this| AppCell {
            app: RefCell::new(App::build(this.clone(), platform, executor)),
        })
    }

    /// # Panics
    ///
    /// While [`borrow_mut`](Self::borrow_mut) is held.
    pub fn borrow(&self) -> Ref<'_, App> {
        self.app.borrow()
    }

    /// The `App` for one platform event or one test step. Release it before
    /// [`checkpoint`](Self::checkpoint) or [`elapse`](Self::elapse).
    ///
    /// # Panics
    ///
    /// While another borrow is held.
    pub fn borrow_mut(&self) -> RefMut<'_, App> {
        self.app.borrow_mut()
    }

    /// The microtask checkpoint — the HTML event loop's "perform a microtask checkpoint",
    /// V8's `PerformMicrotaskCheckpoint`, the point where Dart drains its microtask queue:
    /// runs every queued microtask and polls every ready task, including the ones they
    /// queue, until nothing is left. Returns the number of rounds.
    /// The shell runs it at the end of every platform event and [`elapse`](Self::elapse)
    /// around every timer; a test runs it where it would pump.
    ///
    /// # Panics
    ///
    /// While the `App` is borrowed — a task borrows it itself, one closure at a time — and
    /// after `CHECKPOINT_BUDGET` rounds, on the assumption of a cycle.
    pub fn checkpoint(&self) -> usize {
        assert!(
            self.app.try_borrow_mut().is_ok(),
            "AppCell::checkpoint while the App is borrowed: release it first; each task \
             borrows the App for its own steps"
        );
        // Taken for a moment and let go: nothing may hold the `App` while tasks run, since
        // each task borrows it through its own `AsyncApp`.
        let executor = self.borrow().executor_handle();
        let mut rounds = 0usize;
        loop {
            self.borrow_mut().release_dropped_retained_handles();
            self.borrow_mut().flush_entity_effects();
            let had_microtasks = self.borrow().has_pending_microtasks();
            if had_microtasks {
                self.borrow_mut().drain_microtasks();
            }
            let polled = executor.poll_ready_tasks();
            if !had_microtasks && polled == 0 {
                break;
            }
            rounds += 1;
            assert!(
                rounds <= CHECKPOINT_BUDGET,
                "the checkpoint did not converge after {CHECKPOINT_BUDGET} rounds; are a \
                 microtask and a task queueing each other in a cycle?"
            );
        }
        rounds
    }

    /// The wake the host gives for a
    /// [`Dispatcher::wake_now`](inset_embedder::Dispatcher::wake_now): a post, or a task
    /// woken on another thread. Nothing to handle, so straight to the checkpoint, and then
    /// the host is told the new timer wakeup if anything queued during it changed the
    /// earliest waiting timer.
    ///
    /// # Panics
    ///
    /// While the `App` is borrowed.
    pub fn wake(&self) {
        self.checkpoint();
        self.borrow_mut().update_timer_wakeup();
    }

    /// Advances the App clock by `duration`, firing the timers that come due.
    ///
    /// A [`checkpoint`](Self::checkpoint) first, then each due timer in due order with a
    /// checkpoint after it, so a future awaiting one timer can schedule the next within the
    /// same call — Dart's event-loop position for `Timer`, and FakeAsync's `elapse` for
    /// tests. The shell calls this as platform time passes.
    ///
    /// # Panics
    ///
    /// While the `App` is borrowed.
    pub fn elapse(&self, duration: Duration) {
        let target = self.borrow().clock() + duration;
        self.checkpoint();
        let mut fired = 0usize;
        loop {
            let fired_one = self.borrow_mut().fire_next_due(target);
            if !fired_one {
                break;
            }
            fired += 1;
            Timers::assert_fire_budget(fired);
            self.checkpoint();
        }
        self.borrow_mut().advance_clock_to(target);
        self.borrow_mut().update_timer_wakeup();
    }
}

/// A task's handle on the [`App`]: what an `async` continuation captures across its `await`s.
///
/// Carries the executor's queue besides the cell, as gpui's `AsyncApp` carries its
/// executors: a [`post`](Self::post) reaches it without the `App`.
#[derive(Clone)]
pub struct AsyncApp {
    app: Weak<AppCell>,
    executor: ExecutorHandle,
}

impl AsyncApp {
    pub(crate) fn new(app: Weak<AppCell>, executor: ExecutorHandle) -> AsyncApp {
        AsyncApp { app, executor }
    }

    /// Runs `f` on the `App`, borrowing the cell for just that closure.
    ///
    /// # Panics
    ///
    /// If the `App` has been dropped, or is borrowed — which means a checkpoint ran from
    /// inside App code instead of from the cell.
    pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> R {
        let cell = self
            .app
            .upgrade()
            .expect("the App was dropped while one of its tasks still runs");
        let mut app = cell.app.try_borrow_mut().unwrap_or_else(|_| {
            panic!(
                "AsyncApp::update while the App is borrowed: tasks run only from \
                 AppCell::checkpoint, never from inside App code"
            )
        });
        f(&mut app)
    }

    /// Queues `f` to run on the `App` at the next checkpoint; the executor wakes the host so
    /// one comes.
    ///
    /// The door for a native callback — a notification, an observer — that the host delivers
    /// outside any event it drives, possibly while the `App` is borrowed: nothing here touches
    /// the `App`, so it never panics where [`update`](Self::update) would. Dart's counterpart
    /// is a native port message, which lands in the isolate's event queue. After the `App` is
    /// dropped the closure is discarded.
    pub fn post(&self, f: impl FnOnce(&mut App) + 'static) {
        if self.app.strong_count() == 0 {
            return;
        }
        let cx = self.clone();
        // Dropping the handle detaches the task; it runs to completion regardless.
        drop(self.executor.spawn(async move { cx.update(f) }));
    }

    /// [`App::run_in_background`], from a task.
    pub fn run_in_background<T: Send + 'static>(
        &self,
        work: impl FnOnce() -> T + Send + 'static,
    ) -> Task<T> {
        self.executor.run_in_background(work)
    }
}

#[cfg(test)]
mod tests {
    use std::cell::{Cell, RefCell};
    use std::rc::Rc;
    use std::sync::{Arc, Mutex};
    use std::time::{Duration, Instant};

    use inset_embedder::{Dispatcher, Platform, TargetPlatform, ViewId, ViewRef};

    use super::*;
    use crate::change_notifier::Listener;
    use crate::completer::{Completer, CompleterFuture};
    use crate::timers::Timer;

    /// A host that keeps every timer wakeup it is given and never wakes the app itself, so
    /// a test can read what the app told it and when.
    #[derive(Default)]
    struct RecordingHost {
        timer_wakeups: Mutex<Vec<Option<Instant>>>,
    }

    impl RecordingHost {
        fn timer_wakeups(&self) -> Vec<Option<Instant>> {
            self.timer_wakeups
                .lock()
                .expect("the wakeups are never poisoned: nothing runs under their lock")
                .clone()
        }
    }

    impl Dispatcher for RecordingHost {
        fn wake_at(&self, timer_wakeup: Option<Instant>) {
            self.timer_wakeups
                .lock()
                .expect("the wakeups are never poisoned: nothing runs under their lock")
                .push(timer_wakeup);
        }

        fn wake_now(&self) {}

        fn dispatch(&self, work: Box<dyn FnOnce() + Send>) {
            work();
        }
    }

    struct RecordingPlatform(Arc<RecordingHost>);

    impl Platform for RecordingPlatform {
        fn target_platform(&self) -> TargetPlatform {
            TargetPlatform::Android
        }

        fn request_frame(&self, _view: ViewId) {}

        fn now(&self) -> Instant {
            Instant::now()
        }

        fn dispatcher(&self) -> Arc<dyn Dispatcher> {
            Arc::clone(&self.0) as Arc<dyn Dispatcher>
        }

        fn views(&self) -> Vec<ViewRef> {
            Vec::new()
        }

        fn view(&self, _id: ViewId) -> Option<ViewRef> {
            None
        }
    }

    fn app_on_a_recording_host() -> (Rc<AppCell>, Arc<RecordingHost>) {
        let host = Arc::new(RecordingHost::default());
        let cell = AppCell::with_platform(Rc::new(RecordingPlatform(Arc::clone(&host))));
        (cell, host)
    }

    fn counter() -> Rc<Cell<u32>> {
        Rc::new(Cell::new(0))
    }

    fn log() -> Rc<RefCell<Vec<&'static str>>> {
        Rc::new(RefCell::new(Vec::new()))
    }

    #[test]
    fn a_post_runs_at_the_next_checkpoint_even_when_made_while_borrowed() {
        let cell = AppCell::new();
        let ran = counter();
        let cx = cell.borrow().to_async();
        {
            let _borrowed = cell.borrow_mut();
            let ran = Rc::clone(&ran);
            cx.post(move |_app| ran.set(ran.get() + 1));
        }
        assert_eq!(ran.get(), 0, "a post never runs inline");
        cell.checkpoint();
        assert_eq!(ran.get(), 1);
        cell.checkpoint();
        assert_eq!(ran.get(), 1, "a post runs once");
    }

    #[test]
    fn a_post_after_the_app_is_gone_is_dropped() {
        let cx = AppCell::new().borrow().to_async();
        cx.post(|_app| panic!("the App is gone"));
    }

    /// A wake asked for by a task woken on another thread is not a timer wakeup, and the
    /// wake it is given must leave the waiting timer's wakeup exactly where it is. Mixing
    /// the two is what once stopped an animated image as soon as nothing else touched the
    /// loop.
    #[test]
    fn waking_for_a_ready_task_leaves_the_timer_wakeup_alone() {
        let (cell, host) = app_on_a_recording_host();
        Timer::new(
            &mut cell.borrow_mut(),
            Duration::from_millis(50),
            Listener::new(|_app| {}),
        );
        let wakeups = host.timer_wakeups();
        assert_eq!(wakeups.len(), 1, "the timer sets its wakeup");
        assert!(wakeups[0].is_some_and(|wakeup| wakeup > Instant::now()));

        // What a task woken off the main thread does, and the wake the host gives for it.
        host.wake_now();
        cell.wake();

        assert_eq!(
            host.timer_wakeups(),
            wakeups,
            "the wake neither cleared the timer wakeup nor made the app set it again"
        );
    }

    /// The timer wakeup is the earliest waiting timer and nothing else, so losing that
    /// timer has to change it: to the next one, or to no timer at all.
    #[test]
    fn cancelling_the_last_timer_tells_the_host_no_timer_is_waiting() {
        let (cell, host) = app_on_a_recording_host();
        let timer = Timer::new(
            &mut cell.borrow_mut(),
            Duration::from_millis(50),
            Listener::new(|_app| {}),
        );
        timer.cancel(&mut cell.borrow_mut());
        let wakeups = host.timer_wakeups();
        assert_eq!(wakeups.len(), 2, "the cancel changes the timer wakeup");
        assert_eq!(
            wakeups[1], None,
            "with no timer left the host is told to stop waiting for one"
        );
    }

    #[test]
    fn a_wake_runs_the_checkpoint() {
        let cell = AppCell::new();
        let ran = counter();
        let cx = cell.borrow().to_async();
        {
            let ran = Rc::clone(&ran);
            cx.post(move |_app| ran.set(ran.get() + 1));
        }
        cell.wake();
        assert_eq!(ran.get(), 1);
    }

    #[test]
    fn a_spawned_continuation_runs_at_the_checkpoint_not_inline() {
        let cell = AppCell::new();
        let ran = counter();
        {
            let seen = ran.clone();
            cell.borrow()
                .spawn(async move |_cx| seen.set(seen.get() + 1));
            assert_eq!(ran.get(), 0, "spawn never polls inline");
        }
        cell.checkpoint();
        assert_eq!(ran.get(), 1);
    }

    #[test]
    fn a_task_reaches_the_app_through_update() {
        let cell = AppCell::new();
        let handle = cell.borrow_mut().create(3u32);
        cell.borrow().spawn(async move |cx| {
            cx.update(|app| *app.get_mut(handle) += 4);
        });
        cell.checkpoint();
        assert_eq!(*cell.borrow().get(handle), 7);
    }

    #[test]
    fn awaiting_a_completer_resumes_at_the_checkpoint_after_completion() {
        let cell = AppCell::new();
        let completer: Completer<u32> = Completer::new();
        let future = completer.future();
        let seen = counter();
        let out = seen.clone();
        cell.borrow().spawn(async move |_cx| out.set(future.await));
        cell.checkpoint();
        assert_eq!(seen.get(), 0, "pending: the waker is parked");

        completer.complete(&mut cell.borrow_mut(), 7);
        assert_eq!(seen.get(), 0, "completion wakes; it does not poll inline");
        cell.checkpoint();
        assert_eq!(seen.get(), 7);
    }

    #[test]
    fn a_completer_resolves_every_listener() {
        let cell = AppCell::new();
        let completer: Completer<u32> = Completer::new();
        let seen = counter();
        for _ in 0..3 {
            let future = completer.future();
            let out = seen.clone();
            cell.borrow().spawn(async move |_cx| {
                let value = future.await;
                out.set(out.get() + value);
            });
        }
        cell.checkpoint();
        completer.complete(&mut cell.borrow_mut(), 5);
        cell.checkpoint();
        assert_eq!(seen.get(), 15);
    }

    #[test]
    fn a_task_awaited_as_a_task_yields_its_result() {
        let cell = AppCell::new();
        let seen = counter();
        let out = seen.clone();
        let inner = cell.borrow().spawn(async move |_cx| 40u32);
        cell.borrow()
            .spawn(async move |_cx| out.set(inner.await + 2));
        cell.checkpoint();
        assert_eq!(seen.get(), 42);
    }

    /// Dart never ends a checkpoint with work queued: what a continuation queues runs in the
    /// same checkpoint.
    #[test]
    fn a_checkpoint_runs_what_its_continuations_queue() {
        let cell = AppCell::new();
        let events = log();
        let completer: Completer<()> = Completer::new();
        let future = completer.future();
        let seen = events.clone();
        cell.borrow().spawn(async move |cx| {
            future.await;
            seen.borrow_mut().push("task");
            let seen = seen.clone();
            cx.update(|app| {
                app.schedule_microtask(Listener::new(move |_app| {
                    seen.borrow_mut().push("microtask from the task")
                }))
            });
        });
        cell.checkpoint();

        completer.complete(&mut cell.borrow_mut(), ());
        cell.checkpoint();
        assert_eq!(*events.borrow(), ["task", "microtask from the task"]);
        assert!(!cell.borrow().has_pending_microtasks());
    }

    /// A timer as a future: what `Future.delayed` awaits.
    fn delayed(app: &mut App, duration: Duration) -> CompleterFuture<()> {
        let completer: Completer<()> = Completer::new();
        let future = completer.future();
        Timer::new(
            app,
            duration,
            Listener::new(move |app| completer.complete(app, ())),
        );
        future
    }

    #[test]
    fn elapse_lets_a_task_chain_timers_within_one_call() {
        let cell = AppCell::new();
        let seen = counter();
        let out = seen.clone();
        cell.borrow().spawn(async move |cx| {
            let first = cx.update(|app| delayed(app, Duration::from_millis(10)));
            first.await;
            out.set(1);
            let second = cx.update(|app| delayed(app, Duration::from_millis(10)));
            second.await;
            out.set(2);
        });
        cell.checkpoint();
        cell.elapse(Duration::from_millis(15));
        assert_eq!(
            seen.get(),
            1,
            "the first timer fired, the second is still due"
        );
        cell.elapse(Duration::from_millis(10));
        assert_eq!(
            seen.get(),
            2,
            "the second timer was scheduled by the resumed task"
        );
    }

    #[test]
    fn dropping_the_cell_releases_queued_and_pending_tasks() {
        let cell = AppCell::new();
        let token = Rc::new(());
        let queued = token.clone();
        cell.borrow().spawn(async move |_cx| drop(queued));
        let completer = cell.borrow_mut().create(Completer::<()>::new());
        let future = cell.borrow().get(completer).future();
        let pending = token.clone();
        cell.borrow().spawn(async move |_cx| {
            future.await;
            drop(pending);
        });
        assert_eq!(Rc::strong_count(&token), 3);

        drop(cell);
        assert_eq!(
            Rc::strong_count(&token),
            1,
            "the queued and the pending task's futures were dropped with the App"
        );
    }

    #[test]
    #[should_panic(expected = "AppCell::checkpoint while the App is borrowed")]
    fn a_checkpoint_while_borrowed_panics() {
        let cell = AppCell::new();
        let _app = cell.borrow_mut();
        cell.checkpoint();
    }

    #[test]
    fn then_runs_on_the_microtask_queue_after_completion() {
        let cell = AppCell::new();
        let completer: Completer<u32> = Completer::new();
        let seen = counter();
        {
            let mut app = cell.borrow_mut();
            let out = seen.clone();
            completer
                .future()
                .then(&mut app, move |_app, value| out.set(value));
            completer.complete(&mut app, 9);
            assert_eq!(seen.get(), 0, "a listener never runs inline");
            app.drain_microtasks();
            assert_eq!(seen.get(), 9, "a listener is a microtask");
        }
        let later = counter();
        {
            let mut app = cell.borrow_mut();
            let out = later.clone();
            completer
                .future()
                .then(&mut app, move |_app, value| out.set(value));
            assert_eq!(later.get(), 0, "even on a resolved future");
        }
        cell.checkpoint();
        assert_eq!(later.get(), 9);
    }
}