tx2_link/
debug.rs

1use crate::protocol::{Message, MessageType, DeltaChange};
2use crate::serialization::{WorldSnapshot, Delta};
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::env;
5
6static DEBUG_MODE: AtomicBool = AtomicBool::new(false);
7static TRACE_MODE: AtomicBool = AtomicBool::new(false);
8
9/// Initialize debug mode from environment variables
10///
11/// - `TX2_DEBUG=1` or `TX2_DEBUG_JSON=1`: Enable JSON pretty-printing of all messages
12/// - `TX2_TRACE=1`: Enable human-readable trace logging of operations
13pub fn init_debug_mode() {
14    let debug = env::var("TX2_DEBUG").is_ok()
15        || env::var("TX2_DEBUG_JSON").is_ok();
16
17    let trace = env::var("TX2_TRACE").is_ok();
18
19    DEBUG_MODE.store(debug, Ordering::Relaxed);
20    TRACE_MODE.store(trace, Ordering::Relaxed);
21
22    if debug {
23        eprintln!("[TX2-LINK] Debug mode enabled - all messages will be logged as JSON");
24    }
25
26    if trace {
27        eprintln!("[TX2-LINK] Trace mode enabled - human-readable operation logs");
28    }
29}
30
31/// Check if debug mode is enabled
32pub fn is_debug_enabled() -> bool {
33    DEBUG_MODE.load(Ordering::Relaxed)
34}
35
36/// Check if trace mode is enabled
37pub fn is_trace_enabled() -> bool {
38    TRACE_MODE.load(Ordering::Relaxed)
39}
40
41/// Log a message in JSON format if debug mode is enabled
42pub fn log_message(direction: &str, message: &Message) {
43    if !is_debug_enabled() {
44        return;
45    }
46
47    match serde_json::to_string_pretty(message) {
48        Ok(json) => {
49            eprintln!("\n[TX2-LINK] {} Message:\n{}\n", direction, json);
50        }
51        Err(e) => {
52            eprintln!("[TX2-LINK] Failed to serialize message to JSON: {}", e);
53        }
54    }
55}
56
57/// Log a world snapshot in JSON format if debug mode is enabled
58pub fn log_snapshot(label: &str, snapshot: &WorldSnapshot) {
59    if !is_debug_enabled() {
60        return;
61    }
62
63    match serde_json::to_string_pretty(snapshot) {
64        Ok(json) => {
65            eprintln!("\n[TX2-LINK] {} Snapshot ({} entities):\n{}\n",
66                label, snapshot.entities.len(), json);
67        }
68        Err(e) => {
69            eprintln!("[TX2-LINK] Failed to serialize snapshot to JSON: {}", e);
70        }
71    }
72}
73
74/// Log a delta in JSON format if debug mode is enabled
75pub fn log_delta(label: &str, delta: &Delta) {
76    if !is_debug_enabled() {
77        return;
78    }
79
80    match serde_json::to_string_pretty(delta) {
81        Ok(json) => {
82            let change_count = delta.changes.len();
83
84            eprintln!("\n[TX2-LINK] {} Delta ({} changes):\n{}\n",
85                label, change_count, json);
86        }
87        Err(e) => {
88            eprintln!("[TX2-LINK] Failed to serialize delta to JSON: {}", e);
89        }
90    }
91}
92
93/// Trace a delta in human-readable format if trace mode is enabled
94pub fn trace_delta(delta: &Delta) {
95    if !is_trace_enabled() {
96        return;
97    }
98
99    eprintln!("[TX2-LINK] Delta Summary:");
100    eprintln!("  Timestamp: {} (base: {})", delta.timestamp, delta.base_timestamp);
101    eprintln!("  Total changes: {}", delta.changes.len());
102
103    let mut entities_added = 0;
104    let mut entities_removed = 0;
105    let mut components_added = 0;
106    let mut components_removed = 0;
107    let mut components_modified = 0;
108
109    for change in &delta.changes {
110        match change {
111            DeltaChange::EntityAdded { .. } => entities_added += 1,
112            DeltaChange::EntityRemoved { .. } => entities_removed += 1,
113            DeltaChange::ComponentAdded { .. } => components_added += 1,
114            DeltaChange::ComponentRemoved { .. } => components_removed += 1,
115            DeltaChange::ComponentUpdated { .. } => components_modified += 1,
116            DeltaChange::FieldsUpdated { .. } => components_modified += 1,
117        }
118    }
119
120    if entities_added > 0 {
121        eprintln!("  + {} entities added", entities_added);
122    }
123    if entities_removed > 0 {
124        eprintln!("  - {} entities removed", entities_removed);
125    }
126    if components_added > 0 {
127        eprintln!("  + {} components added", components_added);
128    }
129    if components_removed > 0 {
130        eprintln!("  - {} components removed", components_removed);
131    }
132    if components_modified > 0 {
133        eprintln!("  ~ {} components modified", components_modified);
134    }
135
136    eprintln!();
137}
138
139/// Trace a serialization operation
140pub fn trace_serialization(format: &str, size_bytes: usize, duration_micros: u128) {
141    if !is_trace_enabled() {
142        return;
143    }
144
145    eprintln!("[TX2-LINK] Serialized {} bytes using {} in {}µs",
146        size_bytes, format, duration_micros);
147}
148
149/// Trace a deserialization operation
150pub fn trace_deserialization(format: &str, size_bytes: usize, duration_micros: u128) {
151    if !is_trace_enabled() {
152        return;
153    }
154
155    eprintln!("[TX2-LINK] Deserialized {} bytes using {} in {}µs",
156        size_bytes, format, duration_micros);
157}
158
159/// Trace a delta compression operation
160pub fn trace_compression(original_size: usize, delta_size: usize, duration_micros: u128) {
161    if !is_trace_enabled() {
162        return;
163    }
164
165    let ratio = if delta_size > 0 {
166        original_size as f64 / delta_size as f64
167    } else {
168        0.0
169    };
170
171    eprintln!("[TX2-LINK] Delta compression: {} bytes → {} bytes ({:.2}× reduction) in {}µs",
172        original_size, delta_size, ratio, duration_micros);
173}
174
175/// Trace a rate limit check
176pub fn trace_rate_limit(allowed: bool, current_rate: f64, limit: f64) {
177    if !is_trace_enabled() {
178        return;
179    }
180
181    let status = if allowed { "ALLOWED" } else { "BLOCKED" };
182    eprintln!("[TX2-LINK] Rate limit check: {} (current: {:.1}/s, limit: {:.1}/s)",
183        status, current_rate, limit);
184}
185
186/// Trace a transport operation
187pub fn trace_transport_send(bytes: usize, destination: &str) {
188    if !is_trace_enabled() {
189        return;
190    }
191
192    eprintln!("[TX2-LINK] → Sent {} bytes to {}", bytes, destination);
193}
194
195/// Trace a transport receive
196pub fn trace_transport_receive(bytes: usize, source: &str) {
197    if !is_trace_enabled() {
198        return;
199    }
200
201    eprintln!("[TX2-LINK] ← Received {} bytes from {}", bytes, source);
202}
203
204/// Format bytes in human-readable format (KB, MB, etc.)
205pub fn format_bytes(bytes: usize) -> String {
206    const KB: usize = 1024;
207    const MB: usize = KB * 1024;
208    const GB: usize = MB * 1024;
209
210    if bytes >= GB {
211        format!("{:.2} GB", bytes as f64 / GB as f64)
212    } else if bytes >= MB {
213        format!("{:.2} MB", bytes as f64 / MB as f64)
214    } else if bytes >= KB {
215        format!("{:.2} KB", bytes as f64 / KB as f64)
216    } else {
217        format!("{} bytes", bytes)
218    }
219}
220
221/// Create a debug summary of a message
222pub fn message_summary(message: &Message) -> String {
223    match message.header.msg_type {
224        MessageType::Snapshot => {
225            format!("Snapshot (seq: {})", message.header.sequence)
226        }
227        MessageType::Delta => {
228            format!("Delta (seq: {})", message.header.sequence)
229        }
230        MessageType::RequestSnapshot => {
231            format!("RequestSnapshot (seq: {})", message.header.sequence)
232        }
233        MessageType::Ack => {
234            format!("Ack (seq: {})", message.header.sequence)
235        }
236        MessageType::Ping => {
237            format!("Ping (seq: {})", message.header.sequence)
238        }
239        MessageType::Pong => {
240            format!("Pong (seq: {})", message.header.sequence)
241        }
242        MessageType::SchemaSync => {
243            format!("SchemaSync (seq: {})", message.header.sequence)
244        }
245        MessageType::Error => {
246            format!("Error (seq: {})", message.header.sequence)
247        }
248    }
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_format_bytes() {
257        assert_eq!(format_bytes(500), "500 bytes");
258        assert_eq!(format_bytes(1024), "1.00 KB");
259        assert_eq!(format_bytes(1536), "1.50 KB");
260        assert_eq!(format_bytes(1024 * 1024), "1.00 MB");
261        assert_eq!(format_bytes(1024 * 1024 * 1024), "1.00 GB");
262    }
263
264    #[test]
265    fn test_debug_mode_initialization() {
266        // Should not crash without env vars
267        init_debug_mode();
268    }
269}