subtr_actor/stats/analysis_graph/graph/
render.rs1use std::any::TypeId;
2use std::collections::HashMap;
3
4use super::{analysis_node_graph_error, AnalysisGraph};
5use crate::*;
6
7impl AnalysisGraph {
8 pub fn render_ascii_dag(&mut self) -> SubtrActorResult<String> {
9 self.resolve()?;
10
11 let providers = self.provider_index_by_type()?;
12 let mut external_labels = Vec::new();
13 let mut external_node_ids = HashMap::new();
14
15 for node in &self.nodes {
16 for dependency in node.dependencies() {
17 let dependency_type_id = dependency.state_type_id();
18 if providers.contains_key(&dependency_type_id) {
19 continue;
20 }
21
22 let label = if self.declared_root_states.contains_key(&dependency_type_id) {
23 format!("root:{}", short_type_name(dependency.state_type_name()))
24 } else if self.declared_input_states.contains_key(&dependency_type_id) {
25 format!("input:{}", short_type_name(dependency.state_type_name()))
26 } else {
27 return Err(analysis_node_graph_error(format!(
28 "Node '{}' depends on missing state {}",
29 node.name(),
30 dependency.state_type_name(),
31 )));
32 };
33 ensure_external_render_node(
34 &mut external_labels,
35 &mut external_node_ids,
36 dependency_type_id,
37 label,
38 );
39 }
40 }
41
42 if self.nodes.is_empty() && external_labels.is_empty() {
43 return Ok("AnalysisGraph\n\\- (empty)".to_owned());
44 }
45
46 let external_count = external_labels.len();
47 let mut lines = Vec::with_capacity(1 + external_count + self.nodes.len());
48 lines.push("AnalysisGraph".to_owned());
49
50 for (display_id, (_, label)) in external_labels.iter().enumerate() {
51 lines.push(format!("[{display_id}] {label}"));
52 }
53
54 for (index, node) in self.nodes.iter().enumerate() {
55 let display_id = external_count + index;
56 let mut dependency_refs = Vec::new();
57 for dependency in node.dependencies() {
58 let dependency_type_id = dependency.state_type_id();
59 let source_id = if let Some(provider_index) = providers.get(&dependency_type_id) {
60 external_count + *provider_index
61 } else if self.declared_root_states.contains_key(&dependency_type_id) {
62 *external_node_ids
63 .get(&dependency_type_id)
64 .expect("root node should have been prepared")
65 } else if self.declared_input_states.contains_key(&dependency_type_id) {
66 *external_node_ids
67 .get(&dependency_type_id)
68 .expect("input node should have been prepared")
69 } else {
70 return Err(analysis_node_graph_error(format!(
71 "Node '{}' depends on missing state {}",
72 node.name(),
73 dependency.state_type_name(),
74 )));
75 };
76 dependency_refs.push(format!("[{source_id}]"));
77 }
78
79 if dependency_refs.is_empty() {
80 lines.push(format!("[{display_id}] {}", node.name()));
81 } else {
82 lines.push(format!(
83 "[{display_id}] {} <- {}",
84 node.name(),
85 dependency_refs.join(", "),
86 ));
87 }
88 }
89
90 Ok(lines.join("\n"))
91 }
92}
93
94fn ensure_external_render_node(
95 labels: &mut Vec<(TypeId, Box<str>)>,
96 external_node_ids: &mut HashMap<TypeId, usize>,
97 dependency_type_id: TypeId,
98 label: String,
99) -> usize {
100 if let Some(node_id) = external_node_ids.get(&dependency_type_id) {
101 return *node_id;
102 }
103
104 let node_id = labels.len();
105 labels.push((dependency_type_id, label.into_boxed_str()));
106 external_node_ids.insert(dependency_type_id, node_id);
107 node_id
108}
109
110fn short_type_name(type_name: &str) -> String {
111 let mut shortened = String::with_capacity(type_name.len());
112 let mut token = String::new();
113
114 for character in type_name.chars() {
115 if character.is_alphanumeric() || matches!(character, '_' | ':') {
116 token.push(character);
117 continue;
118 }
119
120 if !token.is_empty() {
121 shortened.push_str(token.rsplit("::").next().unwrap_or(&token));
122 token.clear();
123 }
124 shortened.push(character);
125 }
126
127 if !token.is_empty() {
128 shortened.push_str(token.rsplit("::").next().unwrap_or(&token));
129 }
130
131 shortened
132}