Skip to main content

subtr_actor/stats/analysis_graph/graph/
render.rs

1use 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}