Add dynamic node kinds

Move commands.rs tests to lib.rs
Add TestNodeBuilder
This commit is contained in:
mars 2022-09-20 07:08:16 -06:00
parent 247028caa8
commit 47f0288f21
5 changed files with 258 additions and 202 deletions

View File

@ -1,5 +1,5 @@
use super::{Graph, GraphError, GraphResult};
use crate::node::{Edge, Node, NodeKind, SlotIndex};
use crate::node::{Edge, Node, SlotIndex};
use std::fmt::Debug;
pub trait Command: Debug {
@ -10,8 +10,8 @@ pub trait Command: Debug {
pub type DynCommand = Box<dyn Command>;
pub struct CommandHistory {
commands: Vec<(DynCommand, DynCommand)>,
cursor: usize,
pub(crate) commands: Vec<(DynCommand, DynCommand)>,
pub(crate) cursor: usize,
}
impl CommandHistory {
@ -60,7 +60,7 @@ impl CommandHistory {
#[derive(Clone, Debug)]
pub struct CreateNode {
pub kind: NodeKind,
pub kind: usize,
pub pos: glam::Vec2,
pub inputs: Vec<Option<SlotIndex>>,
}
@ -144,7 +144,7 @@ impl Command for MoveNode {
fn apply(&self, graph: &mut Graph) -> GraphResult<()> {
let target = graph.get_node_mut(self.target)?;
if self.relative {
target.pos += self.to;
} else {
@ -237,110 +237,3 @@ impl Command for DeleteEdge {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::AtomOp;
impl CommandHistory {
pub fn push_multi(
&mut self,
graph: &mut Graph,
cmds: impl IntoIterator<Item = DynCommand>,
) -> GraphResult<()> {
for cmd in cmds {
println!("{:#?}", cmd);
self.push(graph, cmd)?;
println!("{:#?}", graph);
}
Ok(())
}
pub fn rewind(&mut self, graph: &mut Graph) -> GraphResult<()> {
while self.cursor > 0 {
self.cursor -= 1;
let undo = &self.commands[self.cursor].1;
println!("{:#?}", undo);
undo.apply(graph)?;
println!("{:#?}", graph);
}
Ok(())
}
}
fn three_node_add_cmds() -> Vec<DynCommand> {
vec![
Box::new(CreateNode::from(Node::literal(1.0))),
Box::new(CreateNode::from(Node::literal(2.0))),
Box::new(CreateNode::from(Node {
kind: NodeKind::Op(AtomOp::Add),
pos: glam::Vec2::ZERO,
inputs: vec![None; 2],
})),
Box::new(SetEdge {
edge: Edge {
input: SlotIndex { node: 0, slot: 0 },
output: SlotIndex { node: 2, slot: 0 },
},
}),
Box::new(SetEdge {
edge: Edge {
input: SlotIndex { node: 1, slot: 0 },
output: SlotIndex { node: 2, slot: 1 },
},
}),
]
}
#[test]
fn create_node() -> GraphResult<()> {
let mut graph = Graph::new();
let cmd = CreateNode {
kind: NodeKind::Lit(0.0),
pos: glam::Vec2::ZERO,
inputs: vec![],
};
let undo = cmd.undo(&graph)?;
cmd.apply(&mut graph)?;
assert_eq!(graph.nodes.len(), 1);
undo.apply(&mut graph)?;
assert_eq!(graph.nodes.len(), 0);
Ok(())
}
#[test]
fn three_node_add() -> GraphResult<()> {
let mut graph = Graph::new();
let cmds = three_node_add_cmds();
for cmd in cmds.into_iter() {
println!("{:#?}", cmd);
cmd.apply(&mut graph)?;
println!("{:#?}", graph);
}
graph.topological_order()?;
Ok(())
}
#[test]
fn three_node_add_rewind() -> GraphResult<()> {
let mut graph = Graph::new();
let mut history = CommandHistory::new();
let cmds = three_node_add_cmds();
history.push_multi(&mut graph, cmds)?;
graph.topological_order()?;
history.rewind(&mut graph)?;
assert_eq!(graph.nodes.len(), 0);
Ok(())
}
}

View File

@ -1,9 +1,13 @@
use slab::Slab;
use std::sync::Arc;
pub mod command;
pub mod node;
pub mod node_kind;
use node::{AtomOp, Edge, Node, NodeKind, SlotIndex};
use command::*;
use node::{Edge, Node, SlotIndex};
use node_kind::*;
#[derive(Debug)]
pub enum GraphError {
@ -16,12 +20,16 @@ pub type GraphResult<T> = Result<T, GraphError>;
#[derive(Debug)]
pub struct Graph {
pub node_kinds: Arc<NodeKindStore>,
pub nodes: Slab<Node>,
}
impl Graph {
pub fn new() -> Self {
Self { nodes: Slab::new() }
pub fn new(node_kinds: Arc<NodeKindStore>) -> Self {
Self {
node_kinds,
nodes: Slab::new(),
}
}
pub fn get_node(&self, index: usize) -> GraphResult<&Node> {
@ -34,35 +42,6 @@ impl Graph {
.ok_or(GraphError::InvalidReference)
}
pub fn compile(&self) -> GraphResult<()> {
// TODO: Only compile the nodes that are actually used (set an output node)
// Iterate through the nodes in topological order
for key in self.topological_order()? {
let node = &self.nodes[key];
match node.kind {
NodeKind::Lit(x) => {
println!("let n{}: f32 = {:?};", key, x);
}
NodeKind::Op(op) => {
let symbol = match op {
AtomOp::Add => '+',
AtomOp::Sub => '-',
AtomOp::Mul => '*',
AtomOp::Div => '/',
};
let inputs = node.unwrap_inputs()?;
let lhs = inputs[0].node;
let rhs = inputs[1].node;
println!("let n{}: f32 = n{} {} n{};", key, lhs, symbol, rhs);
}
}
}
Ok(())
}
// Return the edges that can be derived from the graph
pub fn edges(&self) -> Result<Vec<Edge>, GraphError> {
let topo_order = self.topological_order()?;
@ -139,15 +118,207 @@ impl Graph {
}
}
impl From<Vec<Node>> for Graph {
fn from(vec: Vec<Node>) -> Self {
let mut nodes = Slab::with_capacity(vec.len());
pub enum TestOpKind {
Add,
Sub,
Mul,
Div,
}
for (idx, node) in vec.into_iter().enumerate() {
let new_idx = nodes.insert(node);
assert_eq!(new_idx, idx);
pub struct TestGraphBuilder {
pub graph: Graph,
pub literal_kind: usize,
pub add_kind: usize,
pub sub_kind: usize,
pub mul_kind: usize,
pub div_kind: usize,
}
impl TestGraphBuilder {
pub fn new() -> Self {
let mut kinds = NodeKindStore { kinds: vec![] };
let literal_kind = kinds.push(NodeKind {
title: "Literal".into(),
inputs: vec![],
outputs: vec![NodeKindOutput { label: "".into() }],
});
let mut make_arith_kind = |title: &str| -> usize {
kinds.push(NodeKind {
title: title.to_string(),
inputs: vec![
NodeKindInput {
label: "A".to_string(),
},
NodeKindInput {
label: "B".to_string(),
},
],
outputs: vec![NodeKindOutput {
label: "Result".to_string(),
}],
})
};
let add_kind = make_arith_kind("Add");
let sub_kind = make_arith_kind("Subtract");
let mul_kind = make_arith_kind("Multiply");
let div_kind = make_arith_kind("Divide");
let graph = Graph::new(Arc::new(kinds));
Self {
graph,
literal_kind,
add_kind,
sub_kind,
mul_kind,
div_kind,
}
}
pub fn add(&mut self, node: Node) -> usize {
self.graph.nodes.insert(node)
}
Self { nodes }
pub fn add_literal(&mut self, value: f32) -> usize {
self.graph.nodes.insert(self.make_literal(value))
}
pub fn make_literal(&self, _value: f32) -> Node {
Node {
kind: self.literal_kind,
pos: glam::Vec2::ZERO,
inputs: vec![],
}
}
pub fn add_op(&mut self, kind: TestOpKind, a: usize, b: usize) -> usize {
self.graph.nodes.insert(self.make_op(kind, a, b))
}
pub fn make_op(&self, kind: TestOpKind, a: usize, b: usize) -> Node {
let kind = match kind {
TestOpKind::Add => self.add_kind,
TestOpKind::Sub => self.sub_kind,
TestOpKind::Mul => self.mul_kind,
TestOpKind::Div => self.div_kind,
};
Node {
kind,
pos: glam::Vec2::ZERO,
inputs: vec![
Some(SlotIndex { node: a, slot: 0 }),
Some(SlotIndex { node: b, slot: 0 }),
],
}
}
pub fn three_node_add_cmds(&self) -> Vec<DynCommand> {
vec![
Box::new(CreateNode::from(self.make_literal(1.0))),
Box::new(CreateNode::from(self.make_literal(2.0))),
Box::new(CreateNode::from(Node {
kind: self.add_kind,
pos: glam::Vec2::ZERO,
inputs: vec![None; 2],
})),
Box::new(SetEdge {
edge: Edge {
input: SlotIndex { node: 0, slot: 0 },
output: SlotIndex { node: 2, slot: 0 },
},
}),
Box::new(SetEdge {
edge: Edge {
input: SlotIndex { node: 1, slot: 0 },
output: SlotIndex { node: 2, slot: 1 },
},
}),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
impl CommandHistory {
pub fn push_multi(
&mut self,
graph: &mut Graph,
cmds: impl IntoIterator<Item = DynCommand>,
) -> GraphResult<()> {
for cmd in cmds {
println!("{:#?}", cmd);
self.push(graph, cmd)?;
println!("{:#?}", graph);
}
Ok(())
}
pub fn rewind(&mut self, graph: &mut Graph) -> GraphResult<()> {
while self.cursor > 0 {
self.cursor -= 1;
let undo = &self.commands[self.cursor].1;
println!("{:#?}", undo);
undo.apply(graph)?;
println!("{:#?}", graph);
}
Ok(())
}
}
#[test]
fn create_node() -> GraphResult<()> {
let mut builder = TestGraphBuilder::new();
let cmd = CreateNode {
kind: builder.literal_kind,
pos: glam::Vec2::ZERO,
inputs: vec![],
};
let undo = cmd.undo(&builder.graph)?;
cmd.apply(&mut builder.graph)?;
assert_eq!(builder.graph.nodes.len(), 1);
undo.apply(&mut builder.graph)?;
assert_eq!(builder.graph.nodes.len(), 0);
Ok(())
}
#[test]
fn three_node_add() -> GraphResult<()> {
let mut builder = TestGraphBuilder::new();
let cmds = builder.three_node_add_cmds();
for cmd in cmds.into_iter() {
println!("{:#?}", cmd);
cmd.apply(&mut builder.graph)?;
println!("{:#?}", builder.graph);
}
builder.graph.topological_order()?;
Ok(())
}
#[test]
fn three_node_add_rewind() -> GraphResult<()> {
let mut builder = TestGraphBuilder::new();
let mut history = CommandHistory::new();
let cmds = builder.three_node_add_cmds();
history.push_multi(&mut builder.graph, cmds)?;
builder.graph.topological_order()?;
history.rewind(&mut builder.graph)?;
assert_eq!(builder.graph.nodes.len(), 0);
Ok(())
}
}

View File

@ -2,7 +2,7 @@ use crate::{GraphError, GraphResult};
#[derive(Debug, Clone)]
pub struct Node {
pub kind: NodeKind,
pub kind: usize,
pub pos: glam::Vec2,
pub inputs: Vec<Option<SlotIndex>>,
}
@ -22,27 +22,6 @@ impl Node {
Ok(inputs)
}
/// Temporary helper function for writing tests
pub fn literal(val: f32) -> Self {
Self {
kind: NodeKind::Lit(val),
pos: glam::Vec2::ZERO,
inputs: vec![],
}
}
/// Temporary helper function for writing tests
pub fn op(op: AtomOp, lhs: usize, rhs: usize) -> Self {
let lhs = SlotIndex { node: lhs, slot: 0 };
let rhs = SlotIndex { node: rhs, slot: 0 };
Self {
kind: NodeKind::Op(op),
pos: glam::Vec2::ZERO,
inputs: vec![Some(lhs), Some(rhs)],
}
}
pub fn at(self, pos: glam::Vec2) -> Self {
Self {
pos,
@ -51,20 +30,6 @@ impl Node {
}
}
#[derive(Copy, Debug, Clone)]
pub enum NodeKind {
Lit(f32),
Op(AtomOp),
}
#[derive(Copy, Debug, Clone)]
pub enum AtomOp {
Add,
Sub,
Mul,
Div,
}
#[derive(Copy, Debug, Clone, Hash, PartialEq, Eq)]
pub struct SlotIndex {
pub node: usize,

29
ramen/src/node_kind.rs Normal file
View File

@ -0,0 +1,29 @@
#[derive(Debug)]
pub struct NodeKindStore {
pub kinds: Vec<NodeKind>,
}
impl NodeKindStore {
pub fn push(&mut self, kind: NodeKind) -> usize {
let idx = self.kinds.len();
self.kinds.push(kind);
idx
}
}
#[derive(Debug)]
pub struct NodeKind {
pub title: String,
pub inputs: Vec<NodeKindInput>,
pub outputs: Vec<NodeKindOutput>,
}
#[derive(Debug)]
pub struct NodeKindInput {
pub label: String,
}
#[derive(Debug)]
pub struct NodeKindOutput {
pub label: String,
}

View File

@ -1,6 +1,5 @@
use eframe::egui;
use ramen::node::{AtomOp, Node};
use ramen::Graph;
use ramen::{TestGraphBuilder, TestOpKind};
use ramen_egui::NodeEditor;
fn main() {
@ -23,32 +22,31 @@ impl Application {
let scale = 250.0;
let pos = |x, y| glam::Vec2::new(x, y) * scale;
let mut graph = Graph::new();
let nodes = &mut graph.nodes;
let n0 = nodes.insert(Node::literal(1.0).at(pos(1.0, 1.0)));
let n1 = nodes.insert(Node::literal(2.0).at(pos(1.0, 2.0)));
let n2 = nodes.insert(Node::op(AtomOp::Add, n0, n1).at(pos(2.0, 1.5)));
let n3 = nodes.insert(Node::literal(4.0).at(pos(2.0, 2.5)));
let n4 = nodes.insert(Node::op(AtomOp::Div, n2, n3).at(pos(3.0, 2.0)));
let n5 = nodes.insert(Node::op(AtomOp::Mul, n4, n4).at(pos(4.0, 2.0)));
let _n6 = nodes.insert(Node::op(AtomOp::Add, n5, n0).at(pos(3.0, 1.0)));
let mut builder = TestGraphBuilder::new();
let n0 = builder.add(builder.make_literal(1.0).at(pos(1.0, 1.0)));
let n1 = builder.add(builder.make_literal(2.0).at(pos(1.0, 2.0)));
let n2 = builder.add(builder.make_op(TestOpKind::Add, n0, n1).at(pos(2.0, 1.5)));
let n3 = builder.add(builder.make_literal(4.0).at(pos(2.0, 2.5)));
let n4 = builder.add(builder.make_op(TestOpKind::Div, n2, n3).at(pos(3.0, 2.0)));
let n5 = builder.add(builder.make_op(TestOpKind::Mul, n4, n4).at(pos(4.0, 2.0)));
let _n6 = builder.add(builder.make_op(TestOpKind::Add, n5, n0).at(pos(3.0, 1.0)));
Self {
editor: NodeEditor::from_graph(graph),
editor: NodeEditor::from_graph(builder.graph),
}
}
}
impl eframe::App for Application {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::TopBottomPanel::top("menu_panel").show(ctx, |ui| {
egui::menu::bar(ui, |ui| {
egui::widgets::global_dark_light_mode_switch(ui);
if ui.button("Undo").clicked() {
self.editor.undo();
}
if ui.button("Redo").clicked() {
self.editor.redo();
}