Skip to main content

kui_core/runtime/
cause.rs

1//! Why a frame was drawn: two readings a view can take of
2//! the frame it is building, for an app that keeps a ledger of its frames
3//! and wants to know why one ran with nothing on screen moving.
4//!
5//! - [`FrameCause`], the set of reasons that reached the window since the
6//!   last frame began. The core records the input it was handed
7//!   ([`Core::handle_input`]) and whether the last frame left one owed;
8//!   the driver adds what only it sees ([`Core::note_frame_cause`]) — a
9//!   wake, a resize, the caret's blink, a surface retry, the OS events
10//!   that never become input. Always kept: it is a few ORs a frame.
11//! - [`OwedBy`], *who* holds a frame the last one left owed: the nodes
12//!   whose transition is mid-flight, the cycles running, the departures
13//!   playing, the containers easing a scroll, the held drag's scroller,
14//!   the `animate` nodes, and where `request_frame` was called from.
15//!   [`Core::owed`] with names. Off unless [`Core::set_frame_trace`] asked
16//!   for it, and taken at the start of `begin_frame` — the only moment the
17//!   last frame's tree, which names the holders, and the stores' state,
18//!   which says who they are, are both still there.
19//!
20//! The same switch keeps a digest of every finished frame's display list,
21//! so [`Core::frame_unchanged`] says whether a frame drew exactly what the
22//! one before it drew: a frame that changed nothing on screen.
23
24use std::panic::Location;
25
26use rustc_hash::FxHashMap;
27
28use super::*;
29use crate::input::InputEvent;
30
31/// Why a frame was drawn: every reason that reached the window between
32/// the start of the last frame and the start of this one.
33/// Read from a view as [`Core::frame_cause`]. A set, because a frame
34/// answers everything that asked since the last one — a keystroke and the
35/// caret's blink, a wheel and the transition it started.
36///
37/// Empty is a frame nobody here asked for: the platform's own redraw (an
38/// expose, a live resize the OS paints through), or a driver that notes
39/// nothing.
40#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
41pub struct FrameCause(u32);
42
43impl FrameCause {
44    /// Nothing recorded.
45    pub const NONE: FrameCause = FrameCause(0);
46
47    // The input the core was handed (`Core::handle_input`), by kind —
48    // every driver's, recorded by the core itself.
49    /// The pointer moved over the window.
50    pub const POINTER_MOVE: FrameCause = FrameCause(1 << 0);
51    /// The pointer left the window.
52    pub const POINTER_LEAVE: FrameCause = FrameCause(1 << 1);
53    /// A mouse button went down or up, or a force click.
54    pub const BUTTON: FrameCause = FrameCause(1 << 2);
55    /// A wheel or a trackpad scroll — a swipe's momentum included, which
56    /// the platform sends as scrolls after the hand has left.
57    pub const WHEEL: FrameCause = FrameCause(1 << 3);
58    /// A key went down or up.
59    pub const KEY: FrameCause = FrameCause(1 << 4);
60    /// The modifier keys changed.
61    pub const MODIFIERS: FrameCause = FrameCause(1 << 5);
62    /// Text arrived: typed, committed by an input method, or pasted.
63    pub const TEXT: FrameCause = FrameCause(1 << 6);
64    /// An input method's composition changed.
65    pub const PREEDIT: FrameCause = FrameCause(1 << 7);
66    /// Assistive technology asked for an action.
67    pub const ACCESS: FrameCause = FrameCause(1 << 8);
68    /// Files dragged over the window, dropped on it, or taken away.
69    pub const FILE_DRAG: FrameCause = FrameCause(1 << 9);
70    /// A file dialog answered, or the OS handed the app documents to open
71    /// (`InputEvent::Open`, backlog F124).
72    pub const FILES: FrameCause = FrameCause(1 << 10);
73
74    // What the driver saw (`Core::note_frame_cause`).
75    /// The window's first frame.
76    pub const FIRST: FrameCause = FrameCause(1 << 11);
77    /// The app woke the loop from another thread (a `Waker`).
78    pub const WAKE: FrameCause = FrameCause(1 << 12);
79    /// The host asked for a frame: a pumped runner's `request_redraw`,
80    /// a new view handed over.
81    pub const HOST: FrameCause = FrameCause(1 << 13);
82    /// The window changed size.
83    pub const RESIZE: FrameCause = FrameCause(1 << 14);
84    /// The window moved to a display of another scale.
85    pub const SCALE: FrameCause = FrameCause(1 << 15);
86    /// The window gained or lost the keyboard.
87    pub const FOCUS: FrameCause = FrameCause(1 << 16);
88    /// The window was uncovered, or came back from minimized. The runner
89    /// leaves out of that frame what it noted while the window could not
90    /// draw and no frame took — another window's input, an appearance
91    /// change — so a restore does not report reasons from long before;
92    /// `resize` is the same edge on Windows, where
93    /// restoring is a resize.
94    pub const OCCLUSION: FrameCause = FrameCause(1 << 17);
95    /// The system's appearance or settings changed: light or dark, the
96    /// accent, reduced motion, assistive technology coming or going, a
97    /// font installed or removed.
98    pub const APPEARANCE: FrameCause = FrameCause(1 << 18);
99    /// The caret's blink changed phase.
100    pub const CARET: FrameCause = FrameCause(1 << 19);
101    /// The last frame's surface refused it (skipped, out of date,
102    /// misconfigured) and the frame is tried again.
103    pub const RETRY: FrameCause = FrameCause(1 << 20);
104    /// A frame held for the display waited too long and is drawn anyway.
105    pub const OVERDUE: FrameCause = FrameCause(1 << 21);
106    /// The graphics device was reopened.
107    pub const DEVICE: FrameCause = FrameCause(1 << 22);
108    /// The last frame's own events — a resize, what a new layout put
109    /// under a still pointer — were routed after it, and answered.
110    pub const AFTER_FRAME: FrameCause = FrameCause(1 << 23);
111    /// Something that reached the app in another window, which can change
112    /// what this one shows.
113    pub const ELSEWHERE: FrameCause = FrameCause(1 << 24);
114    /// A menu (the platform's context menu or menu bar) was answered.
115    pub const MENU: FrameCause = FrameCause(1 << 25);
116    /// A sound finished or was refused, and its event was routed.
117    pub const AUDIO: FrameCause = FrameCause(1 << 26);
118    /// A smoke run's frame counter asked for the next frame.
119    pub const SMOKE: FrameCause = FrameCause(1 << 27);
120
121    // The core's own.
122    /// The last frame left one owed ([`Core::owed`]); [`Core::owed_by`]
123    /// says who, when traced.
124    pub const OWED: FrameCause = FrameCause(1 << 28);
125
126    /// Each reason and its name, in bit order.
127    pub const ALL: [(FrameCause, &'static str); 29] = [
128        (Self::POINTER_MOVE, "pointerMove"),
129        (Self::POINTER_LEAVE, "pointerLeave"),
130        (Self::BUTTON, "button"),
131        (Self::WHEEL, "wheel"),
132        (Self::KEY, "key"),
133        (Self::MODIFIERS, "modifiers"),
134        (Self::TEXT, "text"),
135        (Self::PREEDIT, "preedit"),
136        (Self::ACCESS, "access"),
137        (Self::FILE_DRAG, "fileDrag"),
138        (Self::FILES, "files"),
139        (Self::FIRST, "first"),
140        (Self::WAKE, "wake"),
141        (Self::HOST, "host"),
142        (Self::RESIZE, "resize"),
143        (Self::SCALE, "scale"),
144        (Self::FOCUS, "focus"),
145        (Self::OCCLUSION, "occlusion"),
146        (Self::APPEARANCE, "appearance"),
147        (Self::CARET, "caret"),
148        (Self::RETRY, "retry"),
149        (Self::OVERDUE, "overdue"),
150        (Self::DEVICE, "device"),
151        (Self::AFTER_FRAME, "afterFrame"),
152        (Self::ELSEWHERE, "elsewhere"),
153        (Self::MENU, "menu"),
154        (Self::AUDIO, "audio"),
155        (Self::SMOKE, "smoke"),
156        (Self::OWED, "owed"),
157    ];
158
159    /// The bits, for a binding or a log.
160    pub const fn bits(self) -> u32 {
161        self.0
162    }
163
164    /// The set these bits spell; bits no reason has are dropped.
165    pub const fn from_bits(bits: u32) -> FrameCause {
166        FrameCause(bits & ((1 << Self::ALL.len()) - 1))
167    }
168
169    pub const fn is_empty(self) -> bool {
170        self.0 == 0
171    }
172
173    /// Whether every reason in `other` is in this set.
174    pub const fn contains(self, other: FrameCause) -> bool {
175        self.0 & other.0 == other.0
176    }
177
178    /// Whether any reason in `other` is in this set.
179    pub const fn intersects(self, other: FrameCause) -> bool {
180        self.0 & other.0 != 0
181    }
182
183    pub const fn union(self, other: FrameCause) -> FrameCause {
184        FrameCause(self.0 | other.0)
185    }
186
187    /// The names of the reasons in the set, in bit order.
188    pub fn names(self) -> impl Iterator<Item = &'static str> {
189        Self::ALL
190            .into_iter()
191            .filter(move |(c, _)| self.contains(*c))
192            .map(|(_, name)| name)
193    }
194
195    /// The reason one input is: what [`Core::handle_input`] records, and
196    /// what a driver that screens input before the core sees it can note
197    /// for an event it keeps.
198    pub fn of_input(ev: &InputEvent) -> FrameCause {
199        match ev {
200            InputEvent::CursorMoved(_) => Self::POINTER_MOVE,
201            InputEvent::CursorLeft => Self::POINTER_LEAVE,
202            InputEvent::MouseDown { .. } | InputEvent::MouseUp { .. } => Self::BUTTON,
203            InputEvent::ForceClick(_) => Self::BUTTON,
204            InputEvent::Scroll(_) | InputEvent::ScrollGesture { .. } => Self::WHEEL,
205            InputEvent::Key(..) | InputEvent::KeyDown(_) | InputEvent::KeyUp(_) => Self::KEY,
206            InputEvent::Modifiers(_) => Self::MODIFIERS,
207            InputEvent::Text(_) | InputEvent::Commit(_) | InputEvent::Paste { .. } => Self::TEXT,
208            InputEvent::Preedit(..) => Self::PREEDIT,
209            InputEvent::Access(_) => Self::ACCESS,
210            InputEvent::DragFiles { .. }
211            | InputEvent::DropFiles { .. }
212            | InputEvent::DragCancel => Self::FILE_DRAG,
213            InputEvent::Files(_) | InputEvent::Open(_) => Self::FILES,
214        }
215    }
216}
217
218impl std::ops::BitOr for FrameCause {
219    type Output = FrameCause;
220    fn bitor(self, rhs: FrameCause) -> FrameCause {
221        self.union(rhs)
222    }
223}
224
225impl std::ops::BitOrAssign for FrameCause {
226    fn bitor_assign(&mut self, rhs: FrameCause) {
227        self.0 |= rhs.0;
228    }
229}
230
231impl std::fmt::Debug for FrameCause {
232    /// `FrameCause(key|caret)`, `FrameCause()` when empty.
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        write!(f, "FrameCause(")?;
235        for (i, name) in self.names().enumerate() {
236            if i > 0 {
237                write!(f, "|")?;
238            }
239            write!(f, "{name}")?;
240        }
241        write!(f, ")")
242    }
243}
244
245/// One thing that holds an owed frame, named for a person: see
246/// [`OwedBy`].
247#[derive(Clone, Debug, PartialEq, Eq)]
248pub struct FrameHolder {
249    pub key: Key,
250    /// The node's labels from the root, `/`-joined — the ones it and its
251    /// ancestors were opened under (`open_keyed`, a `key` prop), which is
252    /// the name the devtools' tree shows a node by. A node opened without
253    /// one ends in its accessible name in quotes, or `#` and the low
254    /// eight hex digits of its key, as the devtools' event stream writes
255    /// an unlabelled node. A departure's node is in no tree, so it is its
256    /// own label alone, when a frame still knows it.
257    pub name: String,
258    /// A transition's slots still mid-flight — `width`, `height`, `bg`,
259    /// `borderColor`, `radius`, `position`, `opacity`, `shadow`,
260    /// `shadowColor` — in that order. Empty for every other holder.
261    pub slots: Vec<&'static str>,
262}
263
264/// Where a frame was asked for: a [`Core::request_frame`] call, or one the
265/// core makes for itself.
266#[derive(Clone, Copy, Debug, PartialEq, Eq)]
267pub struct FrameRequest {
268    /// `"request_frame"` for a call to [`Core::request_frame`] or
269    /// `Ui::request_frame` — the app's or a binding's, which `at` tells
270    /// apart. A door that asks for the frame that lands it is named for
271    /// itself, `at` its caller's line: `"reveal"`,
272    /// `"reveal_label"`, `"set_scroll"`, `"set_scroll_label"`,
273    /// `"focus_region"`, `"focus_region_by_label"`,
274    /// `"request_focus_step"` (`Ui::focus_next`, `Ui::focus_prev`),
275    /// `"request_files"`. The core's and its widgets' own asks say what
276    /// they are for: `"scrollbar fade"`, `"atlas full"`,
277    /// `"long line rows"`, `"resliced"`, `"devtools"`,
278    /// `"list first frame"`.
279    pub why: &'static str,
280    /// The source line that asked (`#[track_caller]`): the app's own for
281    /// its calls, a binding's for a guest's, kui's for its own asks.
282    pub at: &'static Location<'static>,
283}
284
285/// Who holds the frame the last one left owed: [`Core::owed`], named.
286/// Each list is empty when its kind in [`crate::Owed`] is
287/// false, and names what made it true when it is.
288///
289/// Read with [`Core::owed_by`] from inside a view, where it describes the
290/// frame before — the reason the frame being built exists — or between
291/// frames, where it still does. Empty unless [`Core::set_frame_trace`]
292/// turned the record on before that frame began.
293#[derive(Clone, Debug, Default, PartialEq, Eq)]
294pub struct OwedBy {
295    /// Nodes with a transition mid-flight, each with its slots
296    /// (`Owed::transition`).
297    pub transitions: Vec<FrameHolder>,
298    /// Nodes with a keyframe cycle running (`Owed::cycle`).
299    pub cycles: Vec<FrameHolder>,
300    /// Subtrees still playing their exit (`Owed::depart`).
301    pub departures: Vec<FrameHolder>,
302    /// Scroll containers easing a programmatic offset change
303    /// (`Owed::scroll`).
304    pub scrolls: Vec<FrameHolder>,
305    /// The container a held drag is scrolling (`Owed::autoscroll`).
306    pub autoscroll: Option<FrameHolder>,
307    /// Nodes declaring `animate` (half of `Owed::requested`).
308    pub animate: Vec<FrameHolder>,
309    /// Where the frame was asked for (the other half of
310    /// `Owed::requested`), one per calling line, in the order first
311    /// asked, at most [`OwedBy::REQUESTS`] of them.
312    pub requests: Vec<FrameRequest>,
313}
314
315impl OwedBy {
316    /// How many distinct asking lines a frame keeps.
317    pub const REQUESTS: usize = 32;
318
319    /// Nothing held.
320    pub fn is_empty(&self) -> bool {
321        self.transitions.is_empty()
322            && self.cycles.is_empty()
323            && self.departures.is_empty()
324            && self.scrolls.is_empty()
325            && self.autoscroll.is_none()
326            && self.animate.is_empty()
327            && self.requests.is_empty()
328    }
329
330    fn clear(&mut self) {
331        self.transitions.clear();
332        self.cycles.clear();
333        self.departures.clear();
334        self.scrolls.clear();
335        self.autoscroll = None;
336        self.animate.clear();
337        self.requests.clear();
338    }
339}
340
341/// The core's side of the trace: what [`Core::frame_cause`] and
342/// [`Core::owed_by`] read, and what fills them.
343#[derive(Default)]
344pub(crate) struct Trace {
345    /// `set_frame_trace`: the holders and the digest are kept.
346    on: bool,
347    /// The frame being built's reasons (the last frame's, between frames).
348    cause: FrameCause,
349    /// What has reached the window since this frame began: the next
350    /// frame's reasons so far.
351    since: FrameCause,
352    /// Who held the frame being built, as its `begin_frame` found them.
353    owed_by: OwedBy,
354    /// The lines that asked for a frame since the last one began.
355    requests: Vec<FrameRequest>,
356    /// The last finished frame's display-list digest, and whether it
357    /// matched the one before.
358    digest: Option<u64>,
359    unchanged: Option<bool>,
360    /// The departures' names, taken by the exit diff from the frame that
361    /// still had them: a ghost's node is in no tree after that frame.
362    gone: FxHashMap<Key, String>,
363    /// [`Core::begin_frame_cause`] took the next frame's reasons ahead of
364    /// its `begin_frame`, which keeps them.
365    begun: bool,
366}
367
368impl Trace {
369    fn ask(&mut self, why: &'static str, at: &'static Location<'static>) {
370        if self.requests.len() < OwedBy::REQUESTS
371            && !self.requests.iter().any(|r| r.at == at && r.why == why)
372        {
373            self.requests.push(FrameRequest { why, at });
374        }
375    }
376}
377
378impl Core {
379    /// Turns the trace of why frames run on or off: who
380    /// holds each owed frame ([`Self::owed_by`]) and whether each frame
381    /// changed what is drawn ([`Self::frame_unchanged`]). Off by default,
382    /// where neither costs anything; on, the holders are taken at the
383    /// start of every frame the last one owed — a walk of what is owed
384    /// and one of the last frame's tree to name it — and the display
385    /// list is hashed at the end of every frame. [`Self::frame_cause`]
386    /// is kept either way.
387    pub fn set_frame_trace(&mut self, on: bool) {
388        let t = &mut self.trace;
389        t.on = on;
390        if !on {
391            t.owed_by.clear();
392            t.requests.clear();
393            t.digest = None;
394            t.unchanged = None;
395            t.gone.clear();
396        }
397    }
398
399    /// Whether [`Self::set_frame_trace`] turned the trace on.
400    pub fn frame_trace(&self) -> bool {
401        self.trace.on
402    }
403
404    /// Why the frame being built runs: every reason that reached the
405    /// window between the start of the last frame and the start of this
406    /// one. Between frames, the last frame's — or, after
407    /// [`Self::begin_frame_cause`], the next one's. See [`FrameCause`].
408    pub fn frame_cause(&self) -> FrameCause {
409        self.trace.cause
410    }
411
412    /// Starts the next frame's record now rather than at its
413    /// `begin_frame`: its reasons ([`Self::frame_cause`]) and, traced,
414    /// who holds it ([`Self::owed_by`]) — for a driver whose view runs
415    /// before the frame it is for begins. Node's loop runs
416    /// `view` to a tree and only then hands the tree to a frame, so a
417    /// view reading either would read the frame before; the loop calls
418    /// this first, and the view reads the frame it is building. The
419    /// `begin_frame` that follows keeps what this took, and a second
420    /// call before it is nothing. What reaches the window in between —
421    /// input, a note — is the frame after's, as it is during a build.
422    pub fn begin_frame_cause(&mut self) {
423        if !self.trace.begun {
424            self.take_frame_cause();
425            self.trace.begun = true;
426        }
427    }
428
429    /// Adds to the next frame's reasons — the driver's door, for what it
430    /// saw and the core never will: a wake, a resize, a blink, a retry,
431    /// an OS event it kept. The input it hands [`Self::handle_input`] is
432    /// recorded without this.
433    pub fn note_frame_cause(&mut self, cause: FrameCause) {
434        self.trace.since |= cause;
435    }
436
437    /// Who held the frame the last one left owed, as the frame being
438    /// built found them (see [`OwedBy`]) — or, after
439    /// [`Self::begin_frame_cause`], as the next one will. Empty when the
440    /// trace is off.
441    pub fn owed_by(&self) -> &OwedBy {
442        &self.trace.owed_by
443    }
444
445    /// Whether the last finished frame drew exactly what the one before
446    /// it drew — the same quads, clips, fragments and textures at the
447    /// same size and scale — so it changed nothing on screen. `None`
448    /// while the trace is off and for the first frame after it came on.
449    /// A frame that draws a fragment is never unchanged: the shader reads
450    /// the clock. What the glyph atlas holds is not compared, only where
451    /// the quads sample it.
452    pub fn frame_unchanged(&self) -> Option<bool> {
453        self.trace.unchanged
454    }
455
456    /// Asks for the next frame on the core's own behalf — `why` says
457    /// what for, in a trace.
458    #[track_caller]
459    pub(crate) fn owe_frame(&mut self, why: &'static str) {
460        self.taint_kept("a widget in it owes a frame");
461        self.frame_requested = true;
462        if self.trace.on {
463            self.trace.ask(why, Location::caller());
464        }
465    }
466
467    /// `request_frame`'s half of the trace.
468    #[track_caller]
469    pub(super) fn trace_request(&mut self) {
470        if self.trace.on {
471            self.trace.ask("request_frame", Location::caller());
472        }
473    }
474
475    /// `begin_frame`'s reset of `frame_requested`, mirrored.
476    pub(super) fn trace_forget_requests(&mut self) {
477        self.trace.requests.clear();
478    }
479
480    /// One input's reason, from [`Self::handle_input`].
481    pub(super) fn trace_input(&mut self, ev: &InputEvent) {
482        self.trace.since |= FrameCause::of_input(ev);
483    }
484
485    /// The start of `begin_frame`, before anything there moves: the
486    /// reasons gathered since the last frame began become this frame's,
487    /// and — traced — the holders of what the last frame owed are named
488    /// against its tree, which `begin_frame` is about to clear.
489    pub(super) fn trace_begin_frame(&mut self) {
490        // Taken already, ahead of a view that ran before this frame did.
491        if std::mem::take(&mut self.trace.begun) {
492            return;
493        }
494        self.take_frame_cause();
495    }
496
497    /// The record itself: [`Self::trace_begin_frame`]'s, or
498    /// [`Self::begin_frame_cause`]'s ahead of it.
499    fn take_frame_cause(&mut self) {
500        let owed = self.owed();
501        let mut cause = std::mem::take(&mut self.trace.since);
502        if owed.any() {
503            cause |= FrameCause::OWED;
504        }
505        self.trace.cause = cause;
506        if !self.trace.on {
507            return;
508        }
509        let mut by = std::mem::take(&mut self.trace.owed_by);
510        by.clear();
511        std::mem::swap(&mut by.requests, &mut self.trace.requests);
512        self.trace.requests.clear();
513        if owed.any() {
514            self.name_holders(owed, &mut by);
515        } else {
516            by.requests.clear();
517        }
518        self.trace.owed_by = by;
519        if !self.trace.gone.is_empty() {
520            let held: rustc_hash::FxHashSet<Key> = self.depart.keys().collect();
521            self.trace.gone.retain(|k, _| held.contains(k));
522        }
523    }
524
525    /// The exit diff's roots, as indices of `prev_tree` — the frame that
526    /// declared them — named against that frame, traced.
527    pub(super) fn trace_departures(&mut self, roots: &[usize]) {
528        if !self.trace.on {
529            return;
530        }
531        let names = Names::new(&self.prev_tree, &self.key_labels_last);
532        for &i in roots {
533            let key = self.prev_tree.keys[i];
534            let name = names.holder(key, None).name;
535            self.trace.gone.insert(key, name);
536        }
537    }
538
539    fn name_holders(&self, owed: crate::Owed, by: &mut OwedBy) {
540        let names = Names::new(&self.tree, &self.key_labels);
541        if owed.transition {
542            let mut slots: FxHashMap<Key, u16> = FxHashMap::default();
543            self.anim.owing(|key, slot| {
544                *slots.entry(key).or_default() |= 1 << slot as u16;
545            });
546            let mut held: Vec<(usize, FrameHolder)> = slots
547                .into_iter()
548                .map(|(key, mask)| {
549                    let mut h = names.holder(key, None);
550                    h.slots = crate::anim::Slot::names(mask);
551                    (names.order(key), h)
552                })
553                .collect();
554            held.sort_by_key(|(at, _)| *at);
555            by.transitions = held.into_iter().map(|(_, h)| h).collect();
556        }
557        if owed.cycle && self.anim.time().is_some() {
558            for i in 0..self.tree.len() {
559                let spec = &self.tree.specs[i];
560                // A finite cycle that is over holds no frame (F133).
561                if let Some(t) = spec.transition
562                    && t.duration_ms > 0.0
563                    && !spec.anim().keyframes.is_empty()
564                    && self
565                        .anim
566                        .cycle_running(self.tree.keys[i], t, spec.anim().iterations)
567                {
568                    by.cycles.push(names.holder(self.tree.keys[i], None));
569                }
570            }
571        }
572        if owed.depart {
573            for (key, spec) in self.depart.roots() {
574                let mut h = names.holder(key, Some(spec));
575                if let Some(name) = self.trace.gone.get(&key) {
576                    h.name.clone_from(name);
577                }
578                by.departures.push(h);
579            }
580        }
581        if owed.scroll {
582            let mut keys: Vec<Key> = self.scroll.easing().collect();
583            keys.sort_by_key(|k| names.order(*k));
584            by.scrolls = keys.into_iter().map(|k| names.holder(k, None)).collect();
585        }
586        if owed.autoscroll {
587            by.autoscroll = self.autoscroller().map(|k| names.holder(k, None));
588        }
589        if self.tree.any_animate {
590            for i in 0..self.tree.len() {
591                if self.tree.specs[i].animate {
592                    by.animate.push(names.holder(self.tree.keys[i], None));
593                }
594            }
595        }
596        if !owed.requested {
597            by.requests.clear();
598        }
599    }
600
601    /// The end of `finish_frame`, traced: this frame's display list
602    /// against the last one's.
603    pub(super) fn trace_finish_frame(&mut self) {
604        if !self.trace.on {
605            return;
606        }
607        let digest = digest(&self.display);
608        self.trace.unchanged = self.trace.digest.map(|d| d == digest);
609        self.trace.digest = Some(digest);
610    }
611}
612
613/// The last frame's tree and labels, indexed once for naming holders.
614struct Names<'a> {
615    tree: &'a Tree,
616    at: FxHashMap<Key, u32>,
617    labels: FxHashMap<Key, &'a str>,
618}
619
620impl<'a> Names<'a> {
621    fn new(tree: &'a Tree, labels: &'a crate::key::LabelIndex) -> Self {
622        Names {
623            tree,
624            at: tree
625                .keys
626                .iter()
627                .enumerate()
628                .map(|(i, k)| (*k, i as u32))
629                .collect(),
630            labels: labels.iter().collect(),
631        }
632    }
633
634    /// Where `key` sits in tree order, for sorting; past the end when it
635    /// is in no tree.
636    fn order(&self, key: Key) -> usize {
637        self.at.get(&key).map_or(usize::MAX, |i| *i as usize)
638    }
639
640    fn holder(&self, key: Key, gone: Option<&NodeSpec>) -> FrameHolder {
641        let mut parts: Vec<&str> = Vec::new();
642        let spec = match self.at.get(&key) {
643            Some(&i) => {
644                let mut p = self.tree.parent[i as usize];
645                while p != NIL {
646                    if let Some(l) = self.labels.get(&self.tree.keys[p as usize]) {
647                        parts.push(l);
648                    }
649                    p = self.tree.parent[p as usize];
650                }
651                parts.reverse();
652                Some(&self.tree.specs[i as usize])
653            }
654            None => gone,
655        };
656        let own = match self.labels.get(&key) {
657            Some(l) => l.to_string(),
658            None => match spec.and_then(|s| s.access().label.as_deref()) {
659                Some(l) => format!("\"{l}\""),
660                None => format!("#{:08x}", key.0 as u32),
661            },
662        };
663        let mut name = String::new();
664        for p in parts {
665            name.push_str(p);
666            name.push('/');
667        }
668        name.push_str(&own);
669        FrameHolder {
670            key,
671            name,
672            slots: Vec::new(),
673        }
674    }
675}
676
677/// A digest of what a display list draws: every quad, clip, fragment and
678/// texture draw, at its viewport and scale. Not the dropped handles (a
679/// backend's bookkeeping) and not the pixels behind a texture beyond its
680/// revision.
681fn digest(dl: &crate::display::DisplayList) -> u64 {
682    use std::hash::{Hash, Hasher};
683    let mut h = rustc_hash::FxHasher::default();
684    let f = |h: &mut rustc_hash::FxHasher, v: f32| h.write_u32(v.to_bits());
685    f(&mut h, dl.viewport.w);
686    f(&mut h, dl.viewport.h);
687    f(&mut h, dl.scale);
688    h.write_usize(dl.quads.len());
689    for q in &dl.quads {
690        for v in [q.rect.x, q.rect.y, q.rect.w, q.rect.h] {
691            f(&mut h, v);
692        }
693        for c in [q.color, q.border_color] {
694            for v in [c.r, c.g, c.b, c.a] {
695                f(&mut h, v);
696            }
697        }
698        for v in q.radius {
699            f(&mut h, v);
700        }
701        f(&mut h, q.border_w);
702        f(&mut h, q.blur);
703        h.write_u32(q.kind as u32);
704        h.write_u32(q.clip);
705        for w in q.uv {
706            h.write_u32(w);
707        }
708    }
709    h.write_usize(dl.clips.len());
710    for c in &dl.clips {
711        for v in [c.rect.x, c.rect.y, c.rect.w, c.rect.h] {
712            f(&mut h, v);
713        }
714        for v in c.radius {
715            f(&mut h, v);
716        }
717    }
718    h.write_usize(dl.fragments.len());
719    if !dl.fragments.is_empty() {
720        // A fragment reads the clock, so its frame is its own.
721        f(&mut h, dl.time);
722        for fr in &dl.fragments {
723            fr.id.hash(&mut h);
724            for v in fr.params {
725                f(&mut h, v);
726            }
727            fr.image.hash(&mut h);
728        }
729    }
730    h.write_usize(dl.textures.len());
731    for (t, px) in dl.textures.iter().zip(&dl.texture_pixels) {
732        t.id.hash(&mut h);
733        for w in t.uv {
734            h.write_u32(w);
735        }
736        h.write_u32(px.rev);
737    }
738    h.finish()
739}