use std::panic::Location;
use rustc_hash::FxHashMap;
use super::*;
use crate::input::InputEvent;
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct FrameCause(u32);
impl FrameCause {
pub const NONE: FrameCause = FrameCause(0);
pub const POINTER_MOVE: FrameCause = FrameCause(1 << 0);
pub const POINTER_LEAVE: FrameCause = FrameCause(1 << 1);
pub const BUTTON: FrameCause = FrameCause(1 << 2);
pub const WHEEL: FrameCause = FrameCause(1 << 3);
pub const KEY: FrameCause = FrameCause(1 << 4);
pub const MODIFIERS: FrameCause = FrameCause(1 << 5);
pub const TEXT: FrameCause = FrameCause(1 << 6);
pub const PREEDIT: FrameCause = FrameCause(1 << 7);
pub const ACCESS: FrameCause = FrameCause(1 << 8);
pub const FILE_DRAG: FrameCause = FrameCause(1 << 9);
pub const FILES: FrameCause = FrameCause(1 << 10);
pub const FIRST: FrameCause = FrameCause(1 << 11);
pub const WAKE: FrameCause = FrameCause(1 << 12);
pub const HOST: FrameCause = FrameCause(1 << 13);
pub const RESIZE: FrameCause = FrameCause(1 << 14);
pub const SCALE: FrameCause = FrameCause(1 << 15);
pub const FOCUS: FrameCause = FrameCause(1 << 16);
pub const OCCLUSION: FrameCause = FrameCause(1 << 17);
pub const APPEARANCE: FrameCause = FrameCause(1 << 18);
pub const CARET: FrameCause = FrameCause(1 << 19);
pub const RETRY: FrameCause = FrameCause(1 << 20);
pub const OVERDUE: FrameCause = FrameCause(1 << 21);
pub const DEVICE: FrameCause = FrameCause(1 << 22);
pub const AFTER_FRAME: FrameCause = FrameCause(1 << 23);
pub const ELSEWHERE: FrameCause = FrameCause(1 << 24);
pub const MENU: FrameCause = FrameCause(1 << 25);
pub const AUDIO: FrameCause = FrameCause(1 << 26);
pub const SMOKE: FrameCause = FrameCause(1 << 27);
pub const OWED: FrameCause = FrameCause(1 << 28);
pub const ALL: [(FrameCause, &'static str); 29] = [
(Self::POINTER_MOVE, "pointerMove"),
(Self::POINTER_LEAVE, "pointerLeave"),
(Self::BUTTON, "button"),
(Self::WHEEL, "wheel"),
(Self::KEY, "key"),
(Self::MODIFIERS, "modifiers"),
(Self::TEXT, "text"),
(Self::PREEDIT, "preedit"),
(Self::ACCESS, "access"),
(Self::FILE_DRAG, "fileDrag"),
(Self::FILES, "files"),
(Self::FIRST, "first"),
(Self::WAKE, "wake"),
(Self::HOST, "host"),
(Self::RESIZE, "resize"),
(Self::SCALE, "scale"),
(Self::FOCUS, "focus"),
(Self::OCCLUSION, "occlusion"),
(Self::APPEARANCE, "appearance"),
(Self::CARET, "caret"),
(Self::RETRY, "retry"),
(Self::OVERDUE, "overdue"),
(Self::DEVICE, "device"),
(Self::AFTER_FRAME, "afterFrame"),
(Self::ELSEWHERE, "elsewhere"),
(Self::MENU, "menu"),
(Self::AUDIO, "audio"),
(Self::SMOKE, "smoke"),
(Self::OWED, "owed"),
];
pub const fn bits(self) -> u32 {
self.0
}
pub const fn from_bits(bits: u32) -> FrameCause {
FrameCause(bits & ((1 << Self::ALL.len()) - 1))
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn contains(self, other: FrameCause) -> bool {
self.0 & other.0 == other.0
}
pub const fn intersects(self, other: FrameCause) -> bool {
self.0 & other.0 != 0
}
pub const fn union(self, other: FrameCause) -> FrameCause {
FrameCause(self.0 | other.0)
}
pub fn names(self) -> impl Iterator<Item = &'static str> {
Self::ALL
.into_iter()
.filter(move |(c, _)| self.contains(*c))
.map(|(_, name)| name)
}
pub fn of_input(ev: &InputEvent) -> FrameCause {
match ev {
InputEvent::CursorMoved(_) => Self::POINTER_MOVE,
InputEvent::CursorLeft => Self::POINTER_LEAVE,
InputEvent::MouseDown { .. } | InputEvent::MouseUp { .. } => Self::BUTTON,
InputEvent::ForceClick(_) => Self::BUTTON,
InputEvent::Scroll(_) | InputEvent::ScrollGesture { .. } => Self::WHEEL,
InputEvent::Key(..) | InputEvent::KeyDown(_) | InputEvent::KeyUp(_) => Self::KEY,
InputEvent::Modifiers(_) => Self::MODIFIERS,
InputEvent::Text(_) | InputEvent::Commit(_) | InputEvent::Paste { .. } => Self::TEXT,
InputEvent::Preedit(..) => Self::PREEDIT,
InputEvent::Access(_) => Self::ACCESS,
InputEvent::DragFiles { .. }
| InputEvent::DropFiles { .. }
| InputEvent::DragCancel => Self::FILE_DRAG,
InputEvent::Files(_) | InputEvent::Open(_) => Self::FILES,
}
}
}
impl std::ops::BitOr for FrameCause {
type Output = FrameCause;
fn bitor(self, rhs: FrameCause) -> FrameCause {
self.union(rhs)
}
}
impl std::ops::BitOrAssign for FrameCause {
fn bitor_assign(&mut self, rhs: FrameCause) {
self.0 |= rhs.0;
}
}
impl std::fmt::Debug for FrameCause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "FrameCause(")?;
for (i, name) in self.names().enumerate() {
if i > 0 {
write!(f, "|")?;
}
write!(f, "{name}")?;
}
write!(f, ")")
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FrameHolder {
pub key: Key,
pub name: String,
pub slots: Vec<&'static str>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FrameRequest {
pub why: &'static str,
pub at: &'static Location<'static>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OwedBy {
pub transitions: Vec<FrameHolder>,
pub cycles: Vec<FrameHolder>,
pub departures: Vec<FrameHolder>,
pub scrolls: Vec<FrameHolder>,
pub autoscroll: Option<FrameHolder>,
pub animate: Vec<FrameHolder>,
pub requests: Vec<FrameRequest>,
}
impl OwedBy {
pub const REQUESTS: usize = 32;
pub fn is_empty(&self) -> bool {
self.transitions.is_empty()
&& self.cycles.is_empty()
&& self.departures.is_empty()
&& self.scrolls.is_empty()
&& self.autoscroll.is_none()
&& self.animate.is_empty()
&& self.requests.is_empty()
}
fn clear(&mut self) {
self.transitions.clear();
self.cycles.clear();
self.departures.clear();
self.scrolls.clear();
self.autoscroll = None;
self.animate.clear();
self.requests.clear();
}
}
#[derive(Default)]
pub(crate) struct Trace {
on: bool,
cause: FrameCause,
since: FrameCause,
owed_by: OwedBy,
requests: Vec<FrameRequest>,
digest: Option<u64>,
unchanged: Option<bool>,
gone: FxHashMap<Key, String>,
begun: bool,
}
impl Trace {
fn ask(&mut self, why: &'static str, at: &'static Location<'static>) {
if self.requests.len() < OwedBy::REQUESTS
&& !self.requests.iter().any(|r| r.at == at && r.why == why)
{
self.requests.push(FrameRequest { why, at });
}
}
}
impl Core {
pub fn set_frame_trace(&mut self, on: bool) {
let t = &mut self.trace;
t.on = on;
if !on {
t.owed_by.clear();
t.requests.clear();
t.digest = None;
t.unchanged = None;
t.gone.clear();
}
}
pub fn frame_trace(&self) -> bool {
self.trace.on
}
pub fn frame_cause(&self) -> FrameCause {
self.trace.cause
}
pub fn begin_frame_cause(&mut self) {
if !self.trace.begun {
self.take_frame_cause();
self.trace.begun = true;
}
}
pub fn note_frame_cause(&mut self, cause: FrameCause) {
self.trace.since |= cause;
}
pub fn owed_by(&self) -> &OwedBy {
&self.trace.owed_by
}
pub fn frame_unchanged(&self) -> Option<bool> {
self.trace.unchanged
}
#[track_caller]
pub(crate) fn owe_frame(&mut self, why: &'static str) {
self.frame_requested = true;
if self.trace.on {
self.trace.ask(why, Location::caller());
}
}
#[track_caller]
pub(super) fn trace_request(&mut self) {
if self.trace.on {
self.trace.ask("request_frame", Location::caller());
}
}
pub(super) fn trace_forget_requests(&mut self) {
self.trace.requests.clear();
}
pub(super) fn trace_input(&mut self, ev: &InputEvent) {
self.trace.since |= FrameCause::of_input(ev);
}
pub(super) fn trace_begin_frame(&mut self) {
if std::mem::take(&mut self.trace.begun) {
return;
}
self.take_frame_cause();
}
fn take_frame_cause(&mut self) {
let owed = self.owed();
let mut cause = std::mem::take(&mut self.trace.since);
if owed.any() {
cause |= FrameCause::OWED;
}
self.trace.cause = cause;
if !self.trace.on {
return;
}
let mut by = std::mem::take(&mut self.trace.owed_by);
by.clear();
std::mem::swap(&mut by.requests, &mut self.trace.requests);
self.trace.requests.clear();
if owed.any() {
self.name_holders(owed, &mut by);
} else {
by.requests.clear();
}
self.trace.owed_by = by;
if !self.trace.gone.is_empty() {
let held: rustc_hash::FxHashSet<Key> = self.depart.keys().collect();
self.trace.gone.retain(|k, _| held.contains(k));
}
}
pub(super) fn trace_departures(&mut self, roots: &[usize]) {
if !self.trace.on {
return;
}
let names = Names::new(&self.prev_tree, &self.key_labels_last);
for &i in roots {
let key = self.prev_tree.keys[i];
let name = names.holder(key, None).name;
self.trace.gone.insert(key, name);
}
}
fn name_holders(&self, owed: crate::Owed, by: &mut OwedBy) {
let names = Names::new(&self.tree, &self.key_labels);
if owed.transition {
let mut slots: FxHashMap<Key, u16> = FxHashMap::default();
self.anim.owing(|key, slot| {
*slots.entry(key).or_default() |= 1 << slot as u16;
});
let mut held: Vec<(usize, FrameHolder)> = slots
.into_iter()
.map(|(key, mask)| {
let mut h = names.holder(key, None);
h.slots = crate::anim::Slot::names(mask);
(names.order(key), h)
})
.collect();
held.sort_by_key(|(at, _)| *at);
by.transitions = held.into_iter().map(|(_, h)| h).collect();
}
if owed.cycle && self.anim.time().is_some() {
for i in 0..self.tree.len() {
let spec = &self.tree.specs[i];
if spec.transition.is_some_and(|t| t.duration_ms > 0.0)
&& !spec.anim().keyframes.is_empty()
{
by.cycles.push(names.holder(self.tree.keys[i], None));
}
}
}
if owed.depart {
for (key, spec) in self.depart.roots() {
let mut h = names.holder(key, Some(spec));
if let Some(name) = self.trace.gone.get(&key) {
h.name.clone_from(name);
}
by.departures.push(h);
}
}
if owed.scroll {
let mut keys: Vec<Key> = self.scroll.easing().collect();
keys.sort_by_key(|k| names.order(*k));
by.scrolls = keys.into_iter().map(|k| names.holder(k, None)).collect();
}
if owed.autoscroll {
by.autoscroll = self.autoscroller().map(|k| names.holder(k, None));
}
if self.tree.any_animate {
for i in 0..self.tree.len() {
if self.tree.specs[i].animate {
by.animate.push(names.holder(self.tree.keys[i], None));
}
}
}
if !owed.requested {
by.requests.clear();
}
}
pub(super) fn trace_finish_frame(&mut self) {
if !self.trace.on {
return;
}
let digest = digest(&self.display);
self.trace.unchanged = self.trace.digest.map(|d| d == digest);
self.trace.digest = Some(digest);
}
}
struct Names<'a> {
tree: &'a Tree,
at: FxHashMap<Key, u32>,
labels: FxHashMap<Key, &'a str>,
}
impl<'a> Names<'a> {
fn new(tree: &'a Tree, labels: &'a crate::key::LabelIndex) -> Self {
Names {
tree,
at: tree
.keys
.iter()
.enumerate()
.map(|(i, k)| (*k, i as u32))
.collect(),
labels: labels.iter().collect(),
}
}
fn order(&self, key: Key) -> usize {
self.at.get(&key).map_or(usize::MAX, |i| *i as usize)
}
fn holder(&self, key: Key, gone: Option<&NodeSpec>) -> FrameHolder {
let mut parts: Vec<&str> = Vec::new();
let spec = match self.at.get(&key) {
Some(&i) => {
let mut p = self.tree.parent[i as usize];
while p != NIL {
if let Some(l) = self.labels.get(&self.tree.keys[p as usize]) {
parts.push(l);
}
p = self.tree.parent[p as usize];
}
parts.reverse();
Some(&self.tree.specs[i as usize])
}
None => gone,
};
let own = match self.labels.get(&key) {
Some(l) => l.to_string(),
None => match spec.and_then(|s| s.access().label.as_deref()) {
Some(l) => format!("\"{l}\""),
None => format!("#{:08x}", key.0 as u32),
},
};
let mut name = String::new();
for p in parts {
name.push_str(p);
name.push('/');
}
name.push_str(&own);
FrameHolder {
key,
name,
slots: Vec::new(),
}
}
}
fn digest(dl: &crate::display::DisplayList) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = rustc_hash::FxHasher::default();
let f = |h: &mut rustc_hash::FxHasher, v: f32| h.write_u32(v.to_bits());
f(&mut h, dl.viewport.w);
f(&mut h, dl.viewport.h);
f(&mut h, dl.scale);
h.write_usize(dl.quads.len());
for q in &dl.quads {
for v in [q.rect.x, q.rect.y, q.rect.w, q.rect.h] {
f(&mut h, v);
}
for c in [q.color, q.border_color] {
for v in [c.r, c.g, c.b, c.a] {
f(&mut h, v);
}
}
for v in q.radius {
f(&mut h, v);
}
f(&mut h, q.border_w);
f(&mut h, q.blur);
h.write_u32(q.kind as u32);
h.write_u32(q.clip);
for w in q.uv {
h.write_u32(w);
}
}
h.write_usize(dl.clips.len());
for c in &dl.clips {
for v in [c.rect.x, c.rect.y, c.rect.w, c.rect.h] {
f(&mut h, v);
}
for v in c.radius {
f(&mut h, v);
}
}
h.write_usize(dl.fragments.len());
if !dl.fragments.is_empty() {
f(&mut h, dl.time);
for fr in &dl.fragments {
fr.id.hash(&mut h);
for v in fr.params {
f(&mut h, v);
}
fr.image.hash(&mut h);
}
}
h.write_usize(dl.textures.len());
for (t, px) in dl.textures.iter().zip(&dl.texture_pixels) {
t.id.hash(&mut h);
for w in t.uv {
h.write_u32(w);
}
h.write_u32(px.rev);
}
h.finish()
}