159 lines
4.7 KiB
Rust
159 lines
4.7 KiB
Rust
//! What the graph computes, versus where it is drawn.
|
|
//!
|
|
//! `Graph::semantic_key` is a cache key: the editor memoizes T0 evaluation on it so that
|
|
//! dragging a node does not re-run the dataflow at pointer rate. A key that misses a
|
|
//! semantic field serves a STALE value, which is a worse failure than the slowness it
|
|
//! exists to fix — so every field that changes what the graph computes gets a test that
|
|
//! fails if the key ignores it.
|
|
|
|
use flowrs_core::graph::ir::WireMode;
|
|
use flowrs_core::{Graph, NodeId};
|
|
|
|
fn two_nodes() -> (Graph, NodeId, NodeId) {
|
|
let mut g = Graph::new();
|
|
let a = g.add_node("IntLiteral", 0.0, 0.0);
|
|
let b = g.add_node("PrintLn", 100.0, 0.0);
|
|
(g, a, b)
|
|
}
|
|
|
|
#[test]
|
|
fn moving_a_node_does_not_change_what_it_computes() {
|
|
let (mut g, a, _) = two_nodes();
|
|
let before = g.semantic_key();
|
|
g.get_node_mut(a).unwrap().position = (900.0, -450.0);
|
|
assert_eq!(
|
|
g.semantic_key(),
|
|
before,
|
|
"position is not an input to any computation; if it moves the key, every drag \
|
|
re-runs the graph",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn changing_a_property_changes_the_key() {
|
|
let (mut g, a, _) = two_nodes();
|
|
let before = g.semantic_key();
|
|
g.get_node_mut(a)
|
|
.unwrap()
|
|
.properties
|
|
.insert("value".into(), "7".into());
|
|
assert_ne!(
|
|
g.semantic_key(),
|
|
before,
|
|
"a literal's value is what it computes"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_property_changing_value_changes_the_key() {
|
|
let (mut g, a, _) = two_nodes();
|
|
g.get_node_mut(a)
|
|
.unwrap()
|
|
.properties
|
|
.insert("value".into(), "7".into());
|
|
let before = g.semantic_key();
|
|
g.get_node_mut(a)
|
|
.unwrap()
|
|
.properties
|
|
.insert("value".into(), "8".into());
|
|
assert_ne!(
|
|
g.semantic_key(),
|
|
before,
|
|
"7 and 8 must not hash alike — the classic stale-cache bug is a key that only \
|
|
notices a property appearing, not changing",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn connecting_a_wire_changes_the_key() {
|
|
let (mut g, a, b) = two_nodes();
|
|
let before = g.semantic_key();
|
|
g.add_wire(a, "value", b, "value", WireMode::Move)
|
|
.expect("the two default ports should connect");
|
|
assert_ne!(g.semantic_key(), before, "a wire changes what flows where");
|
|
}
|
|
|
|
#[test]
|
|
fn a_wires_mode_changes_the_key() {
|
|
let (mut g, a, b) = two_nodes();
|
|
let w = g
|
|
.add_wire(a, "value", b, "value", WireMode::Move)
|
|
.expect("connect");
|
|
let before = g.semantic_key();
|
|
g.wires.get_mut(&w).unwrap().mode = WireMode::Borrow;
|
|
assert_ne!(
|
|
g.semantic_key(),
|
|
before,
|
|
"move vs borrow is a real difference in the generated program",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn adding_and_removing_a_node_changes_the_key() {
|
|
let (mut g, _, _) = two_nodes();
|
|
let before = g.semantic_key();
|
|
let c = g.add_node("FloatLiteral", 0.0, 0.0);
|
|
let with_node = g.semantic_key();
|
|
assert_ne!(with_node, before, "a new node changes the graph");
|
|
g.remove_node(c);
|
|
assert_eq!(
|
|
g.semantic_key(),
|
|
before,
|
|
"removing it should return the key to where it was",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn the_key_is_stable_across_repeated_calls() {
|
|
let (g, _, _) = two_nodes();
|
|
assert_eq!(
|
|
g.semantic_key(),
|
|
g.semantic_key(),
|
|
"an unstable key would make the cache never hit — HashMap iteration order is the \
|
|
usual culprit, which is why nodes, wires and properties are all sorted first",
|
|
);
|
|
}
|
|
|
|
/// The cache holds one entry, so a graph that returns to a previous shape must return to
|
|
/// its previous key or the memo silently stops hitting.
|
|
#[test]
|
|
fn an_unstable_key_would_show_up_as_order_dependence() {
|
|
let mut a = Graph::new();
|
|
let n1 = a.add_node("IntLiteral", 0.0, 0.0);
|
|
let n2 = a.add_node("FloatLiteral", 0.0, 0.0);
|
|
a.get_node_mut(n1)
|
|
.unwrap()
|
|
.properties
|
|
.insert("x".into(), "1".into());
|
|
a.get_node_mut(n1)
|
|
.unwrap()
|
|
.properties
|
|
.insert("y".into(), "2".into());
|
|
a.get_node_mut(n2)
|
|
.unwrap()
|
|
.properties
|
|
.insert("z".into(), "3".into());
|
|
|
|
let mut b = Graph::new();
|
|
let m1 = b.add_node("IntLiteral", 0.0, 0.0);
|
|
let m2 = b.add_node("FloatLiteral", 0.0, 0.0);
|
|
// Same content, inserted in a different order.
|
|
b.get_node_mut(m2)
|
|
.unwrap()
|
|
.properties
|
|
.insert("z".into(), "3".into());
|
|
b.get_node_mut(m1)
|
|
.unwrap()
|
|
.properties
|
|
.insert("y".into(), "2".into());
|
|
b.get_node_mut(m1)
|
|
.unwrap()
|
|
.properties
|
|
.insert("x".into(), "1".into());
|
|
|
|
assert_eq!(
|
|
a.semantic_key(),
|
|
b.semantic_key(),
|
|
"two graphs with identical content must key alike regardless of insertion order",
|
|
);
|
|
}
|