1use std::any::type_name_of_val;
8use std::cell::RefCell;
9use std::collections::BTreeMap;
10use std::fmt::Debug;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::sync::{Mutex, MutexGuard, OnceLock};
13
14use crate::session::{
15 Edge, EdgeKind, Event, EventKind, FunctionMeta, Node, NodeKind, Session, TypeMeta, ValueSlot,
16};
17
18#[derive(Clone, Debug)]
24struct CallFrame {
25 call_id: u64,
26 node_id: String,
27}
28
29#[derive(Default)]
31struct RuntimeState {
32 title: String,
33 nodes: BTreeMap<String, Node>,
34 edges: BTreeMap<(String, String), Edge>,
35 events: Vec<Event>,
36 last_event_node_id: Option<String>,
37 next_seq: AtomicU64,
38 next_call_id: AtomicU64,
39}
40
41thread_local! {
42 static CALL_STACK: RefCell<Vec<CallFrame>> = const { RefCell::new(Vec::new()) };
43}
44
45static STATE: OnceLock<Mutex<RuntimeState>> = OnceLock::new();
46
47fn state() -> &'static Mutex<RuntimeState> {
48 STATE.get_or_init(|| Mutex::new(RuntimeState::default()))
49}
50
51fn lock_state() -> MutexGuard<'static, RuntimeState> {
52 state().lock().expect("dbgflow-core runtime mutex poisoned")
53}
54
55fn next_seq(state: &RuntimeState) -> u64 {
56 state.next_seq.fetch_add(1, Ordering::Relaxed) + 1
57}
58
59fn next_call_id(state: &RuntimeState) -> u64 {
60 state.next_call_id.fetch_add(1, Ordering::Relaxed) + 1
61}
62
63fn push_event(state: &mut RuntimeState, mut event: Event) {
64 event.seq = next_seq(state);
65 state.last_event_node_id = Some(event.node_id.clone());
66 state.events.push(event);
67}
68
69fn ensure_node(state: &mut RuntimeState, node: Node) {
70 state.nodes.entry(node.id.clone()).or_insert(node);
71}
72
73fn ensure_edge(state: &mut RuntimeState, from: &str, to: &str) {
74 ensure_edge_kind(state, from, to, EdgeKind::ControlFlow, None);
75}
76
77fn ensure_edge_kind(
78 state: &mut RuntimeState,
79 from: &str,
80 to: &str,
81 kind: EdgeKind,
82 label: Option<String>,
83) {
84 if from == to {
85 return;
86 }
87
88 state.edges.insert(
89 (from.to_owned(), to.to_owned()),
90 Edge {
91 from: from.to_owned(),
92 to: to.to_owned(),
93 kind,
94 label,
95 },
96 );
97}
98
99pub fn reset_session(title: impl Into<String>) {
105 let mut state = lock_state();
106 state.title = title.into();
107 state.nodes.clear();
108 state.edges.clear();
109 state.events.clear();
110 state.last_event_node_id = None;
111 state.next_seq.store(0, Ordering::Relaxed);
112 state.next_call_id.store(0, Ordering::Relaxed);
113 CALL_STACK.with(|stack| stack.borrow_mut().clear());
114}
115
116pub fn current_session() -> Session {
118 let state = lock_state();
119 let nodes = state.nodes.values().cloned().collect();
120 let edges = state.edges.values().cloned().collect();
121
122 Session {
123 title: state.title.clone(),
124 nodes,
125 edges,
126 events: state.events.clone(),
127 }
128}
129
130pub fn type_preview<T>(value: &T) -> String {
132 format!("type {}", type_name_of_val(value))
133}
134
135pub trait UiDebugValue: Debug + Sized {
141 fn ui_debug_type_meta() -> TypeMeta;
143
144 fn ui_debug_snapshot(&self) -> String {
146 format!("{self:#?}")
147 }
148
149 fn emit_snapshot(&self, label: impl Into<String>) {
151 runtime::record_type_snapshot(self, label.into());
152 }
153}
154
155pub mod runtime {
161 use super::{
162 CALL_STACK, CallFrame, EdgeKind, Event, EventKind, FunctionMeta, Node, NodeKind,
163 UiDebugValue, ValueSlot, ensure_edge, ensure_edge_kind, ensure_node, lock_state,
164 next_call_id, push_event,
165 };
166
167 #[derive(Debug)]
169 pub struct TraceFrame {
170 call_id: u64,
171 parent_call_id: Option<u64>,
172 node_id: &'static str,
173 finished: bool,
174 }
175
176 impl TraceFrame {
177 pub fn enter(meta: FunctionMeta, values: Vec<ValueSlot>) -> Self {
179 let (call_id, parent_call_id) = {
180 let mut state = lock_state();
181
182 ensure_node(
183 &mut state,
184 Node {
185 id: meta.id.to_owned(),
186 label: meta.label.to_owned(),
187 kind: NodeKind::Function,
188 module_path: meta.module_path.to_owned(),
189 file: meta.file.to_owned(),
190 line: meta.line,
191 source: Some(meta.source.to_owned()),
192 },
193 );
194
195 let call_id = next_call_id(&state);
196 let parent_call_id = CALL_STACK.with(|stack| {
197 let stack = stack.borrow();
198 stack.last().map(|frame| frame.call_id)
199 });
200
201 let parent_node = CALL_STACK.with(|stack| {
202 let stack = stack.borrow();
203 stack.last().map(|frame| frame.node_id.clone())
204 });
205
206 if let Some(parent_node) = parent_node {
207 ensure_edge(&mut state, &parent_node, meta.id);
208 }
209
210 push_event(
211 &mut state,
212 Event {
213 seq: 0,
214 call_id: Some(call_id),
215 parent_call_id,
216 node_id: meta.id.to_owned(),
217 kind: EventKind::FunctionEnter,
218 title: format!("enter {}", meta.label),
219 values,
220 },
221 );
222
223 (call_id, parent_call_id)
224 };
225
226 CALL_STACK.with(|stack| {
227 stack.borrow_mut().push(CallFrame {
228 call_id,
229 node_id: meta.id.to_owned(),
230 });
231 });
232
233 Self {
234 call_id,
235 parent_call_id,
236 node_id: meta.id,
237 finished: false,
238 }
239 }
240
241 pub fn finish_return<T>(&mut self, result: &T) {
243 if self.finished {
244 return;
245 }
246
247 {
248 let mut state = lock_state();
249 push_event(
250 &mut state,
251 Event {
252 seq: 0,
253 call_id: Some(self.call_id),
254 parent_call_id: self.parent_call_id,
255 node_id: self.node_id.to_owned(),
256 kind: EventKind::FunctionExit,
257 title: format!(
258 "return {}",
259 self.node_id.rsplit("::").next().unwrap_or(self.node_id)
260 ),
261 values: vec![ValueSlot {
262 name: "result".to_owned(),
263 preview: super::type_preview(result),
264 }],
265 },
266 );
267 }
268
269 self.finished = true;
270 pop_stack(self.call_id);
271 }
272
273 pub fn finish_error(&mut self, message: impl Into<String>) {
275 if self.finished {
276 return;
277 }
278
279 {
280 let mut state = lock_state();
281 push_event(
282 &mut state,
283 Event {
284 seq: 0,
285 call_id: Some(self.call_id),
286 parent_call_id: self.parent_call_id,
287 node_id: self.node_id.to_owned(),
288 kind: EventKind::FunctionExit,
289 title: format!(
290 "panic {}",
291 self.node_id.rsplit("::").next().unwrap_or(self.node_id)
292 ),
293 values: vec![ValueSlot {
294 name: "status".to_owned(),
295 preview: message.into(),
296 }],
297 },
298 );
299 }
300
301 self.finished = true;
302 pop_stack(self.call_id);
303 }
304 }
305
306 impl Drop for TraceFrame {
307 fn drop(&mut self) {
308 if self.finished {
309 return;
310 }
311
312 self.finish_error("unwound before explicit return");
313 }
314 }
315
316 fn pop_stack(call_id: u64) {
317 CALL_STACK.with(|stack| {
318 let mut stack = stack.borrow_mut();
319 if stack.last().map(|frame| frame.call_id) == Some(call_id) {
320 stack.pop();
321 }
322 });
323 }
324
325 pub fn preview_argument<T>(name: impl Into<String>, value: &T) -> ValueSlot {
327 ValueSlot {
328 name: name.into(),
329 preview: super::type_preview(value),
330 }
331 }
332
333 pub fn record_type_snapshot<T: UiDebugValue>(value: &T, label: impl Into<String>) {
335 let meta = T::ui_debug_type_meta();
336 let label = label.into();
337 let mut state = lock_state();
338
339 ensure_node(
340 &mut state,
341 Node {
342 id: meta.id.to_owned(),
343 label: meta.label.to_owned(),
344 kind: NodeKind::Type,
345 module_path: meta.module_path.to_owned(),
346 file: meta.file.to_owned(),
347 line: meta.line,
348 source: Some(meta.source.to_owned()),
349 },
350 );
351
352 if let Some(parent_node) = CALL_STACK.with(|stack| {
353 let stack = stack.borrow();
354 stack.last().map(|frame| frame.node_id.clone())
355 }) {
356 ensure_edge_kind(
357 &mut state,
358 &parent_node,
359 meta.id,
360 EdgeKind::DataFlow,
361 Some(label.clone()),
362 );
363 }
364
365 push_event(
366 &mut state,
367 Event {
368 seq: 0,
369 call_id: CALL_STACK.with(|stack| {
370 let stack = stack.borrow();
371 stack.last().map(|frame| frame.call_id)
372 }),
373 parent_call_id: None,
374 node_id: meta.id.to_owned(),
375 kind: EventKind::ValueSnapshot,
376 title: label,
377 values: vec![ValueSlot {
378 name: meta.label.to_owned(),
379 preview: value.ui_debug_snapshot(),
380 }],
381 },
382 );
383 }
384
385 pub fn record_test_started(test_name: impl Into<String>, node_id: impl Into<String>) {
387 let test_name = test_name.into();
388 record_test_event(
389 EventKind::TestStarted,
390 test_name.clone(),
391 short_label(&test_name),
392 node_id.into(),
393 Vec::new(),
394 );
395 }
396
397 pub fn record_test_passed(test_name: impl Into<String>, node_id: impl Into<String>) {
399 let test_name = test_name.into();
400 record_test_event(
401 EventKind::TestPassed,
402 test_name.clone(),
403 short_label(&test_name),
404 node_id.into(),
405 Vec::new(),
406 );
407 }
408
409 pub fn record_test_failed(
411 test_name: impl Into<String>,
412 node_id: impl Into<String>,
413 failure: impl Into<String>,
414 ) {
415 let test_name = test_name.into();
416 record_test_event(
417 EventKind::TestFailed,
418 test_name.clone(),
419 short_label(&test_name),
420 node_id.into(),
421 vec![ValueSlot {
422 name: "failure".to_owned(),
423 preview: failure.into(),
424 }],
425 );
426 }
427
428 pub fn latest_node_id() -> Option<String> {
430 let state = lock_state();
431 state.last_event_node_id.clone()
432 }
433
434 pub fn record_test_started_latest(test_name: impl Into<String>) {
436 let test_name = test_name.into();
437 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
438 let label = short_label(&test_name);
439 record_test_event(
440 EventKind::TestStarted,
441 test_name,
442 label,
443 node_id,
444 Vec::new(),
445 );
446 }
447
448 pub fn record_test_passed_latest(test_name: impl Into<String>) {
450 let test_name = test_name.into();
451 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
452 let label = short_label(&test_name);
453 record_test_event(EventKind::TestPassed, test_name, label, node_id, Vec::new());
454 }
455
456 pub fn record_test_failed_latest(test_name: impl Into<String>, failure: impl Into<String>) {
458 let test_name = test_name.into();
459 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
460 let label = short_label(&test_name);
461 record_test_event(
462 EventKind::TestFailed,
463 test_name,
464 label,
465 node_id,
466 vec![ValueSlot {
467 name: "failure".to_owned(),
468 preview: failure.into(),
469 }],
470 );
471 }
472
473 pub fn record_test_started_latest_with_label(
475 test_name: impl Into<String>,
476 label: impl Into<String>,
477 ) {
478 let test_name = test_name.into();
479 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
480 record_test_event(
481 EventKind::TestStarted,
482 test_name,
483 label.into(),
484 node_id,
485 Vec::new(),
486 );
487 }
488
489 pub fn record_test_passed_latest_with_label(
491 test_name: impl Into<String>,
492 label: impl Into<String>,
493 ) {
494 let test_name = test_name.into();
495 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
496 record_test_event(
497 EventKind::TestPassed,
498 test_name,
499 label.into(),
500 node_id,
501 Vec::new(),
502 );
503 }
504
505 pub fn record_test_failed_latest_with_label(
507 test_name: impl Into<String>,
508 label: impl Into<String>,
509 failure: impl Into<String>,
510 ) {
511 let test_name = test_name.into();
512 let node_id = latest_node_id().unwrap_or_else(|| format!("test::{test_name}"));
513 record_test_event(
514 EventKind::TestFailed,
515 test_name,
516 label.into(),
517 node_id,
518 vec![ValueSlot {
519 name: "failure".to_owned(),
520 preview: failure.into(),
521 }],
522 );
523 }
524
525 fn record_test_event(
526 kind: EventKind,
527 test_name: String,
528 label: String,
529 node_id: String,
530 values: Vec<ValueSlot>,
531 ) {
532 let test_id = format!("test::{test_name}");
533 let mut state = lock_state();
534
535 ensure_node(
536 &mut state,
537 Node {
538 id: test_id.clone(),
539 label,
540 kind: NodeKind::Test,
541 module_path: "cargo::test".to_owned(),
542 file: "<runner>".to_owned(),
543 line: 0,
544 source: None,
545 },
546 );
547 ensure_edge_kind(&mut state, &test_id, &node_id, EdgeKind::TestLink, None);
548
549 push_event(
550 &mut state,
551 Event {
552 seq: 0,
553 call_id: None,
554 parent_call_id: None,
555 node_id: test_id,
556 kind,
557 title: short_label(&test_name),
558 values,
559 },
560 );
561 }
562
563 fn short_label(path: &str) -> String {
565 path.rsplit("::").next().unwrap_or(path).to_owned()
566 }
567}