use std::time::Duration;
use web_time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ComponentId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AtomId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RequestId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum LogLevel {
Error = 0,
Warn = 1,
Info = 2,
Debug = 3,
Trace = 4,
}
impl LogLevel {
pub fn label(self) -> &'static str {
match self {
LogLevel::Error => "ERROR",
LogLevel::Warn => "WARN ",
LogLevel::Info => "INFO ",
LogLevel::Debug => "DEBUG",
LogLevel::Trace => "TRACE",
}
}
pub fn ansi(self) -> &'static str {
match self {
LogLevel::Error => "\x1b[1;31m", LogLevel::Warn => "\x1b[33m", LogLevel::Info => "\x1b[32m", LogLevel::Debug => "\x1b[36m", LogLevel::Trace => "\x1b[2;37m", }
}
}
#[derive(Debug, Clone)]
pub struct Location {
pub file: &'static str,
pub line: u32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Size {
pub width: f32,
pub height: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Rect {
pub origin: Point,
pub size: Size,
}
#[derive(Debug, Clone)]
pub struct TraceConstraints {
pub min_width: f32,
pub max_width: Option<f32>,
pub min_height: f32,
pub max_height: Option<f32>,
}
#[derive(Debug, Clone)]
pub enum TraceValue {
Debug(String),
Opaque,
}
#[derive(Debug, Clone)]
pub enum RebuildCause {
AtomChanged(AtomId),
ParentRebuilt,
PropsChanged,
Manual,
}
#[derive(Debug, Clone)]
pub struct Route(pub String);
#[derive(Debug, Clone)]
pub struct Transition(pub String);
#[derive(Debug, Clone)]
pub enum Method {
Get,
Post,
Put,
Delete,
Patch,
Other(String),
}
#[derive(Debug, Clone)]
pub enum GestureKind {
Tap,
LongPress,
Drag,
Swipe,
Pinch,
Scroll,
}
#[derive(Debug, Clone)]
pub enum RosaceTrace {
ComponentMount {
id: ComponentId,
name: &'static str,
location: Location,
},
ComponentUnmount {
id: ComponentId,
name: &'static str,
},
ComponentRebuild {
id: ComponentId,
cause: RebuildCause,
duration: Duration,
},
AtomRead {
atom: AtomId,
component: ComponentId,
},
AtomWrite {
atom: AtomId,
old: TraceValue,
new: TraceValue,
by: ComponentId,
location: Location,
},
LayoutStart {
component: ComponentId,
constraints: TraceConstraints,
},
LayoutEnd {
component: ComponentId,
size: Size,
duration: Duration,
},
FrameStart {
frame: u64,
timestamp: Instant,
},
FrameEnd {
frame: u64,
duration: Duration,
dropped: bool,
},
PaintRegion {
rect: Rect,
},
RouteChange {
from: Option<Route>,
to: Route,
transition: Transition,
},
RequestStart {
id: RequestId,
url: String,
method: Method,
component: ComponentId,
},
RequestEnd {
id: RequestId,
status: u16,
duration: Duration,
cached: bool,
size: usize,
},
FfiCall {
fn_name: &'static str,
duration: Duration,
},
FfiError {
fn_name: &'static str,
error: String,
},
GestureReceived {
kind: GestureKind,
handler: ComponentId,
},
ShaderRegister {
pipeline: u64,
wgsl_len: usize,
},
Log {
level: LogLevel,
target: &'static str,
message: String,
timestamp: Instant,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TraceCategory {
State,
Lifecycle,
Layout,
Frame,
Render,
Route,
Network,
Ffi,
Gesture,
Shader,
Log,
}
impl RosaceTrace {
pub fn category(&self) -> TraceCategory {
match self {
RosaceTrace::ComponentMount { .. }
| RosaceTrace::ComponentUnmount { .. }
| RosaceTrace::ComponentRebuild { .. } => TraceCategory::Lifecycle,
RosaceTrace::AtomRead { .. } | RosaceTrace::AtomWrite { .. } => TraceCategory::State,
RosaceTrace::LayoutStart { .. } | RosaceTrace::LayoutEnd { .. } => TraceCategory::Layout,
RosaceTrace::FrameStart { .. } | RosaceTrace::FrameEnd { .. } => TraceCategory::Frame,
RosaceTrace::PaintRegion { .. } => TraceCategory::Render,
RosaceTrace::RouteChange { .. } => TraceCategory::Route,
RosaceTrace::RequestStart { .. } | RosaceTrace::RequestEnd { .. } => TraceCategory::Network,
RosaceTrace::FfiCall { .. } | RosaceTrace::FfiError { .. } => TraceCategory::Ffi,
RosaceTrace::GestureReceived { .. } => TraceCategory::Gesture,
RosaceTrace::ShaderRegister { .. } => TraceCategory::Shader,
RosaceTrace::Log { .. } => TraceCategory::Log,
}
}
pub fn is_high_frequency(&self) -> bool {
matches!(
self,
RosaceTrace::AtomRead { .. }
| RosaceTrace::FrameStart { .. }
| RosaceTrace::FrameEnd { .. }
| RosaceTrace::PaintRegion { .. }
| RosaceTrace::LayoutStart { .. }
| RosaceTrace::LayoutEnd { .. }
)
}
}
#[cfg(test)]
mod category_tests {
use super::*;
#[test]
fn atom_read_is_high_frequency_state() {
let e = RosaceTrace::AtomRead {
atom: AtomId(1),
component: ComponentId(1),
};
assert_eq!(e.category(), TraceCategory::State);
assert!(e.is_high_frequency(), "AtomRead is the loudest event — must be high-frequency");
}
#[test]
fn atom_write_is_state_but_not_high_frequency() {
let e = RosaceTrace::AtomWrite {
atom: AtomId(1),
old: TraceValue::Opaque,
new: TraceValue::Opaque,
by: ComponentId(1),
location: crate::location!(),
};
assert_eq!(e.category(), TraceCategory::State);
assert!(!e.is_high_frequency(), "a state CHANGE is meaningful, not per-frame noise");
}
#[test]
fn frame_and_paint_events_are_high_frequency() {
let frame = RosaceTrace::FrameStart { frame: 0, timestamp: std::time::Instant::now() };
assert!(frame.is_high_frequency());
assert_eq!(frame.category(), TraceCategory::Frame);
}
#[test]
fn network_and_lifecycle_are_meaningful() {
let mount = RosaceTrace::ComponentMount {
id: ComponentId(1), name: "X", location: crate::location!(),
};
assert_eq!(mount.category(), TraceCategory::Lifecycle);
assert!(!mount.is_high_frequency());
}
}