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
9pub 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
31pub fn is_debug_enabled() -> bool {
33 DEBUG_MODE.load(Ordering::Relaxed)
34}
35
36pub fn is_trace_enabled() -> bool {
38 TRACE_MODE.load(Ordering::Relaxed)
39}
40
41pub 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
57pub 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
74pub 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
93pub 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
139pub 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
149pub 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
159pub 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
175pub 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
186pub 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
195pub 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
204pub 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
221pub 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 init_debug_mode();
268 }
269}