Add graphviz dump
This commit is contained in:
parent
dd1143aa9b
commit
712b528ca8
@ -50,7 +50,7 @@ mod util;
|
||||
|
||||
use builder::{BuildGraphError, GraphBuilder};
|
||||
use node::{ErasedNode, NodeValue};
|
||||
use petgraph::visit::{IntoEdgeReferences, NodeIndexable};
|
||||
use petgraph::visit::{IntoEdgeReferences, IntoNodeReferences, NodeIndexable, NodeRef};
|
||||
use petgraph::{stable_graph::StableDiGraph, visit::EdgeRef};
|
||||
use rule::{AsyncRule, Input, InputVisitor, Rule};
|
||||
use std::cell::{Cell, RefCell};
|
||||
@ -192,6 +192,59 @@ impl<O: 'static, S: Synchronicity> Graph<O, S> {
|
||||
is_valid: Rc::clone(&self.is_valid),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a graphviz representation of this graph.
|
||||
///
|
||||
/// The exact format is not guaranteed to be stable.
|
||||
pub fn as_dot_string(&self) -> String {
|
||||
// Escaper/Escaped are cribbed from petgraph (MIT licensed).
|
||||
use std::fmt::Write;
|
||||
struct Escaper<W>(W);
|
||||
impl<W: std::fmt::Write> std::fmt::Write for Escaper<W> {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
for c in s.chars() {
|
||||
self.write_char(c)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn write_char(&mut self, c: char) -> std::fmt::Result {
|
||||
match c {
|
||||
'"' | '\\' => self.0.write_char('\\')?,
|
||||
// \l is for left justified linebreak
|
||||
'\n' => return self.0.write_str("\\l"),
|
||||
_ => {}
|
||||
}
|
||||
self.0.write_char(c)
|
||||
}
|
||||
}
|
||||
struct Escaped<T>(T);
|
||||
impl<T: std::fmt::Debug> std::fmt::Debug for Escaped<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(&mut Escaper(f), "{:?}", &self.0)
|
||||
}
|
||||
}
|
||||
|
||||
// Our own dot formatter, to include both node ids and labels.
|
||||
struct Dot<'a, S: Synchronicity>(&'a NodeGraph<S>);
|
||||
impl<'a, S: Synchronicity> std::fmt::Debug for Dot<'a, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "digraph {{")?;
|
||||
for node in self.0.node_references() {
|
||||
let id = self.0.to_index(node.id());
|
||||
let label = Escaped(node.weight());
|
||||
writeln!(f, "\t{id} [label =\"{label:?} (id={id})\"]")?;
|
||||
}
|
||||
for edge in self.0.edge_references() {
|
||||
let source = self.0.to_index(edge.source());
|
||||
let target = self.0.to_index(edge.target());
|
||||
writeln!(f, "\t{source} -> {target} []")?;
|
||||
}
|
||||
writeln!(f, "}}")
|
||||
}
|
||||
}
|
||||
let graph = self.node_graph.borrow();
|
||||
format!("{:?}", Dot(&*graph))
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: 'static> Graph<O, Synchronous> {
|
||||
@ -630,4 +683,25 @@ mod tests {
|
||||
let mut graph = builder.build().unwrap();
|
||||
assert_eq!(*graph.evaluate_async().await, 43);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn graphviz() {
|
||||
let mut builder = GraphBuilder::new();
|
||||
let a = builder.add_value(1);
|
||||
let b = builder.add_value(2);
|
||||
builder.set_output(Add(a, b));
|
||||
let graph = builder.build().unwrap();
|
||||
println!("{}", graph.as_dot_string());
|
||||
assert_eq!(
|
||||
graph.as_dot_string(),
|
||||
r#"digraph {
|
||||
0 [label ="ConstNode<i32> (id=0)"]
|
||||
1 [label ="ConstNode<i32> (id=1)"]
|
||||
2 [label ="RuleNode<compute_graph::tests::Add> (id=2)"]
|
||||
0 -> 2 []
|
||||
1 -> 2 []
|
||||
}
|
||||
"#
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ pub(crate) struct ErasedNode<Synch: Synchronicity> {
|
||||
invalidate: Box<dyn Fn(&mut Box<dyn Any>) -> ()>,
|
||||
visit_inputs: Box<dyn Fn(&Box<dyn Any>, &mut dyn FnMut(NodeId) -> ()) -> ()>,
|
||||
update: Box<dyn for<'a> Fn(&'a mut Box<dyn Any>) -> Synch::UpdateResult<'a>>,
|
||||
debug_fmt: Box<dyn Fn(&Box<dyn Any>, &mut std::fmt::Formatter<'_>) -> std::fmt::Result>,
|
||||
}
|
||||
|
||||
impl<S: Synchronicity> ErasedNode<S> {
|
||||
@ -36,6 +37,10 @@ impl<S: Synchronicity> ErasedNode<S> {
|
||||
let x = any.downcast_mut::<Box<dyn Node<V, S>>>().unwrap();
|
||||
x.update()
|
||||
}),
|
||||
debug_fmt: Box::new(|any, f| {
|
||||
let x = any.downcast_ref::<Box<dyn Node<V, S>>>().unwrap();
|
||||
x.fmt(f)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +67,13 @@ impl ErasedNode<Asynchronous> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Node<Value: NodeValue, Synch: Synchronicity> {
|
||||
impl<S: Synchronicity> std::fmt::Debug for ErasedNode<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
(self.debug_fmt)(&self.any, f)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait Node<Value: NodeValue, Synch: Synchronicity>: std::fmt::Debug {
|
||||
fn is_valid(&self) -> bool;
|
||||
fn invalidate(&mut self);
|
||||
fn visit_inputs(&self, visitor: &mut dyn FnMut(NodeId) -> ());
|
||||
@ -136,6 +147,12 @@ impl<V: NodeValue, S: Synchronicity> Node<V, S> for ConstNode<V, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, S> std::fmt::Debug for ConstNode<V, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ConstNode<{}>", std::any::type_name::<V>())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InvalidatableConstNode<V, S> {
|
||||
value: Rc<RefCell<Option<V>>>,
|
||||
valid: bool,
|
||||
@ -175,6 +192,12 @@ impl<V: NodeValue, S: Synchronicity> Node<V, S> for InvalidatableConstNode<V, S>
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, S> std::fmt::Debug for InvalidatableConstNode<V, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "InvalidatableConstNode<{}>", std::any::type_name::<V>())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RuleNode<R, V, S> {
|
||||
rule: R,
|
||||
value: Rc<RefCell<Option<V>>>,
|
||||
@ -233,6 +256,12 @@ impl<R: Rule, S: Synchronicity> Node<R::Output, S> for RuleNode<R, R::Output, S>
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, V, S> std::fmt::Debug for RuleNode<R, V, S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "RuleNode<{}>", std::any::type_name::<R>())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AsyncConstNode<V, P: FnOnce() -> F, F: Future<Output = V>> {
|
||||
provider: Option<P>,
|
||||
value: Rc<RefCell<Option<V>>>,
|
||||
@ -279,6 +308,12 @@ impl<V: NodeValue, P: FnOnce() -> F, F: Future<Output = V>> Node<V, Asynchronous
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, P: FnOnce() -> F, F: Future<Output = V>> std::fmt::Debug for AsyncConstNode<V, P, F> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "AsyncConstNode<{}>", std::any::type_name::<V>())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AsyncRuleNode<R, V> {
|
||||
rule: R,
|
||||
value: Rc<RefCell<Option<V>>>,
|
||||
@ -338,3 +373,9 @@ impl<R: AsyncRule> Node<R::Output, Asynchronous> for AsyncRuleNode<R, R::Output>
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<R, V> std::fmt::Debug for AsyncRuleNode<R, V> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "AsyncRuleNode<{}>", std::any::type_name::<R>())
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,6 @@ pub trait InputVisitable {
|
||||
/// To obtain an input, add a value or rule to a [`GraphBuilder`](`crate::builder::GraphBuilder`).
|
||||
///
|
||||
/// Note that this type implements `Clone`, so can be cloned and used as an input for multiple nodes.
|
||||
#[derive(Debug)]
|
||||
pub struct Input<T> {
|
||||
pub(crate) node_idx: NodeId,
|
||||
pub(crate) value: Rc<RefCell<Option<T>>>,
|
||||
@ -116,6 +115,17 @@ impl<T> Clone for Input<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Input<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Input<{}>({:?})",
|
||||
std::any::type_name::<T>(),
|
||||
self.node_idx
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: i really want Input to be able to implement Deref somehow
|
||||
|
||||
/// A type that can visit arbitrary [`Input`]s.
|
||||
|
7
crates/compute_graph/test.dot
Normal file
7
crates/compute_graph/test.dot
Normal file
@ -0,0 +1,7 @@
|
||||
digraph {
|
||||
0 [label ="ConstNode<i32> (id=0)"]
|
||||
1 [label ="ConstNode<i32> (id=1)"]
|
||||
2 [label ="RuleNode<compute_graph::tests::Add> (id=2)"]
|
||||
0 -> 2 []
|
||||
1 -> 2 []
|
||||
}
|
42
crates/compute_graph/test.svg
Normal file
42
crates/compute_graph/test.svg
Normal file
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 10.0.1 (20240210.2158)
|
||||
-->
|
||||
<!-- Pages: 1 -->
|
||||
<svg width="432pt" height="116pt"
|
||||
viewBox="0.00 0.00 432.02 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-112 428.02,-112 428.02,4 -4,4"/>
|
||||
<!-- 0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>0</title>
|
||||
<ellipse fill="none" stroke="black" cx="101.51" cy="-90" rx="101.51" ry="18"/>
|
||||
<text text-anchor="middle" x="101.51" y="-84.95" font-family="Times,serif" font-size="14.00">ConstNode<i32> (id=0)</text>
|
||||
</g>
|
||||
<!-- 2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>2</title>
|
||||
<ellipse fill="none" stroke="black" cx="211.51" cy="-18" rx="185.96" ry="18"/>
|
||||
<text text-anchor="middle" x="211.51" y="-12.95" font-family="Times,serif" font-size="14.00">RuleNode<compute_graph::tests::Add> (id=2)</text>
|
||||
</g>
|
||||
<!-- 0->2 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>0->2</title>
|
||||
<path fill="none" stroke="black" d="M127.86,-72.23C142.07,-63.19 159.82,-51.89 175.31,-42.03"/>
|
||||
<polygon fill="black" stroke="black" points="176.78,-45.25 183.33,-36.93 173.02,-39.34 176.78,-45.25"/>
|
||||
</g>
|
||||
<!-- 1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>1</title>
|
||||
<ellipse fill="none" stroke="black" cx="322.51" cy="-90" rx="101.51" ry="18"/>
|
||||
<text text-anchor="middle" x="322.51" y="-84.95" font-family="Times,serif" font-size="14.00">ConstNode<i32> (id=1)</text>
|
||||
</g>
|
||||
<!-- 1->2 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>1->2</title>
|
||||
<path fill="none" stroke="black" d="M295.92,-72.23C281.58,-63.19 263.67,-51.89 248.04,-42.03"/>
|
||||
<polygon fill="black" stroke="black" points="250.26,-39.3 239.93,-36.92 246.52,-45.22 250.26,-39.3"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Loading…
x
Reference in New Issue
Block a user