1use std::fmt::{self, Write as FmtWrite};
36
37use crate::object::{ObjectId, ObjectResult, global_registry};
38
39pub mod span_names {
43 pub const EVENT_LOOP: &str = "horizon_lattice::event_loop";
45 pub const TIMER: &str = "horizon_lattice::timer";
47 pub const SIGNAL: &str = "horizon_lattice::signal";
49 pub const PROPERTY: &str = "horizon_lattice::property";
51 pub const OBJECT: &str = "horizon_lattice::object";
53 pub const TASK: &str = "horizon_lattice::task";
55}
56
57pub mod targets {
61 pub const CORE: &str = "horizon_lattice_core";
63 pub const EVENT_LOOP: &str = "horizon_lattice_core::event_loop";
65 pub const TIMER: &str = "horizon_lattice_core::timer";
67 pub const SIGNAL: &str = "horizon_lattice_core::signal";
69 pub const PROPERTY: &str = "horizon_lattice_core::property";
71 pub const OBJECT: &str = "horizon_lattice_core::object";
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
77pub enum TreeStyle {
78 Ascii,
80 #[default]
82 Unicode,
83 Compact,
85}
86
87#[derive(Debug, Clone)]
89pub struct TreeFormatOptions {
90 pub style: TreeStyle,
92 pub show_ids: bool,
94 pub show_types: bool,
96 pub show_properties: bool,
98 pub max_depth: Option<usize>,
100 pub indent_size: usize,
102}
103
104impl Default for TreeFormatOptions {
105 fn default() -> Self {
106 Self {
107 style: TreeStyle::default(),
108 show_ids: true,
109 show_types: true,
110 show_properties: false,
111 max_depth: None,
112 indent_size: 2,
113 }
114 }
115}
116
117impl TreeFormatOptions {
118 pub fn detailed() -> Self {
120 Self {
121 show_properties: true,
122 ..Default::default()
123 }
124 }
125
126 pub fn minimal() -> Self {
128 Self {
129 show_ids: false,
130 show_types: false,
131 show_properties: false,
132 ..Default::default()
133 }
134 }
135}
136
137#[derive(Debug, Clone)]
142pub struct ObjectTreeDebug {
143 options: TreeFormatOptions,
144}
145
146impl ObjectTreeDebug {
147 pub fn new() -> Self {
149 Self {
150 options: TreeFormatOptions::default(),
151 }
152 }
153
154 pub fn with_options(options: TreeFormatOptions) -> Self {
156 Self { options }
157 }
158
159 pub fn format_all(&self) -> ObjectResult<String> {
161 let registry = global_registry()?;
162 let roots = registry.root_objects();
163
164 let mut output = String::new();
165 writeln!(
166 output,
167 "Object Tree ({} total objects):",
168 registry.object_count()
169 )
170 .expect("write to String");
171
172 if roots.is_empty() {
173 writeln!(output, " (empty)").expect("write to String");
174 } else {
175 for root_id in roots {
176 self.format_subtree_into(root_id, 0, true, &mut output)?;
177 }
178 }
179
180 Ok(output)
181 }
182
183 pub fn format_subtree(&self, root: ObjectId) -> ObjectResult<String> {
185 let mut output = String::new();
186 self.format_subtree_into(root, 0, true, &mut output)?;
187 Ok(output)
188 }
189
190 fn format_subtree_into(
192 &self,
193 id: ObjectId,
194 depth: usize,
195 is_last: bool,
196 output: &mut String,
197 ) -> ObjectResult<()> {
198 if let Some(max) = self.options.max_depth
200 && depth > max
201 {
202 return Ok(());
203 }
204
205 let registry = global_registry()?;
206
207 let name = match registry.object_name(id) {
209 Ok(name) => name,
210 Err(crate::object::ObjectError::InvalidObjectId) => return Ok(()),
211 Err(e) => return Err(e),
212 };
213 let type_name = match registry.type_name(id) {
214 Ok(name) => name,
215 Err(crate::object::ObjectError::InvalidObjectId) => return Ok(()),
216 Err(e) => return Err(e),
217 };
218 let children = match registry.children(id) {
219 Ok(children) => children,
220 Err(crate::object::ObjectError::InvalidObjectId) => return Ok(()),
221 Err(e) => return Err(e),
222 };
223
224 let prefix = self.build_prefix(depth, is_last);
226 output.push_str(&prefix);
227
228 let display_name = if name.is_empty() { "(unnamed)" } else { &name };
230 output.push_str(display_name);
231
232 if self.options.show_ids {
234 write!(output, " [{:?}]", id).expect("write to String");
235 }
236
237 if self.options.show_types {
239 let short_type = type_name.rsplit("::").next().unwrap_or(type_name);
241 write!(output, " ({})", short_type).expect("write to String");
242 }
243
244 output.push('\n');
245
246 if self.options.show_properties {
248 let prop_names: Vec<String> = registry.with_read(|r| {
249 r.dynamic_property_names(id)
250 .map(|names| names.into_iter().map(|s| s.to_string()).collect())
251 })?;
252 if !prop_names.is_empty() {
253 let prop_prefix = self.build_property_prefix(depth, is_last);
254 for prop_name in prop_names {
255 writeln!(output, "{} .{}", prop_prefix, prop_name).expect("write to String");
256 }
257 }
258 }
259
260 let child_count = children.len();
262 for (i, child_id) in children.into_iter().enumerate() {
263 let child_is_last = i == child_count - 1;
264 self.format_subtree_into(child_id, depth + 1, child_is_last, output)?;
265 }
266
267 Ok(())
268 }
269
270 fn build_prefix(&self, depth: usize, is_last: bool) -> String {
272 if depth == 0 {
273 return String::new();
274 }
275
276 let (branch, corner, space) = match self.options.style {
277 TreeStyle::Ascii => ("|", "+--", " "),
278 TreeStyle::Unicode => (
279 "\u{2502}",
280 "\u{251c}\u{2500}\u{2500}",
281 "\u{2514}\u{2500}\u{2500}",
282 ),
283 TreeStyle::Compact => ("", "- ", "- "),
284 };
285
286 let mut prefix = String::new();
287
288 for _ in 0..(depth - 1) {
290 prefix.push_str(branch);
291 for _ in 0..self.options.indent_size {
292 prefix.push(' ');
293 }
294 }
295
296 if is_last {
298 prefix.push_str(if self.options.style == TreeStyle::Unicode {
299 "\u{2514}\u{2500}\u{2500} "
300 } else {
301 space
302 });
303 } else {
304 prefix.push_str(corner);
305 prefix.push(' ');
306 }
307
308 prefix
309 }
310
311 fn build_property_prefix(&self, depth: usize, _is_last: bool) -> String {
313 let (branch, _) = match self.options.style {
314 TreeStyle::Ascii => ("|", " "),
315 TreeStyle::Unicode => ("\u{2502}", " "),
316 TreeStyle::Compact => ("", " "),
317 };
318
319 let mut prefix = String::new();
320 for _ in 0..depth {
321 prefix.push_str(branch);
322 for _ in 0..self.options.indent_size {
323 prefix.push(' ');
324 }
325 }
326 prefix
327 }
328}
329
330impl Default for ObjectTreeDebug {
331 fn default() -> Self {
332 Self::new()
333 }
334}
335
336impl fmt::Display for ObjectTreeDebug {
337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 match self.format_all() {
339 Ok(output) => write!(f, "{}", output),
340 Err(e) => write!(f, "Error formatting object tree: {}", e),
341 }
342 }
343}
344
345#[derive(Debug)]
349pub struct PerfSpan {
350 #[allow(dead_code)]
351 span: tracing::span::EnteredSpan,
352}
353
354impl PerfSpan {
355 pub fn new(name: &'static str) -> Self {
359 let span = tracing::info_span!(target: "horizon_lattice::perf", "perf", operation = name);
360 Self {
361 span: span.entered(),
362 }
363 }
364}
365
366#[macro_export]
371macro_rules! lattice_trace {
372 ($($arg:tt)*) => {
373 tracing::trace!(target: "horizon_lattice_core", $($arg)*)
374 };
375}
376
377#[macro_export]
381macro_rules! lattice_debug {
382 ($($arg:tt)*) => {
383 tracing::debug!(target: "horizon_lattice_core", $($arg)*)
384 };
385}
386
387#[macro_export]
391macro_rules! lattice_info {
392 ($($arg:tt)*) => {
393 tracing::info!(target: "horizon_lattice_core", $($arg)*)
394 };
395}
396
397#[macro_export]
401macro_rules! lattice_warn {
402 ($($arg:tt)*) => {
403 tracing::warn!(target: "horizon_lattice_core", $($arg)*)
404 };
405}
406
407#[macro_export]
411macro_rules! lattice_error {
412 ($($arg:tt)*) => {
413 tracing::error!(target: "horizon_lattice_core", $($arg)*)
414 };
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420 use crate::object::{Object, ObjectBase, init_global_registry};
421
422 struct TestWidget {
423 base: ObjectBase,
424 }
425
426 impl TestWidget {
427 fn new(name: &str) -> Self {
428 let widget = Self {
429 base: ObjectBase::new::<Self>(),
430 };
431 widget.base.set_name(name);
432 widget
433 }
434 }
435
436 impl Object for TestWidget {
437 fn object_id(&self) -> ObjectId {
438 self.base.id()
439 }
440 }
441
442 fn setup() {
443 init_global_registry();
444 }
445
446 #[test]
447 fn test_tree_format_empty() {
448 setup();
449 let debug = ObjectTreeDebug::new();
450 let output = debug.format_all().unwrap();
451 assert!(output.contains("Object Tree"));
452 }
453
454 #[test]
455 fn test_tree_format_single() {
456 setup();
457 let widget = TestWidget::new("root");
458
459 let debug = ObjectTreeDebug::new();
460 let output = debug.format_subtree(widget.object_id()).unwrap();
461
462 assert!(output.contains("root"));
463 assert!(output.contains("TestWidget"));
464 }
465
466 #[test]
467 fn test_tree_format_hierarchy() {
468 setup();
469 let root = TestWidget::new("window");
470 let child1 = TestWidget::new("button1");
471 let child2 = TestWidget::new("button2");
472
473 child1.base.set_parent(Some(root.object_id())).unwrap();
474 child2.base.set_parent(Some(root.object_id())).unwrap();
475
476 let debug = ObjectTreeDebug::new();
477 let output = debug.format_subtree(root.object_id()).unwrap();
478
479 assert!(output.contains("window"));
480 assert!(output.contains("button1"));
481 assert!(output.contains("button2"));
482 }
483
484 #[test]
485 fn test_tree_format_minimal() {
486 setup();
487 let widget = TestWidget::new("test");
488
489 let debug = ObjectTreeDebug::with_options(TreeFormatOptions::minimal());
490 let output = debug.format_subtree(widget.object_id()).unwrap();
491
492 assert!(output.contains("test"));
493 assert!(!output.contains("TestWidget"));
494 assert!(!output.contains("["));
495 }
496
497 #[test]
498 fn test_perf_span() {
499 setup();
500 let _span = PerfSpan::new("test_operation");
502 }
503}