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