Files
vectorcraft/crates/plugins/src/deferred.rs
T
dotCooCoo feda950bc8 Plug-ins: live effects of a document's first view share a 2,000 ms budget
While the app draws a document on screen, the live-effect plug-in runs of
its first view share a 2,000 ms budget (VectorCraft's own value); each run
takes the lesser of its 5,000 ms limit and what is left. A stage left out
for time, or one that times out, fails, has no plug-in or meets Safe Mode,
draws its input art unchanged and is deferred, and the status bar says how
many. Later drawings leave a deferred stage as it is until its input or
parameters change.

- effect.deferredStatus lists the deferred stages with object, effect
  index, fill or stroke, plug-in and reason, objects in symbol, pattern,
  brush and opacity-mask art included.
- Effect › Refresh Deferred Effects (effect.refreshDeferred {ids?}) runs
  them again with a fresh budget and returns {refreshed, stillDeferred,
  failed}.
- Each document has its own budget and deferred stages; exports, Expand
  Appearance and commands run every stage under the per-call limit.
- The web build has no clock for the budget: its first view runs no
  plug-in effect, and a refresh runs them.

Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md.
2026-11-10 22:16:01 -08:00

725 lines
29 KiB
Rust

//! Live-effect plug-ins while a document opens: the plug-in effects drawn for its first view share
//! a time budget of [`OPENING_BUDGET_MS`], and a stage that is not run for want of time, or that
//! gives no result, is deferred: it draws its input art and is listed with the reason
//! ([`Deferrals::list`]) until it runs again.
//!
//! One [`Deferrals`] belongs to each open document. Drawing that document on screen runs inside
//! [`Deferrals::view`], which applies its budget and its deferred stages to the plug-in effects
//! evaluated on that thread ([`crate::effect`]):
//!
//! - until [`Deferrals::first_view_done`], every run takes the lesser of its per-call limit and
//! what is left of the budget, no run starts once the budget is spent, and a result that comes
//! back after the budget ran out is not used;
//! - afterwards a deferred stage is not run again while its input and parameters stay the same; a
//! stage whose input or parameters changed runs under the per-call limit;
//! - [`Deferrals::refresh`] runs deferred stages again with a fresh budget.
//!
//! Work outside a view (exports, commands, drawing without a screen) runs every stage under the
//! per-call limit. When such a run gives a result for a stage some document has deferred,
//! [`revision`] changes, so that drawings made with the deferred stage are drawn again.
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
/// The time the plug-in effects drawn for a document's first view share, in milliseconds.
pub const OPENING_BUDGET_MS: u64 = 2_000;
/// Why a stage is deferred.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Reason {
/// It was not run, or its result came too late, because the budget was spent.
Budget,
/// It ran to its per-call time limit.
Timeout,
/// Its plug-in isn't installed as a live effect.
Missing,
/// It trapped, panicked, returned art that was refused, or its parameters were refused.
Failed,
/// Safe Mode is on: no plug-in runs.
SafeMode,
}
impl Reason {
/// The reason as `effect.deferredStatus` names it.
pub fn name(self) -> &'static str {
match self {
Reason::Budget => "budget",
Reason::Timeout => "timeout",
Reason::Missing => "missing",
Reason::Failed => "failed",
Reason::SafeMode => "safeMode",
}
}
}
/// Where a plug-in effect sits: the object, the index of the effect in its list, and the fill or
/// stroke whose list that is (`None`: the object's own effects).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Stage {
pub object: u64,
pub effect: usize,
pub item: Option<usize>,
}
/// A deferred stage.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Deferred {
pub stage: Stage,
/// The plug-in's id.
pub plugin: String,
pub reason: Reason,
/// The input and parameters the stage was deferred with (the effect cache's key), 0 when the
/// plug-in was not looked at (Safe Mode, a missing plug-in).
pub(crate) key: u64,
}
/// What [`Deferrals::refresh`] did with the stages it ran again.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Refreshed {
/// Stages that gave a result.
pub refreshed: usize,
/// Stages left deferred for want of time, or with input the refresh did not run.
pub still_deferred: usize,
/// Stages that ran out of their per-call time, failed or had no plug-in again.
pub failed: usize,
}
/// Milliseconds on a monotonic clock, `None` where there is none (the web build).
pub type Clock = Arc<dyn Fn() -> Option<u64> + Send + Sync>;
/// Milliseconds since the first reading in this process (native builds); `None` on the web build,
/// whose `std` has no monotonic clock.
fn monotonic() -> Option<u64> {
#[cfg(not(target_arch = "wasm32"))]
{
static START: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
let start = START.get_or_init(std::time::Instant::now);
Some(u64::try_from(start.elapsed().as_millis()).unwrap_or(u64::MAX))
}
#[cfg(target_arch = "wasm32")]
None
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Phase {
/// Runs share a budget that started at `started` (`None`: no run yet). `first`: the first
/// view, which reports what it deferred when it ends.
Budgeted { started: Option<u64>, first: bool },
/// Deferred stages are left as they are while their input stays the same.
Open,
}
struct State {
phase: Phase,
deferred: Vec<Deferred>,
/// How many stages the first view left deferred, until [`Deferrals::take_report`] takes it.
report: Option<usize>,
/// The refresh running, if one is ([`Deferrals::refresh`]).
refresh: Option<Refresh>,
clock: Clock,
}
/// A [`Deferrals::refresh`] while it runs. It applies only to the thread that runs it: drawings
/// of the document on other threads go on as if no refresh ran.
struct Refresh {
thread: std::thread::ThreadId,
/// When its budget started (`None`: no run yet).
started: Option<u64>,
/// The objects whose stages run again; the others' stay deferred.
ids: Vec<u64>,
/// The cache keys whose cached failures have been run again.
retried: Vec<u64>,
/// The stages of `ids` that were drawn.
visited: Vec<Stage>,
/// The stage inputs of `ids` that ran, or were taken from the cache, or were deferred again.
touched: Vec<(Stage, u64)>,
}
impl State {
/// The refresh running on this thread.
fn refresh_here(&mut self) -> Option<&mut Refresh> {
let here = std::thread::current().id();
self.refresh.as_mut().filter(|r| r.thread == here)
}
/// The phase that applies on this thread: a refresh running here has a budget of its own.
fn phase_here(&mut self) -> Phase {
match self.refresh_here() {
Some(r) => Phase::Budgeted { started: r.started, first: false },
None => self.phase,
}
}
/// Is a refresh running here, and does it run the stages of `object`?
fn refreshes(&mut self, object: u64) -> Option<bool> {
self.refresh_here().map(|r| r.ids.contains(&object))
}
}
impl Drop for State {
fn drop(&mut self) {
for d in &self.deferred {
unmark(d.key);
}
}
}
/// The deferred stages and the opening budget of one open document. Clones share them.
#[derive(Clone)]
pub struct Deferrals(Arc<Mutex<State>>);
impl Default for Deferrals {
fn default() -> Self {
Self::with_clock(Arc::new(monotonic))
}
}
impl std::fmt::Debug for Deferrals {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Deferrals").field("deferred", &self.list()).finish()
}
}
/// What a budgeted run may take.
pub(crate) enum Allowance {
/// No budget applies: the per-call limit.
Unlimited,
/// At most this many milliseconds, which end the budget at `until`.
Until { ms: u64, until: u64 },
/// The budget is spent: the stage is deferred without running.
Spent,
}
impl Deferrals {
/// A document's deferrals before its first view, timed by the monotonic clock.
pub fn new() -> Self {
Self::default()
}
/// [`Deferrals::new`] timed by `clock` (tests give a clock of their own).
pub fn with_clock(clock: Clock) -> Self {
Self(Arc::new(Mutex::new(State {
phase: Phase::Budgeted { started: None, first: true },
deferred: Vec::new(),
report: None,
refresh: None,
clock,
})))
}
fn state(&self) -> std::sync::MutexGuard<'_, State> {
self.0.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
}
/// The deferred stages, by object and effect, each once (a stage drawn with several inputs,
/// such as the paths of a group, is listed with the first one's reason).
pub fn list(&self) -> Vec<Deferred> {
let mut out = self.state().deferred.clone();
out.sort_by_key(|d| (d.stage.object, d.stage.item, d.stage.effect));
out.dedup_by_key(|d| d.stage);
out
}
/// Is the first view still being drawn ([`Deferrals::first_view_done`] not called yet)?
pub fn opening(&self) -> bool {
matches!(self.state().phase, Phase::Budgeted { first: true, .. })
}
/// Runs `f` with these deferrals applied to the plug-in effects evaluated on this thread.
pub fn view<T>(&self, f: impl FnOnce() -> T) -> T {
let previous = VIEW.with(|v| v.borrow_mut().replace(self.clone()));
let _ = PASSES.try_with(|p| p.borrow_mut().push(Pass::default()));
let _restore = Restore { previous, current: self.clone() };
f()
}
/// One drawing inside [`Deferrals::view`] has ended: a stage that ran after the first view,
/// whether it gave a result or was deferred again, keeps only the records of the inputs that
/// drawing left deferred. The records of its other inputs were made with input it no longer
/// has.
fn settle(&self, pass: Pass) {
if pass.ran.is_empty() {
return;
}
self.state().deferred.retain(|d| {
let keep = !pass.ran.contains(&d.stage) || pass.seen.contains(&(d.stage, d.key));
if !keep {
unmark(d.key);
}
keep
});
}
/// The first view has been drawn: later drawings leave deferred stages deferred, and
/// [`Deferrals::take_report`] gives how many there are. Does nothing after the first call.
pub fn first_view_done(&self) {
let mut s = self.state();
if let Phase::Budgeted { first: true, .. } = s.phase {
s.phase = Phase::Open;
s.report = Some(s.stages().len());
}
}
/// How many stages the first view left deferred, once, after it was drawn.
pub fn take_report(&self) -> Option<usize> {
self.state().report.take()
}
/// Runs the deferred stages of the objects in `objects` (all of them when `None`) again with a
/// fresh budget: `run` is given those objects, draws them inside [`Deferrals::view`] on this
/// thread, and returns the objects it drew whole, every part of them reached. A stage the
/// drawing does not reach (that of a hidden object, say) stays deferred as it was, and so does
/// a stage input it does not reach, unless the object was drawn whole: the input is then one
/// the stage no longer has. The refresh and its budget apply only to this thread.
pub fn refresh(&self, objects: Option<&[u64]>, run: impl FnOnce(&[u64]) -> Vec<u64>) -> Refreshed {
let (targets, ids) = {
let mut s = self.state();
let mut targets: Vec<Stage> = s.deferred.iter().map(|d| d.stage).filter(|t| objects.is_none_or(|o| o.contains(&t.object))).collect();
targets.sort_by_key(|t| (t.object, t.item, t.effect));
targets.dedup();
let mut ids: Vec<u64> = targets.iter().map(|t| t.object).collect();
ids.dedup();
let thread = std::thread::current().id();
let (retried, visited, touched) = (Vec::new(), Vec::new(), Vec::new());
s.refresh = Some(Refresh { thread, started: None, ids: ids.clone(), retried, visited, touched });
(targets, ids)
};
// The refresh ends when `run` returns, also when it unwinds.
struct Reopen<'a>(&'a Deferrals);
impl Drop for Reopen<'_> {
fn drop(&mut self) {
self.0.state().refresh = None;
}
}
let reopen = Reopen(self);
let whole = run(&ids);
let mut s = self.state();
let touched = match s.refresh.take() {
Some(r) => {
s.deferred.retain(|d| {
let gone = r.visited.contains(&d.stage) && whole.contains(&d.stage.object) && !r.touched.contains(&(d.stage, d.key));
if gone {
unmark(d.key);
}
!gone
});
r.touched
}
None => Vec::new(),
};
drop(s);
drop(reopen);
// A stage fails again when an input it ran with this time is deferred for a reason other
// than time; one with records left that this refresh did not run is still deferred.
let s = self.state();
let mut out = Refreshed::default();
for t in &targets {
let mut left = s.deferred.iter().filter(|d| d.stage == *t).peekable();
if left.peek().is_none() {
out.refreshed += 1;
} else if left.any(|d| d.reason != Reason::Budget && touched.contains(&(d.stage, d.key))) {
out.failed += 1;
} else {
out.still_deferred += 1;
}
}
drop(s);
if out.refreshed > 0 {
REVISION.fetch_add(1, Ordering::Relaxed);
}
out
}
/// Records `stage` with cache key `key` (of plug-in `plugin`) as deferred for `reason`, in
/// place of what was recorded for that stage and key. After the first view, when the drawing
/// ends, the stage's records of inputs that drawing did not leave deferred are dropped
/// ([`Deferrals::settle`]).
pub(crate) fn defer(&self, stage: Stage, plugin: &str, reason: Reason, key: u64) {
seen(stage, key);
let mut s = self.state();
s.remove(stage, key);
mark(key);
s.deferred.push(Deferred { stage, plugin: plugin.to_string(), reason, key });
s.ran_here(stage, key);
}
/// `stage` gave a result for cache key `key`, from a run or from the cache: it is no longer
/// deferred with that input. After the first view, when the drawing ends, the stage's records
/// of inputs that drawing did not leave deferred are dropped as well ([`Deferrals::settle`]).
pub(crate) fn ran(&self, stage: Stage, key: u64) {
let mut s = self.state();
s.remove(stage, key);
s.ran_here(stage, key);
}
/// Should `stage`, with cache key `key`, be left deferred without running? After the first
/// view, a stage deferred with the same input and parameters is, and so is one of an object a
/// refresh does not run.
pub(crate) fn holds(&self, stage: Stage, key: u64) -> bool {
let mut s = self.state();
let settled = s.phase_here() == Phase::Open || s.refreshes(stage.object) == Some(false);
let held = settled && s.deferred.iter().any(|d| d.stage == stage && d.key == key);
drop(s);
if held {
seen(stage, key);
}
held
}
/// `stage` is being drawn: a refresh running here that runs its object's stages notes that it
/// reached the stage.
pub(crate) fn visit(&self, stage: Stage) {
let mut s = self.state();
if let Some(r) = s.refresh_here().filter(|r| r.ids.contains(&stage.object) && !r.visited.contains(&stage)) {
r.visited.push(stage);
}
}
/// Drops the records of the stages for which `keep` (given the stage and the plug-in id) is
/// false, such as those of effects or objects removed from the document.
pub fn retain(&self, keep: impl Fn(Stage, &str) -> bool) {
self.state().deferred.retain(|d| {
let k = keep(d.stage, &d.plugin);
if !k {
unmark(d.key);
}
k
});
}
/// Should the failure cached for `stage` with cache key `key` be run again? Yes once per key
/// while a refresh running here runs the stages of `stage`'s object.
pub(crate) fn retries(&self, stage: Stage, key: u64) -> bool {
let mut s = self.state();
let Some(r) = s.refresh_here().filter(|r| r.ids.contains(&stage.object) && !r.retried.contains(&key)) else { return false };
r.retried.push(key);
true
}
/// What a run starting now may take of the budget: the budget starts with the first run.
/// Without a clock, the first view runs nothing and a refresh runs every stage under its
/// per-call limit.
pub(crate) fn allowance(&self) -> Allowance {
let mut s = self.state();
let Phase::Budgeted { started, first } = s.phase_here() else { return Allowance::Unlimited };
let Some(now) = (s.clock)() else { return if first { Allowance::Spent } else { Allowance::Unlimited } };
let start = started.unwrap_or(now);
match s.refresh_here() {
Some(r) => r.started = Some(start),
None => s.phase = Phase::Budgeted { started: Some(start), first },
}
let until = start.saturating_add(OPENING_BUDGET_MS);
match until.checked_sub(now).filter(|ms| *ms > 0) {
Some(ms) => Allowance::Until { ms, until },
None => Allowance::Spent,
}
}
/// Has the clock passed `until` (a result that came back now is too late)? Without a clock,
/// yes.
pub(crate) fn passed(&self, until: u64) -> bool {
let clock = self.state().clock.clone();
clock().is_none_or(|now| now > until)
}
}
impl State {
/// Removes what is recorded for `stage` with cache key `key`. A record without a key (the
/// plug-in was not looked at) stands for any input, so it goes whatever `key` is, and `key` 0
/// removes every record of the stage.
fn remove(&mut self, stage: Stage, key: u64) {
self.deferred.retain(|d| {
let keep = d.stage != stage || (d.key != key && d.key != 0 && key != 0);
if !keep {
unmark(d.key);
}
keep
});
}
/// `stage` was run with cache key `key`, or taken from the cache, by the drawing on this
/// thread: a refresh running here notes the input ([`Deferrals::refresh`]), and after the first
/// view [`Deferrals::settle`] looks at the stage when that drawing ends.
fn ran_here(&mut self, stage: Stage, key: u64) {
if let Some(r) = self.refresh_here().filter(|r| r.ids.contains(&stage.object)) {
r.touched.push((stage, key));
}
if self.phase_here() == Phase::Open {
let _ = PASSES.try_with(|p| {
if let Some(pass) = p.borrow_mut().last_mut().filter(|pass| !pass.ran.contains(&stage)) {
pass.ran.push(stage);
}
});
}
}
/// The deferred stages, each once.
fn stages(&self) -> Vec<Stage> {
let mut out: Vec<Stage> = self.deferred.iter().map(|d| d.stage).collect();
out.sort_by_key(|t| (t.object, t.item, t.effect));
out.dedup();
out
}
}
thread_local! {
static VIEW: RefCell<Option<Deferrals>> = const { RefCell::new(None) };
/// One entry for each [`Deferrals::view`] running on this thread, innermost last.
static PASSES: RefCell<Vec<Pass>> = const { RefCell::new(Vec::new()) };
}
/// What one drawing inside a view did: the stages that ran after the first view (with a result or
/// deferred again), and the stage inputs it left deferred.
#[derive(Default)]
struct Pass {
ran: Vec<Stage>,
seen: Vec<(Stage, u64)>,
}
/// The drawing on this thread left `stage` deferred with cache key `key`.
fn seen(stage: Stage, key: u64) {
let _ = PASSES.try_with(|p| {
if let Some(pass) = p.borrow_mut().last_mut() {
pass.seen.push((stage, key));
}
});
}
/// Ends the drawing and puts the previous view back, also when the work inside unwinds.
struct Restore {
previous: Option<Deferrals>,
current: Deferrals,
}
impl Drop for Restore {
fn drop(&mut self) {
if let Some(pass) = PASSES.try_with(|p| p.borrow_mut().pop()).ok().flatten() {
self.current.settle(pass);
}
let previous = self.previous.take();
let _ = VIEW.try_with(|v| *v.borrow_mut() = previous);
}
}
/// The deferrals of the view being drawn on this thread ([`Deferrals::view`]).
pub fn current() -> Option<Deferrals> {
VIEW.try_with(|v| v.borrow().clone()).ok().flatten()
}
/// Changes when deferred stages give results: [`Deferrals::refresh`] ran some, or a run outside a
/// view gave a result for a stage some document has deferred.
pub fn revision() -> u64 {
REVISION.load(Ordering::Relaxed)
}
static REVISION: AtomicU64 = AtomicU64::new(0);
/// The cache keys some document's deferred stages were deferred with, and how many stages hold each.
static MARKED: Mutex<Option<HashMap<u64, usize>>> = Mutex::new(None);
fn mark(key: u64) {
if key != 0 {
*MARKED.lock().unwrap_or_else(|e| e.into_inner()).get_or_insert_with(HashMap::new).entry(key).or_default() += 1;
}
}
fn unmark(key: u64) {
let mut marked = MARKED.lock().unwrap_or_else(|e| e.into_inner());
let Some(m) = marked.as_mut() else { return };
if let Some(n) = m.get_mut(&key) {
*n -= 1;
if *n == 0 {
m.remove(&key);
}
}
}
/// A run gave a result for cache key `key`: when some document deferred a stage with that key,
/// [`revision`] changes.
pub(crate) fn resolved(key: u64) {
let marked = MARKED.lock().unwrap_or_else(|e| e.into_inner()).as_ref().is_some_and(|m| m.contains_key(&key));
if marked {
REVISION.fetch_add(1, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
/// A clock that reads `step` milliseconds later each time it is read.
fn stepping(step: u64) -> Clock {
let t = Arc::new(AtomicU64::new(0));
Arc::new(move || Some(t.fetch_add(step, Ordering::Relaxed)))
}
#[test]
fn the_budget_starts_with_the_first_run_and_ends_after_it() {
let d = Deferrals::with_clock(stepping(700));
assert!(matches!(d.allowance(), Allowance::Until { ms: 2_000, until: 2_000 }));
assert!(matches!(d.allowance(), Allowance::Until { ms: 1_300, until: 2_000 }));
assert!(matches!(d.allowance(), Allowance::Until { ms: 600, until: 2_000 }));
assert!(matches!(d.allowance(), Allowance::Spent));
d.first_view_done();
assert!(matches!(d.allowance(), Allowance::Unlimited));
}
#[test]
fn without_a_clock_the_first_view_runs_nothing_and_a_refresh_everything() {
let d = Deferrals::with_clock(Arc::new(|| None));
assert!(matches!(d.allowance(), Allowance::Spent));
assert!(d.passed(u64::MAX));
d.first_view_done();
d.defer(Stage::default(), "a", Reason::Budget, 31);
d.refresh(None, |_| {
assert!(matches!(d.allowance(), Allowance::Unlimited));
vec![]
});
}
/// A stage drawn with several inputs (the paths of a group) holds one record for each, and is
/// listed and counted once.
#[test]
fn a_stage_is_listed_and_counted_once() {
let d = Deferrals::with_clock(stepping(1));
let s = Stage { object: 7, effect: 1, item: None };
d.defer(s, "a", Reason::Budget, 11);
d.defer(s, "a", Reason::Budget, 12);
d.defer(s, "a", Reason::Timeout, 12);
d.defer(Stage { item: Some(0), ..s }, "a", Reason::Failed, 13);
assert_eq!(d.list().len(), 2);
assert!(!d.holds(s, 12), "the first view runs deferred stages");
d.first_view_done();
assert!(d.holds(s, 11) && d.holds(s, 12));
assert!(!d.holds(s, 14), "other input");
assert_eq!(d.take_report(), Some(2));
assert_eq!(d.take_report(), None);
d.first_view_done();
assert_eq!(d.take_report(), None, "the first view ends once");
d.ran(s, 11);
assert_eq!(d.list().len(), 2, "the stage is still deferred with its other input");
d.ran(s, 12);
assert_eq!(d.list().len(), 1);
d.defer(s, "a", Reason::Missing, 0);
d.ran(s, 15);
assert_eq!(d.list().len(), 1, "a record without a key goes with any result");
}
/// A refresh runs the deferred stages of the objects asked for with a fresh budget, and counts
/// what became of them; the stages its drawing did not reach stay as they were. Another
/// thread draws the document as if no refresh ran.
#[test]
fn a_refresh_counts_what_became_of_each_stage() {
let d = Deferrals::with_clock(stepping(1));
d.first_view_done();
let st = |object, effect| Stage { object, effect, item: None };
let stages = [
(st(1, 0), Reason::Budget),
(st(1, 1), Reason::Budget),
(st(1, 2), Reason::Budget),
(st(1, 3), Reason::Timeout),
(st(2, 0), Reason::Budget),
(st(3, 0), Reason::Failed),
];
for (stage, reason) in stages {
d.defer(stage, "a", reason, 40 + stage.object * 4 + stage.effect as u64);
}
let seen = std::cell::RefCell::new(vec![]);
let counts = d.refresh(Some(&[1, 3, 9]), |ids| {
seen.borrow_mut().extend_from_slice(ids);
assert!(matches!(d.allowance(), Allowance::Until { .. }), "a fresh budget");
assert!(d.holds(st(2, 0), 48), "an object not asked for stays deferred");
d.visit(st(2, 0));
d.visit(st(1, 0));
d.visit(st(1, 1));
d.defer(st(1, 1), "a", Reason::Budget, 45);
std::thread::scope(|scope| {
let other = scope.spawn(|| (matches!(d.allowance(), Allowance::Unlimited), d.holds(st(1, 2), 46), d.retries(st(3, 0), 52)));
assert_eq!(other.join().unwrap(), (true, true, false), "another thread");
});
d.visit(st(1, 1));
assert_eq!(d.list().iter().filter(|x| x.stage == st(1, 1)).count(), 1, "a stage is reached once");
d.visit(st(3, 0));
d.defer(st(3, 0), "a", Reason::Failed, 52);
ids.to_vec()
});
assert_eq!(*seen.borrow(), [1, 3]);
// st(1, 3) timed out before and was not reached: still deferred, not failed again.
assert_eq!(counts, Refreshed { refreshed: 1, still_deferred: 3, failed: 1 });
assert!(matches!(d.allowance(), Allowance::Unlimited), "the budget ends with the refresh");
let left = d.list().iter().map(|x| x.stage).collect::<Vec<_>>();
assert_eq!(left, [st(1, 1), st(1, 2), st(1, 3), st(2, 0), st(3, 0)]);
// An object the drawing did not reach whole keeps the inputs it did not reach: a reached
// stage of it gives a result only for the inputs that ran.
d.defer(st(1, 1), "a", Reason::Budget, 70);
let counts = d.refresh(Some(&[1]), |_| {
d.visit(st(1, 1));
d.ran(st(1, 1), 45);
vec![]
});
assert_eq!(counts, Refreshed { refreshed: 0, still_deferred: 3, failed: 0 });
let keys = |stage| d.state().deferred.iter().filter(|x| x.stage == stage).map(|x| x.key).collect::<Vec<_>>();
assert_eq!(keys(st(1, 1)), [70]);
let counts = d.refresh(Some(&[1]), |_| {
d.visit(st(1, 1));
vec![1]
});
assert_eq!(counts, Refreshed { refreshed: 1, still_deferred: 2, failed: 0 }, "drawn whole: an input it no longer has");
assert_eq!(keys(st(1, 1)), Vec::<u64>::new());
d.retain(|stage, _| stage.object != 1);
assert_eq!(d.list().iter().map(|x| x.stage).collect::<Vec<_>>(), [st(2, 0), st(3, 0)]);
}
/// After the first view, a stage drawn with several inputs (the paths of a group) whose one
/// changed input runs keeps the records of the inputs the same drawing left deferred, and drops
/// the record of the input it no longer has.
#[test]
fn a_stage_that_runs_keeps_the_inputs_its_drawing_left_deferred() {
let d = Deferrals::with_clock(stepping(1));
let s = Stage { object: 4, effect: 0, item: None };
let other = Stage { object: 5, ..s };
for key in [61, 62] {
d.defer(s, "a", Reason::Budget, key);
}
d.defer(other, "a", Reason::Budget, 63);
d.first_view_done();
d.view(|| {
assert!(d.holds(s, 61), "an unchanged path");
assert!(!d.holds(s, 64), "the changed path");
d.ran(s, 64);
});
let keys = d.state().deferred.iter().map(|x| (x.stage.object, x.key)).collect::<Vec<_>>();
assert_eq!(keys, [(4, 61), (5, 63)]);
// A changed input that fails again replaces the record of the input it no longer has.
d.view(|| {
assert!(!d.holds(other, 65));
d.defer(other, "a", Reason::Failed, 65);
});
let left = d.state().deferred.iter().map(|x| (x.stage.object, x.key, x.reason)).collect::<Vec<_>>();
assert_eq!(left, [(4, 61, Reason::Budget), (5, 65, Reason::Failed)]);
}
#[test]
fn a_result_for_a_deferred_key_changes_the_revision() {
const KEY: u64 = 0xdef0_0001;
let marked = || MARKED.lock().unwrap_or_else(|e| e.into_inner()).as_ref().is_some_and(|m| m.contains_key(&KEY));
let d = Deferrals::with_clock(stepping(1));
assert!(!marked());
d.defer(Stage::default(), "a", Reason::Budget, KEY);
let other = d.clone();
other.defer(Stage { object: 2, ..Stage::default() }, "a", Reason::Budget, KEY);
assert!(marked());
let before = revision();
resolved(KEY);
assert!(revision() > before);
other.ran(Stage::default(), KEY);
assert!(marked(), "another stage holds the key");
drop((d, other));
assert!(!marked(), "the document closed");
}
}