Support optional input slots

This commit is contained in:
mars 2022-09-17 04:26:14 -06:00
parent 13142ee210
commit 4f3363fc0f
3 changed files with 181 additions and 72 deletions

View File

@ -1,4 +1,5 @@
use super::{Graph, GraphError, GraphResult, Node, NodeKind};
use super::{Graph, GraphError, GraphResult};
use crate::node::{Edge, Node, NodeKind, SlotIndex};
pub trait Command {
fn undo(&self, graph: &Graph) -> GraphResult<DynCommand>;
@ -11,7 +12,7 @@ pub type DynCommand = Box<dyn Command>;
pub struct CreateNode {
pub kind: NodeKind,
pub pos: glam::Vec2,
pub inputs_num: usize,
pub inputs: Vec<Option<SlotIndex>>,
}
impl Command for CreateNode {
@ -25,7 +26,7 @@ impl Command for CreateNode {
let node = Node {
kind: self.kind,
pos: self.pos,
inputs: vec![0; self.inputs_num],
inputs: self.inputs.clone(),
};
graph.nodes.insert(node);
@ -46,7 +47,7 @@ impl Command for DeleteNode {
let undo = CreateNode {
kind: target.kind,
pos: target.pos,
inputs_num: target.inputs.len(),
inputs: target.inputs.clone(),
};
Ok(Box::new(undo))
@ -88,39 +89,83 @@ impl Command for MoveNode {
#[derive(Clone, Debug)]
pub struct SetEdge {
pub target_node: usize,
pub slot: usize,
pub input_node: usize,
pub edge: Edge,
}
impl Command for SetEdge {
fn undo(&self, graph: &Graph) -> GraphResult<DynCommand> {
let target = graph.get_node(self.target_node)?;
let target = graph.get_node(self.edge.output.node)?;
let old_input = target
.inputs
.get(self.slot)
.get(self.edge.output.slot)
.ok_or(GraphError::InvalidReference)?;
let undo = Self {
target_node: self.target_node,
slot: self.slot,
input_node: *old_input,
Ok(match old_input {
Some(old_input) => Box::new(SetEdge {
edge: Edge {
input: *old_input,
output: self.edge.output,
},
}),
None => Box::new(DeleteEdge {
input: self.edge.output,
}),
})
}
fn apply(&self, graph: &mut Graph) -> GraphResult<()> {
let target = graph.get_node_mut(self.edge.output.node)?;
let input = target
.inputs
.get_mut(self.edge.input.slot)
.ok_or(GraphError::InvalidReference)?;
*input = Some(self.edge.output);
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct DeleteEdge {
pub input: SlotIndex,
}
impl Command for DeleteEdge {
fn undo(&self, graph: &Graph) -> GraphResult<DynCommand> {
let target = graph.get_node(self.input.node)?;
let old_input = target
.inputs
.get(self.input.slot)
.ok_or(GraphError::InvalidReference)?
.ok_or(GraphError::MissingEdge)?;
let undo = SetEdge {
edge: Edge {
input: old_input,
output: self.input,
},
};
Ok(Box::new(undo))
}
fn apply(&self, graph: &mut Graph) -> GraphResult<()> {
let target = graph.get_node_mut(self.target_node)?;
let target = graph.get_node_mut(self.input.node)?;
let input = target
.inputs
.get_mut(self.slot)
.get_mut(self.input.slot)
.ok_or(GraphError::InvalidReference)?;
*input = self.input_node;
Ok(())
if input.is_some() {
Err(GraphError::MissingEdge)
} else {
*input = None;
Ok(())
}
}
}

View File

@ -1,64 +1,47 @@
use slab::Slab;
pub mod command;
pub mod node;
use node::{AtomOp, Edge, Node, NodeKind, SlotIndex};
fn main() {
// Create a dummy node position for testing
let pos = glam::Vec2::ZERO;
// Represent the graph as adjacency lists
let graph: Graph =
// (1 + 2) / 4
vec![
Node { pos, kind: NodeKind::Lit(1.0), inputs: vec![] },
Node { pos, kind: NodeKind::Lit(2.0), inputs: vec![] },
Node { pos, kind: NodeKind::Op(AtomOp::Add), inputs: vec![0, 1] },
Node { pos, kind: NodeKind::Lit(4.0), inputs: vec![] },
Node { pos, kind: NodeKind::Op(AtomOp::Div), inputs: vec![2, 3] },
Node { pos, kind: NodeKind::Op(AtomOp::Mul), inputs: vec![4, 4] },
Node { pos, kind: NodeKind::Op(AtomOp::Add), inputs: vec![5, 0] },
Node { pos, kind: NodeKind::Lit(0.0), inputs: vec![] }, // Should be ignored since it's not used
]
.into();
let mut graph = Graph::new();
let nodes = &mut graph.nodes;
let n0 = nodes.insert(Node::literal(1.0));
let n1 = nodes.insert(Node::literal(2.0));
let n2 = nodes.insert(Node::op(AtomOp::Add, n0, n1));
let n3 = nodes.insert(Node::literal(4.0));
let n4 = nodes.insert(Node::op(AtomOp::Div, n2, n3));
let n5 = nodes.insert(Node::op(AtomOp::Mul, n4, n4));
let n6 = nodes.insert(Node::op(AtomOp::Add, n5, n0));
let n7 = nodes.insert(Node::literal(0.0)); // Should be ignored since it's not used
graph.compile().expect("Compilation failure");
}
#[derive(Debug, Clone)]
pub struct Node {
pub kind: NodeKind,
pub pos: glam::Vec2,
pub inputs: Vec<usize>,
}
#[derive(Copy, Debug, Clone)]
pub enum NodeKind {
Lit(f32),
Op(AtomOp),
}
#[derive(Debug)]
pub enum GraphError {
InvalidReference,
MissingEdge,
Cyclic,
}
pub type GraphResult<T> = Result<T, GraphError>;
#[derive(Copy, Debug, Clone)]
pub enum AtomOp {
Add,
Sub,
Mul,
Div,
}
#[derive(Debug)]
pub struct Graph {
pub nodes: Slab<Node>,
}
impl Graph {
fn new() -> Self {
Self {
nodes: Slab::new()
}
}
fn get_node(&self, index: usize) -> GraphResult<&Node> {
self.nodes.get(index).ok_or(GraphError::InvalidReference)
}
@ -69,15 +52,14 @@ impl Graph {
.ok_or(GraphError::InvalidReference)
}
fn compile(&self) -> Result<(), GraphError> {
// TODO: Only compile the nodes that are actually used
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 {
match node.kind {
NodeKind::Lit(x) => {
//
println!("let n{}: f32 = {:?};", key, x);
}
NodeKind::Op(op) => {
@ -87,10 +69,11 @@ impl Graph {
AtomOp::Mul => '*',
AtomOp::Div => '/',
};
println!(
"let n{}: f32 = n{} {} n{};",
key, &node.inputs[0], symbol, &node.inputs[1]
);
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);
}
}
}
@ -99,13 +82,23 @@ impl Graph {
}
// Return the edges that can be derived from the graph
fn edges(&self) -> Result<Vec<(usize, usize)>, GraphError> {
fn edges(&self) -> Result<Vec<Edge>, GraphError> {
let topo_order = self.topological_order()?;
let mut edges: Vec<(usize, usize)> = Vec::new();
for (i, node) in topo_order.iter().enumerate() {
for j in &self.nodes[*node].inputs {
edges.push((*j, i));
let mut edges = Vec::new();
for (node_idx, node) in topo_order.iter().enumerate() {
for (slot_idx, input) in self.nodes[*node].inputs.iter().enumerate() {
if let Some(input) = input {
let output = SlotIndex {
node: node_idx,
slot: slot_idx,
};
edges.push(Edge {
input: *input,
output,
});
}
}
}
@ -119,8 +112,8 @@ impl Graph {
// Forward-directed adjacency lists
let mut adjacency_lists: Vec<Vec<usize>> = vec![Vec::new(); v];
for (i, node) in self.nodes.iter() {
for j in &node.inputs {
adjacency_lists[*j].push(i);
for j in node.inputs.iter().flatten() {
adjacency_lists[j.node].push(i);
}
}

71
ramen/src/node.rs Normal file
View File

@ -0,0 +1,71 @@
use crate::{GraphError, GraphResult};
#[derive(Debug, Clone)]
pub struct Node {
pub kind: NodeKind,
pub pos: glam::Vec2,
pub inputs: Vec<Option<SlotIndex>>,
}
impl Node {
pub fn unwrap_inputs(&self) -> GraphResult<Vec<SlotIndex>> {
let mut inputs = Vec::with_capacity(self.inputs.len());
for input in self.inputs.iter() {
if let Some(input) = input {
inputs.push(*input);
} else {
return Err(GraphError::MissingEdge);
}
}
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)],
}
}
}
#[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, PartialEq, Eq)]
pub struct SlotIndex {
pub node: usize,
pub slot: usize,
}
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
pub struct Edge {
pub input: SlotIndex,
pub output: SlotIndex,
}