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}