1use std::time::Duration;
2use web_time::Instant;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub struct ComponentId(pub u64);
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct AtomId(pub u64);
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct RequestId(pub u64);
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[repr(u8)]
20pub enum LogLevel {
21 Error = 0,
22 Warn = 1,
23 Info = 2,
24 Debug = 3,
25 Trace = 4,
26}
27
28impl LogLevel {
29 pub fn label(self) -> &'static str {
31 match self {
32 LogLevel::Error => "ERROR",
33 LogLevel::Warn => "WARN ",
34 LogLevel::Info => "INFO ",
35 LogLevel::Debug => "DEBUG",
36 LogLevel::Trace => "TRACE",
37 }
38 }
39
40 pub fn ansi(self) -> &'static str {
42 match self {
43 LogLevel::Error => "\x1b[1;31m", LogLevel::Warn => "\x1b[33m", LogLevel::Info => "\x1b[32m", LogLevel::Debug => "\x1b[36m", LogLevel::Trace => "\x1b[2;37m", }
49 }
50}
51
52#[derive(Debug, Clone)]
54pub struct Location {
55 pub file: &'static str,
56 pub line: u32,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq)]
61pub struct Size {
62 pub width: f32,
63 pub height: f32,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq)]
68pub struct Point {
69 pub x: f32,
70 pub y: f32,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq)]
75pub struct Rect {
76 pub origin: Point,
77 pub size: Size,
78}
79
80#[derive(Debug, Clone)]
84pub struct TraceConstraints {
85 pub min_width: f32,
86 pub max_width: Option<f32>,
87 pub min_height: f32,
88 pub max_height: Option<f32>,
89}
90
91#[derive(Debug, Clone)]
93pub enum TraceValue {
94 Debug(String),
96 Opaque,
98}
99
100#[derive(Debug, Clone)]
102pub enum RebuildCause {
103 AtomChanged(AtomId),
105 ParentRebuilt,
107 PropsChanged,
109 Manual,
111}
112
113#[derive(Debug, Clone)]
115pub struct Route(pub String);
116
117#[derive(Debug, Clone)]
119pub struct Transition(pub String);
120
121#[derive(Debug, Clone)]
123pub enum Method {
124 Get,
125 Post,
126 Put,
127 Delete,
128 Patch,
129 Other(String),
130}
131
132#[derive(Debug, Clone)]
134pub enum GestureKind {
135 Tap,
136 LongPress,
137 Drag,
138 Swipe,
139 Pinch,
140 Scroll,
141}
142
143#[derive(Debug, Clone)]
148pub enum RosaceTrace {
149 ComponentMount {
151 id: ComponentId,
152 name: &'static str,
153 location: Location,
154 },
155 ComponentUnmount {
157 id: ComponentId,
158 name: &'static str,
159 },
160 ComponentRebuild {
162 id: ComponentId,
163 cause: RebuildCause,
164 duration: Duration,
165 },
166 AtomRead {
168 atom: AtomId,
169 component: ComponentId,
170 },
171 AtomWrite {
173 atom: AtomId,
174 old: TraceValue,
175 new: TraceValue,
176 by: ComponentId,
177 location: Location,
178 },
179 LayoutStart {
181 component: ComponentId,
182 constraints: TraceConstraints,
183 },
184 LayoutEnd {
186 component: ComponentId,
187 size: Size,
188 duration: Duration,
189 },
190 FrameStart {
192 frame: u64,
193 timestamp: Instant,
194 },
195 FrameEnd {
197 frame: u64,
198 duration: Duration,
199 dropped: bool,
201 },
202 PaintRegion {
204 rect: Rect,
205 },
206 RouteChange {
208 from: Option<Route>,
209 to: Route,
210 transition: Transition,
211 },
212 RequestStart {
214 id: RequestId,
215 url: String,
216 method: Method,
217 component: ComponentId,
218 },
219 RequestEnd {
221 id: RequestId,
222 status: u16,
223 duration: Duration,
224 cached: bool,
225 size: usize,
226 },
227 FfiCall {
229 fn_name: &'static str,
230 duration: Duration,
231 },
232 FfiError {
234 fn_name: &'static str,
235 error: String,
236 },
237 GestureReceived {
239 kind: GestureKind,
240 handler: ComponentId,
241 },
242 ShaderRegister {
246 pipeline: u64,
247 wgsl_len: usize,
248 },
249 Log {
255 level: LogLevel,
256 target: &'static str,
258 message: String,
259 timestamp: Instant,
260 },
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
267pub enum TraceCategory {
268 State,
270 Lifecycle,
272 Layout,
274 Frame,
276 Render,
278 Route,
280 Network,
282 Ffi,
284 Gesture,
286 Shader,
288 Log,
290}
291
292impl RosaceTrace {
293 pub fn category(&self) -> TraceCategory {
295 match self {
296 RosaceTrace::ComponentMount { .. }
297 | RosaceTrace::ComponentUnmount { .. }
298 | RosaceTrace::ComponentRebuild { .. } => TraceCategory::Lifecycle,
299 RosaceTrace::AtomRead { .. } | RosaceTrace::AtomWrite { .. } => TraceCategory::State,
300 RosaceTrace::LayoutStart { .. } | RosaceTrace::LayoutEnd { .. } => TraceCategory::Layout,
301 RosaceTrace::FrameStart { .. } | RosaceTrace::FrameEnd { .. } => TraceCategory::Frame,
302 RosaceTrace::PaintRegion { .. } => TraceCategory::Render,
303 RosaceTrace::RouteChange { .. } => TraceCategory::Route,
304 RosaceTrace::RequestStart { .. } | RosaceTrace::RequestEnd { .. } => TraceCategory::Network,
305 RosaceTrace::FfiCall { .. } | RosaceTrace::FfiError { .. } => TraceCategory::Ffi,
306 RosaceTrace::GestureReceived { .. } => TraceCategory::Gesture,
307 RosaceTrace::ShaderRegister { .. } => TraceCategory::Shader,
308 RosaceTrace::Log { .. } => TraceCategory::Log,
309 }
310 }
311
312 pub fn is_high_frequency(&self) -> bool {
320 matches!(
321 self,
322 RosaceTrace::AtomRead { .. }
323 | RosaceTrace::FrameStart { .. }
324 | RosaceTrace::FrameEnd { .. }
325 | RosaceTrace::PaintRegion { .. }
326 | RosaceTrace::LayoutStart { .. }
327 | RosaceTrace::LayoutEnd { .. }
328 )
329 }
330}
331
332#[cfg(test)]
333mod category_tests {
334 use super::*;
335
336 #[test]
337 fn atom_read_is_high_frequency_state() {
338 let e = RosaceTrace::AtomRead {
339 atom: AtomId(1),
340 component: ComponentId(1),
341 };
342 assert_eq!(e.category(), TraceCategory::State);
343 assert!(e.is_high_frequency(), "AtomRead is the loudest event — must be high-frequency");
344 }
345
346 #[test]
347 fn atom_write_is_state_but_not_high_frequency() {
348 let e = RosaceTrace::AtomWrite {
349 atom: AtomId(1),
350 old: TraceValue::Opaque,
351 new: TraceValue::Opaque,
352 by: ComponentId(1),
353 location: crate::location!(),
354 };
355 assert_eq!(e.category(), TraceCategory::State);
356 assert!(!e.is_high_frequency(), "a state CHANGE is meaningful, not per-frame noise");
357 }
358
359 #[test]
360 fn frame_and_paint_events_are_high_frequency() {
361 let frame = RosaceTrace::FrameStart { frame: 0, timestamp: std::time::Instant::now() };
362 assert!(frame.is_high_frequency());
363 assert_eq!(frame.category(), TraceCategory::Frame);
364 }
365
366 #[test]
367 fn network_and_lifecycle_are_meaningful() {
368 let mount = RosaceTrace::ComponentMount {
369 id: ComponentId(1), name: "X", location: crate::location!(),
370 };
371 assert_eq!(mount.category(), TraceCategory::Lifecycle);
372 assert!(!mount.is_high_frequency());
373 }
374}