depends/graphviz/
mod.rs

1use std::{
2    collections::{hash_map::DefaultHasher, BTreeMap, HashSet},
3    hash::BuildHasher,
4};
5
6use crate::{Identifiable, Visitor};
7
8#[derive(Debug)]
9struct Node {
10    id: usize,
11    name: &'static str,
12    edges: Vec<usize>,
13    operation: Option<&'static str>,
14    dependency: Option<&'static str>,
15}
16
17impl Node {
18    fn node_identifier(&self) -> String {
19        format!("node_{}", self.id)
20    }
21}
22
23/// A [Visitor] which builds a `Graphviz` representation of a given graph.
24///
25/// ```
26/// # use std::{cell::Ref, collections::HashSet, hash::Hash, rc::Rc};
27/// # use depends::{
28/// #     derives::{Operation, Value},
29/// #     error::{EarlyExit, ResolveResult},
30/// #     DependencyEdge, DepRef2, DepRef3, Dependencies2, Dependencies3, Dependency, DerivedNode, HashValue,
31/// #     InputNode, NodeHash, Resolve, DepRef, UpdateDerived, UpdateInput,
32/// # };
33/// # pub trait NumberLike {
34/// #     fn number_value(&self) -> i32;
35/// # }
36/// # #[derive(Value, Default, Hash)]
37/// # pub struct NumberValueI32 {
38/// #     pub value: i32,
39/// # }
40/// # impl NumberValueI32 {
41/// #     pub fn new(value: i32) -> Self {
42/// #         Self { value }
43/// #     }
44/// # }
45/// #
46/// # impl UpdateInput for NumberValueI32 {
47/// #     type Update = i32;
48/// #
49/// #     fn update_mut(&mut self, update: Self::Update) {
50/// #         // Implementing this trait will provide a way for code outside of this graph to
51/// #         // change its internal state. This is just a simple replace for now.
52/// #         self.value = update;
53/// #     }
54/// # }
55/// #
56/// # #[derive(Value, Default, Hash)]
57/// # pub struct NumberValueI8 {
58/// #     pub value: i8,
59/// # }
60/// #
61/// # impl NumberValueI8 {
62/// #     pub fn new(value: i8) -> Self {
63/// #         Self { value }
64/// #     }
65/// # }
66/// #
67/// # impl NumberLike for NumberValueI8 {
68/// #     fn number_value(&self) -> i32 {
69/// #         self.value as i32
70/// #     }
71/// # }
72/// # impl NumberLike for NumberValueI32 {
73/// #     fn number_value(&self) -> i32 {
74/// #         self.value
75/// #     }
76/// # }
77/// #
78/// # impl UpdateInput for NumberValueI8 {
79/// #     type Update = i8;
80/// #
81/// #     fn update_mut(&mut self, update: Self::Update) {
82/// #         // Implementing this trait will provide a way for code outside of this graph to
83/// #         // change its internal state. This is just a simple replace for now.
84/// #         self.value = update;
85/// #     }
86/// # }
87/// # #[derive(Operation)]
88/// # pub struct Sum;
89/// #
90/// # impl<A: NumberLike, B: NumberLike> UpdateDerived<DepRef2<'_, A, B>, Sum> for NumberValueI32 {
91/// #     fn update(&mut self, value: DepRef2<'_, A, B>) -> Result<(), EarlyExit> {
92/// #         self.value = value.0.data().number_value() + value.1.data().number_value();
93/// #         Ok(())
94/// #     }
95/// # }
96/// #
97/// # impl<A: NumberLike, B: NumberLike, C: NumberLike> UpdateDerived<DepRef3<'_, A, B, C>, Sum>
98/// # for NumberValueI32
99/// # {
100/// #     fn update(&mut self, value: DepRef3<'_, A, B, C>) -> Result<(), EarlyExit> {
101/// #         self.value = value.0.data().number_value() + value.1.data().number_value() + value.2.data().number_value();
102/// #         Ok(())
103/// #     }
104/// # }
105/// # #[derive(Operation)]
106/// # pub struct Square;
107/// #
108/// # impl<A: NumberLike> UpdateDerived<DepRef<'_, A>, Square> for NumberValueI32 {
109/// #     fn update(&mut self, value: DepRef<'_, A>) -> Result<(), EarlyExit> {
110/// #         self.value = value.data().number_value().pow(2);
111/// #         Ok(())
112/// #     }
113/// # }
114/// # #[derive(Operation)]
115/// # pub struct Multiply;
116/// #
117/// # impl<A: NumberLike, B: NumberLike> UpdateDerived<DepRef2<'_, A, B>, Multiply> for NumberValueI32 {
118/// #     fn update(&mut self, value: DepRef2<'_, A, B>) -> Result<(), EarlyExit> {
119/// #         self.value = value.0.data().number_value() * value.1.data().number_value();
120/// #         Ok(())
121/// #     }
122/// # }
123/// use depends::graphviz::GraphvizVisitor;
124/// use depends::NodeState;
125///
126/// let a = InputNode::new(NumberValueI32::new(1));
127/// let b = InputNode::new(NumberValueI32::new(2));
128/// // Note that we can combine different types in the same graph.
129/// let c = InputNode::new(NumberValueI8::new(3));
130/// let d = InputNode::new(NumberValueI32::new(4));
131/// let e = InputNode::new(NumberValueI32::new(5));
132///
133/// // Now for some derived nodes. We can't update these from outside of the
134/// // graph. They are updated when their dependencies change.
135///
136/// // Compose a graph.
137/// let c_squared = DerivedNode::new(
138///     Dependency::new(Rc::clone(&c)),
139///     Square,
140///     NumberValueI32::default(),
141/// );
142/// // Sum of a and b
143/// let a_plus_b = DerivedNode::new(
144///     Dependencies2::new(Rc::clone(&a), Rc::clone(&b)),
145///     Sum,
146///     NumberValueI32::default(),
147/// );
148///
149/// // Product of d and e
150/// let d_times_e = DerivedNode::new(
151///     Dependencies2::new(Rc::clone(&d), Rc::clone(&e)),
152///     Multiply,
153///     NumberValueI32::default(),
154/// );
155/// // Create another edge to node a
156/// let a_plus_d_times_e = DerivedNode::new(
157///     Dependencies2::new(Rc::clone(&a), Rc::clone(&d_times_e)),
158///     Sum,
159///     NumberValueI32::default(),
160/// );
161///
162/// // Finally, the sum of all of the above
163/// let answer = DerivedNode::new(
164///     Dependencies3::new(Rc::clone(&c_squared), Rc::clone(&a_plus_b), Rc::clone(&a_plus_d_times_e)),
165///     Sum,
166///     NumberValueI32::default(),
167/// );
168///
169/// let mut visitor = GraphvizVisitor::new();
170///
171/// // Resolve the graph with this visitor.
172/// // Be sure NOT to use `resolve_root`, as this will clear the visitor's state.
173/// answer.resolve(&mut visitor).unwrap();
174///
175/// println!("{}", visitor.render().unwrap());
176/// // A Graphviz representation is now available on the visitor!
177/// assert_eq!(
178///     visitor.render().unwrap(),
179///     r#"
180/// digraph Dag {
181///   node_0 [label="NumberValueI32"];
182///   node_1 [label="NumberValueI32"];
183///   node_2 [label="NumberValueI8"];
184///   node_3 [label="NumberValueI32"];
185///   node_4 [label="NumberValueI32"];
186///   node_5 [label="NumberValueI32"];
187///   node_2 -> node_5 [label="Square"];
188///   node_6 [label="NumberValueI32"];
189///   node_0 -> node_6 [label="Sum", class="Dependencies2"];
190///   node_1 -> node_6 [label="Sum", class="Dependencies2"];
191///   node_7 [label="NumberValueI32"];
192///   node_3 -> node_7 [label="Multiply", class="Dependencies2"];
193///   node_4 -> node_7 [label="Multiply", class="Dependencies2"];
194///   node_8 [label="NumberValueI32"];
195///   node_0 -> node_8 [label="Sum", class="Dependencies2"];
196///   node_7 -> node_8 [label="Sum", class="Dependencies2"];
197///   node_9 [label="NumberValueI32"];
198///   node_5 -> node_9 [label="Sum", class="Dependencies3"];
199///   node_6 -> node_9 [label="Sum", class="Dependencies3"];
200///   node_8 -> node_9 [label="Sum", class="Dependencies3"];
201/// }
202/// "#
203///     .trim()
204/// );
205/// ```
206#[derive(Debug, Default)]
207pub struct GraphvizVisitor {
208    visitor: HashSet<usize>,
209    nodes: BTreeMap<usize, Node>,
210    stack: Vec<usize>,
211}
212// TODO: you need to name the actual dependency type, as it could be custom
213//  or like Dependencies4 etc.
214
215impl GraphvizVisitor {
216    pub fn new() -> Self {
217        Self::default()
218    }
219
220    /// Render the visited graph to Graphviz DOT format. Returns [Option::None]
221    /// if no graph has been visited.
222    pub fn render(&self) -> Option<String> {
223        if self.nodes.is_empty() {
224            None
225        } else {
226            let mut lines = Vec::new();
227            lines.push(String::from("digraph Dag {"));
228            self.nodes.values().for_each(|n| {
229                lines.push(format!("  {} [label=\"{}\"];", n.node_identifier(), n.name));
230                // TODO: it would be nice to make this type-system enforced
231                //  at the moment, it's not guaranteed by the type system
232                //  that nodes.len() == 0 iff operation.is_none()
233                //  this would likely be done by splitting `touch` in to
234                //  `touch_input` and `touch_derived`
235                if let Some(op) = n.operation {
236                    let class = n
237                        .dependency
238                        .map(|d| format!(", class=\"{}\"", d))
239                        .unwrap_or_default();
240                    let edge_label = format!("[label=\"{}\"{}]", op, class);
241                    n.edges.iter().for_each(|c| {
242                        lines.push(format!(
243                            "  {} -> {} {};",
244                            self.nodes[c].node_identifier(),
245                            n.node_identifier(),
246                            edge_label
247                        ));
248                    });
249                }
250            });
251            lines.push(String::from("}"));
252            Some(lines.join("\n"))
253        }
254    }
255}
256
257impl Visitor for GraphvizVisitor {
258    type Hasher = DefaultHasher;
259
260    fn visit<N>(&mut self, node: &N) -> bool
261    where
262        N: Identifiable,
263    {
264        self.visitor.visit(node)
265    }
266
267    fn clear(&mut self) {
268        self.visitor.clear();
269        self.nodes.clear();
270    }
271
272    fn touch<N>(&mut self, node: &N, operation: Option<&'static str>)
273    where
274        N: Identifiable,
275    {
276        self.stack.push(node.id());
277        self.nodes.entry(node.id()).or_insert_with(|| {
278            Node {
279                id: node.id(),
280                name: N::name(),
281                edges: Vec::default(),
282                operation,
283                dependency: None,
284            }
285        });
286    }
287
288    fn touch_dependency_group(&mut self, dep: &'static str) {
289        let last = self.stack.last().unwrap();
290        if let Some(n) = self.nodes.get_mut(last) {
291            n.dependency = Some(dep);
292        }
293    }
294
295    fn leave<N>(&mut self, node: &N)
296    where
297        N: Identifiable,
298    {
299        let last = self.stack.pop().unwrap();
300        assert_eq!(last, node.id());
301        if let Some(parent) = self.stack.last() {
302            if let Some(n) = self.nodes.get_mut(parent) {
303                n.edges.push(last)
304            }
305        }
306    }
307
308    fn hasher(&self) -> Self::Hasher {
309        self.visitor.hasher().build_hasher()
310    }
311}