Skip to main content

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}