rosace_trace/bus.rs
1use std::sync::{Arc, Mutex};
2
3use crate::event::RosaceTrace;
4
5/// Receives `RosaceTrace` events from the `TracingBus`.
6///
7/// Implement this trait to create a custom subscriber (console output, ring
8/// buffer, file dump, IDE bridge, etc.).
9///
10/// Implementations must be `Send + Sync` — the bus may be called from any thread.
11/// Implementations must not call `TRACING_BUS.emit()` from within `on_trace` to
12/// avoid re-entrant locking.
13pub trait TraceSubscriber: Send + Sync {
14 /// Called for every emitted `RosaceTrace` event.
15 fn on_trace(&self, event: &RosaceTrace);
16}
17
18/// Central hub that receives `RosaceTrace` events and dispatches to all
19/// registered `TraceSubscriber` implementations.
20///
21/// Access via the `TRACING_BUS` global singleton. The bus is zero-cost in
22/// production — all `trace!()` call sites are stripped by `#[cfg(debug_assertions)]`.
23pub struct TracingBus {
24 subscribers: Mutex<Vec<Arc<dyn TraceSubscriber + Send + Sync>>>,
25}
26
27impl Default for TracingBus {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl TracingBus {
34 /// Creates a new bus with no subscribers.
35 pub const fn new() -> Self {
36 Self {
37 subscribers: Mutex::new(Vec::new()),
38 }
39 }
40
41 /// Registers a subscriber to receive all future trace events.
42 pub fn add_subscriber(&self, subscriber: Arc<dyn TraceSubscriber + Send + Sync>) {
43 self.subscribers
44 .lock()
45 .expect("TracingBus subscriber lock poisoned")
46 .push(subscriber);
47 }
48
49 /// Removes all registered subscribers.
50 pub fn clear_subscribers(&self) {
51 self.subscribers
52 .lock()
53 .expect("TracingBus subscriber lock poisoned")
54 .clear();
55 }
56
57 /// Emits a trace event to all registered subscribers.
58 ///
59 /// The subscriber list lock is released before calling any subscriber so that
60 /// subscribers can safely call `add_subscriber` without deadlocking.
61 pub fn emit(&self, event: RosaceTrace) {
62 let subs: Vec<Arc<dyn TraceSubscriber + Send + Sync>> = self
63 .subscribers
64 .lock()
65 .expect("TracingBus subscriber lock poisoned")
66 .clone();
67
68 for sub in &subs {
69 sub.on_trace(&event);
70 }
71 }
72}
73
74/// The global `TracingBus` singleton.
75///
76/// All ROSACE systems emit events through this bus. Access it directly only
77/// when adding subscribers at startup. For emitting events, prefer the `trace!()`
78/// macro which gates emission behind `#[cfg(debug_assertions)]`.
79pub static TRACING_BUS: TracingBus = TracingBus::new();
80
81/// Emits a `RosaceTrace` event — zero cost in production.
82///
83/// In debug builds, forwards the event to `TRACING_BUS`. In release builds,
84/// the entire call is compiled away with no overhead.
85///
86/// # Example
87/// ```rust
88/// use rosace_trace::{trace, event::{RosaceTrace, ComponentId}, location};
89///
90/// trace!(RosaceTrace::ComponentUnmount {
91/// id: ComponentId(1),
92/// name: "MyComponent",
93/// });
94/// ```
95#[macro_export]
96macro_rules! trace {
97 ($event:expr) => {
98 #[cfg(debug_assertions)]
99 $crate::TRACING_BUS.emit($event);
100 };
101}
102
103/// Captures the current source location as a `Location`.
104///
105/// # Example
106/// ```rust
107/// use rosace_trace::location;
108/// let loc = location!();
109/// ```
110#[macro_export]
111macro_rules! location {
112 () => {
113 $crate::event::Location {
114 file: file!(),
115 line: line!(),
116 }
117 };
118}