diff options
| author | s-ol <s+removethis@s-ol.nu> | 2026-01-23 19:41:46 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2026-01-24 15:26:05 +0000 |
| commit | 7155e060a45af38f3c2b141cc898452762584f60 (patch) | |
| tree | 8c797f7436d66cb855900aa3feb81eb4d905b36e /src | |
| parent | Error checking (diff) | |
| download | nodetoy-7155e060a45af38f3c2b141cc898452762584f60.tar.gz nodetoy-7155e060a45af38f3c2b141cc898452762584f60.zip | |
refactor node signature interface
Diffstat (limited to 'src')
| -rw-r--r-- | src/library.rs | 251 | ||||
| -rw-r--r-- | src/main.rs | 84 | ||||
| -rw-r--r-- | src/node.rs | 32 | ||||
| -rw-r--r-- | src/snarl_ext.rs | 100 | ||||
| -rw-r--r-- | src/types.rs | 75 | ||||
| -rw-r--r-- | src/wasm.rs | 2 |
6 files changed, 346 insertions, 198 deletions
diff --git a/src/library.rs b/src/library.rs index 65929a0..e434170 100644 --- a/src/library.rs +++ b/src/library.rs @@ -1,9 +1,9 @@ use enum_dispatch::enum_dispatch; use itertools::{Itertools, izip}; use serde::{Deserialize, Serialize}; -use std::fmt; +use std::{fmt, iter}; -use crate::node::{CompileError, ConcreteNode}; +use crate::node::{CompileError, ConcreteNode, MAX_INPUTS}; use crate::types::{ Dimension, Dimension::*, FloatPrecision::*, ScalarType, ScalarType::*, Type, Type::*, TypeSignature, @@ -66,10 +66,10 @@ trait FixedNode: fmt::Debug { fn compile( &self, + f: &mut dyn fmt::Write, signature: TypeSignature, inputs: Vec<String>, outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError> { let mut pairs = signature.outputs.iter().zip(outputs.iter()); let (out_typ, out_name) = pairs.next().expect("no output"); @@ -88,34 +88,34 @@ trait FixedNode: fmt::Debug { } impl<T: FixedNode> ConcreteNode for T { - fn max_inputs(&self) -> usize { - self.all_signatures().map(|s| s.inputs.len()).max().unwrap() - } - - // set of possible input type combinations given current connections - fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature> { - self.all_signatures() - .filter(|sig| sig.matches_inputs(connected)) - .collect() + fn visible_inputs(&self, connected: &[Option<Type>; MAX_INPUTS]) -> Vec<Option<Type>> { + let signature = self + .all_signatures() + .into_iter() + .find(|sig| sig.matches_inputs(connected)) + .unwrap_or_else(|| self.all_signatures().next().expect("need one signature")); + signature.inputs.into_iter().map(Some).collect() } - fn signature(&self, connected: &[Option<Type>]) -> TypeSignature { + fn signature( + &self, + connected: &[Option<Type>; MAX_INPUTS], + ) -> Result<TypeSignature, CompileError> { self.all_signatures() - .into_iter() .find(|sig| sig.matches_inputs(connected)) - .unwrap_or_else(|| self.all_signatures().next().expect("need one signature")) + .ok_or(CompileError::MissingArguments) } fn compile( &self, + f: &mut dyn fmt::Write, signature: TypeSignature, inputs: Vec<Option<String>>, outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError> { let inputs: Option<Vec<String>> = inputs.into_iter().collect(); if let Some(inputs) = inputs { - self.compile(signature, inputs, outputs, f) + self.compile(f, signature, inputs, outputs) } else { Err(CompileError::MissingArguments) } @@ -176,7 +176,7 @@ pub enum BinArithmetic { Divide, } -fn check_gentype(inputs: &[Option<Type>]) -> Result<Option<Type>, ()> { +fn check_gentype_up(inputs: &[Option<Type>]) -> Result<Option<Type>, CompileError> { let mut seen: Option<Type> = None; for input in inputs { @@ -190,52 +190,61 @@ fn check_gentype(inputs: &[Option<Type>]) -> Result<Option<Type>, ()> { Ok(seen) } -impl ConcreteNode for BinArithmetic { - fn max_inputs(&self) -> usize { - 10 - } +fn check_gentype_down(inputs: &[Option<Type>]) -> Result<Option<ScalarType>, CompileError> { + let mut seen: Option<ScalarType> = None; - fn num_inputs(&self, connected: &[Option<Type>]) -> usize { - connected - .iter() - .enumerate() - .filter(|(_, v)| v.is_some()) - .map(|(i, _)| i + 2) - .last() - .unwrap_or(2) + for input in inputs { + seen = match (seen, input) { + (a, None) => a, + (None, Some(b)) => Some(b.scalar()), + (Some(a), Some(b)) => Some(b.downcast_gentype(a)?), + } } - fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature> { - if let Ok(out) = check_gentype(connected) { - let out = out.unwrap_or_default(); + Ok(seen) +} + +fn last_connected(connected: &[Option<Type>]) -> Option<usize> { + connected + .iter() + .enumerate() + .filter(|(_, v)| v.is_some()) + .map(|(i, _)| i) + .next_back() +} - vec![TypeSignature { - inputs: connected.iter().map(|v| v.unwrap_or_default()).collect(), - outputs: Box::new([out]), - }] +impl ConcreteNode for BinArithmetic { + fn visible_inputs(&self, connected: &[Option<Type>; MAX_INPUTS]) -> Vec<Option<Type>> { + let typ = check_gentype_up(connected).ok().flatten(); + let num = last_connected(connected).map_or(1, |i| i + 1); + + let inputs = connected.iter().take(num).copied().map(|t| t.or(typ)); + if num < MAX_INPUTS { + inputs.chain(iter::once(None)).collect() } else { - vec![] + inputs.collect() } } - fn signature(&self, connected: &[Option<Type>]) -> TypeSignature { - let out = check_gentype(connected) - .ok() - .flatten() - .unwrap_or(Scalar(Float(Single))); - - TypeSignature { - inputs: connected.iter().map(|v| v.unwrap_or(out)).collect(), - outputs: Box::new([out]), - } + fn signature( + &self, + connected: &[Option<Type>; MAX_INPUTS], + ) -> Result<TypeSignature, CompileError> { + let typ = check_gentype_up(connected)?.ok_or(CompileError::MissingArguments)?; + let num = last_connected(connected).map_or(2, |i| i + 2); + + Ok(TypeSignature { + inputs: (0..num).map(|i| connected[i].unwrap_or(typ)).collect(), + outputs: Box::new([typ]), + }) } fn compile( &self, + f: &mut dyn fmt::Write, signature: TypeSignature, inputs: Vec<Option<String>>, outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError> { let out_typ = &signature.outputs[0]; let out_name = &outputs[0]; @@ -245,7 +254,7 @@ impl ConcreteNode for BinArithmetic { Self::Multiply => " * ", Self::Divide => " / ", }; - let expr = inputs.iter().filter_map(|s| s.as_ref()).join(sym); + let expr = inputs.into_iter().flatten().join(sym); writeln!(f, "{out_typ} {out_name} = {expr};")?; Ok(()) } @@ -394,7 +403,7 @@ impl FixedNode for Thru1 { pub struct Pow; impl Pow { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Pow) + iter::once(Pow) } } impl FixedNode for Pow { @@ -412,7 +421,7 @@ impl FixedNode for Pow { pub struct Mod; impl Mod { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Mod) + iter::once(Mod) } } impl FixedNode for Mod { @@ -437,7 +446,7 @@ impl FixedNode for Mod { pub struct Modf; impl Modf { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Modf) + iter::once(Modf) } } impl FixedNode for Modf { @@ -482,7 +491,7 @@ impl FixedNode for MinMax { pub struct Clamp; impl Clamp { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Clamp) + iter::once(Clamp) } } impl FixedNode for Clamp { @@ -506,7 +515,7 @@ impl FixedNode for Clamp { pub struct Mix; impl Mix { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Self) + iter::once(Self) } } impl FixedNode for Mix { @@ -537,7 +546,7 @@ impl FixedNode for Mix { pub struct Step; impl Step { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Self) + iter::once(Self) } } impl FixedNode for Step { @@ -561,7 +570,7 @@ impl FixedNode for Step { pub struct Smoothstep; impl Smoothstep { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Self) + iter::once(Self) } } impl FixedNode for Smoothstep { @@ -585,7 +594,7 @@ impl FixedNode for Smoothstep { pub struct Length; impl Length { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Self) + iter::once(Self) } } impl FixedNode for Length { @@ -624,7 +633,7 @@ impl FixedNode for VecVecToScalar { pub struct Cross; impl Cross { pub fn all() -> impl Iterator<Item = Self> { - std::iter::once(Self) + iter::once(Self) } } impl FixedNode for Cross { @@ -659,7 +668,7 @@ impl NodeIndex for BuiltinFunction { const TITLE: &'static str = "Builtin Functions"; fn all() -> impl Iterator<Item = Self> { - std::iter::empty() + iter::empty() .chain(Thru1::all().map(Self::from)) .chain(Mod::all().map(Self::from)) .chain(Modf::all().map(Self::from)) @@ -789,10 +798,10 @@ impl FixedNode for Output { fn compile( &self, + f: &mut dyn fmt::Write, signature: TypeSignature, inputs: Vec<String>, _outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError> { match signature.inputs[0] { Vector(_, D2) => writeln!(f, "out_color = vec4({}, 0.0, 1.0);", inputs[0])?, @@ -808,7 +817,7 @@ impl NodeIndex for Output { const TITLE: &'static str = "Output"; fn all() -> impl Iterator<Item = Self> { - std::iter::once(Output) + iter::once(Output) } fn pick_node(ui: &mut egui::Ui) -> Option<Self> { @@ -912,7 +921,7 @@ where Constant<T>: ScalarValue, T: fmt::Display, { - const TYPE: Type = Vector(Constant::<T>::SCALAR_TYPE, Dimension::from(N)); + const TYPE: Type = Vector(Constant::<T>::SCALAR_TYPE, Dimension::from_const(N)); } impl<T, const N: usize> ValueEditor for Constant<[T; N]> @@ -953,24 +962,23 @@ impl<T> ConcreteNode for Constant<T> where Constant<T>: ValueEditor, { - fn max_inputs(&self) -> usize { - 0 - } - - fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature> { - vec![self.signature(connected)] + fn visible_inputs(&self, _connected: &[Option<Type>; MAX_INPUTS]) -> Vec<Option<Type>> { + vec![] } - fn signature(&self, _connected: &[Option<Type>]) -> TypeSignature { - TypeSignature::new([], [Self::TYPE]) + fn signature( + &self, + _connected: &[Option<Type>; MAX_INPUTS], + ) -> Result<TypeSignature, CompileError> { + Ok(TypeSignature::new([], [Self::TYPE])) } fn compile( &self, + f: &mut dyn fmt::Write, _signature: TypeSignature, _inputs: Vec<Option<String>>, outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError> { let out_typ = Self::TYPE; let out_name = &outputs[0]; @@ -1102,30 +1110,29 @@ pub enum Input { } impl ConcreteNode for Input { - fn max_inputs(&self) -> usize { - 0 - } - - fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature> { - vec![self.signature(connected)] + fn visible_inputs(&self, _connected: &[Option<Type>; MAX_INPUTS]) -> Vec<Option<Type>> { + vec![] } - fn signature(&self, _connected: &[Option<Type>]) -> TypeSignature { - TypeSignature::new( + fn signature( + &self, + _connected: &[Option<Type>; MAX_INPUTS], + ) -> Result<TypeSignature, CompileError> { + Ok(TypeSignature::new( [], [match self { Self::UV | Self::Resolution => Vector(Float(Single), D2), Self::Time => Scalar(Float(Single)), }], - ) + )) } fn compile( &self, + f: &mut dyn fmt::Write, signature: TypeSignature, _inputs: Vec<Option<String>>, outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError> { let out_typ = &signature.outputs[0]; let out_name = &outputs[0]; @@ -1174,10 +1181,10 @@ impl FixedNode for SplitVector { fn compile( &self, + f: &mut dyn fmt::Write, signature: TypeSignature, inputs: Vec<String>, outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError> { let input = &inputs[0]; for (typ, name, component) in izip!( @@ -1199,39 +1206,69 @@ impl fmt::Display for SplitVector { #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CombineVector; -impl FixedNode for CombineVector { - fn all_signatures(&self) -> Box<dyn Iterator<Item = TypeSignature>> { - Box::new(SCALAR_TYPES.into_iter().flat_map(|s| { - [ - TypeSignature::new([Scalar(s)], [Scalar(s)]), - TypeSignature::new([Vector(s, D2)], [Vector(s, D2)]), - TypeSignature::new([Vector(s, D3)], [Vector(s, D3)]), - TypeSignature::new([Vector(s, D4)], [Vector(s, D4)]), - TypeSignature::new([Scalar(s); 2], [Vector(s, D2)]), - TypeSignature::new([Scalar(s), Vector(s, D2)], [Vector(s, D3)]), - TypeSignature::new([Vector(s, D2), Scalar(s)], [Vector(s, D3)]), - TypeSignature::new([Scalar(s), Vector(s, D3)], [Vector(s, D4)]), - TypeSignature::new([Vector(s, D3), Scalar(s)], [Vector(s, D4)]), - TypeSignature::new([Scalar(s); 3], [Vector(s, D3)]), - TypeSignature::new([Scalar(s), Scalar(s), Vector(s, D2)], [Vector(s, D4)]), - TypeSignature::new([Scalar(s), Vector(s, D2), Scalar(s)], [Vector(s, D4)]), - TypeSignature::new([Vector(s, D2), Scalar(s), Scalar(s)], [Vector(s, D4)]), - TypeSignature::new([Vector(s, D2), Vector(s, D2)], [Vector(s, D4)]), - TypeSignature::new([Scalar(s); 4], [Vector(s, D4)]), - ] - })) +fn without_tail<'a, T>( + inputs: impl DoubleEndedIterator<Item = &'a Option<T>>, +) -> Vec<&'a Option<T>> { + let mut reversed: Vec<_> = inputs.rev().skip_while(|v| v.is_none()).collect(); + reversed.reverse(); + reversed +} + +impl ConcreteNode for CombineVector { + fn visible_inputs(&self, connected: &[Option<Type>; MAX_INPUTS]) -> Vec<Option<Type>> { + let typ = check_gentype_down(connected).ok().flatten().map(Scalar); + let num = last_connected(connected).map_or(1, |i| i + 1); + + let inputs = connected.iter().take(num).copied().map(|t| t.or(typ)); + if num < 4 { + inputs.chain(iter::once(None)).collect() + } else { + inputs.collect() + } + } + + fn signature( + &self, + connected: &[Option<Type>; MAX_INPUTS], + ) -> Result<TypeSignature, CompileError> { + let scalar = check_gentype_down(connected)?.ok_or(CompileError::MissingArguments)?; + + let mut num: usize = 0; + for input in without_tail(connected.iter()) { + num += if let Some(t) = input { + t.num_components() + } else { + 1 + }; + } + let dim = Dimension::try_from(num).map_err(|_| CompileError::InvalidArguments)?; + + Ok(TypeSignature { + inputs: connected + .iter() + .map(|v| v.unwrap_or(Scalar(scalar))) + .collect(), + outputs: Box::new([Vector(scalar, dim)]), + }) } fn compile( &self, + f: &mut dyn fmt::Write, signature: TypeSignature, - inputs: Vec<String>, + inputs: Vec<Option<String>>, outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError> { let out_typ = &signature.outputs[0]; let out_name = &outputs[0]; - let params = inputs.iter().join(", "); + let params = without_tail(inputs.iter()) + .into_iter() + .enumerate() + .map(|(i, v)| { + v.clone() + .unwrap_or_else(|| signature.inputs[i].default_value()) + }) + .join(", "); writeln!(f, "{out_typ} {out_name} = {out_typ}({params});")?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index ad81cea..8f77953 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,8 @@ use node::{AnyNode, ConcreteNode}; use snarl_ext::{Compilable, GraphError, WithSignatures}; use types::{Dimension, FloatPrecision, ScalarType, Type}; +use crate::node::MAX_INPUTS; + impl Into<PinInfo> for Type { fn into(self) -> PinInfo { match self.scalar() { @@ -41,7 +43,8 @@ impl Into<PinInfo> for Type { } } -static ERROR_COLOR: Color32 = Color32::from_rgb(0xc0, 0x00, 0x00); +static COLOR_ERROR: Color32 = Color32::from_rgb(0xc0, 0x00, 0x00); +static COLOR_INACTIVE: Color32 = Color32::from_rgb(0x90, 0x90, 0x90); pub struct Viewer { dirty: bool, @@ -68,22 +71,24 @@ impl Default for Viewer { impl SnarlViewer<AnyNode> for Viewer { #[inline] fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<AnyNode>) { - let to_node = &snarl[to.id.node]; - - let mut inputs = snarl.get_input_types(to.id.node); - inputs[to.id.input] = Some(snarl.out_pin_type(from.id)); - let signatures = to_node.signatures_matching(&inputs[..]); + if let Ok(from_signature) = snarl.get_node_signature(from.id.node) { + let mut overrides: [Option<Type>; _] = [None; MAX_INPUTS]; + overrides[to.id.input] = Some(from_signature.outputs[from.id.output]); + + if snarl + .get_node_signature_with_overrides(to.id.node, &overrides) + .is_err() + { + return; + } - if signatures.is_empty() { - return; - } + for &remote in &to.remotes { + snarl.disconnect(remote, to.id); + } - for &remote in &to.remotes { - snarl.disconnect(remote, to.id); + snarl.connect(from.id, to.id); + self.dirty = true; } - - snarl.connect(from.id, to.id); - self.dirty = true; } fn disconnect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<AnyNode>) { @@ -104,27 +109,42 @@ impl SnarlViewer<AnyNode> for Viewer { } fn inputs(&mut self, id: NodeId, snarl: &Snarl<AnyNode>) -> usize { - snarl.get_num_inputs(id) + snarl.get_visible_inputs(id).len() } fn outputs(&mut self, id: NodeId, snarl: &Snarl<AnyNode>) -> usize { - snarl.get_node_signature(id).outputs.len() + snarl.get_node_signature(id).map_or(1, |s| s.outputs.len()) } #[allow(clippy::too_many_lines)] #[allow(refining_impl_trait)] - fn show_input(&mut self, pin: &InPin, _ui: &mut Ui, snarl: &mut Snarl<AnyNode>) -> PinInfo { - match snarl.in_pin_type(pin.id) { - None => PinInfo::star().with_fill(Color32::from_rgb(0xb0, 0xb0, 0xb0)), + fn show_input(&mut self, pin: &InPin, ui: &mut Ui, snarl: &mut Snarl<AnyNode>) -> PinInfo { + _ = ui; + let typ = snarl.get_visible_inputs(pin.id.node)[pin.id.input]; + + #[cfg(feature = "debug-ui")] + if let Some(label) = typ { + ui.label(format!("{label}")); + } + + match typ { + None => PinInfo::circle() + .with_fill(COLOR_INACTIVE) + .with_stroke(egui::Stroke::NONE), Some(t) => t.into(), } } #[allow(refining_impl_trait)] fn show_output(&mut self, pin: &OutPin, ui: &mut Ui, snarl: &mut Snarl<AnyNode>) -> PinInfo { - let typ = snarl.out_pin_type(pin.id); - ui.label(format!("{typ}")); - typ.into() + if let Ok(signature) = snarl.get_node_signature(pin.id.node) { + let typ = signature.outputs[pin.id.output]; + ui.label(format!("{typ}")); + typ.into() + } else { + ui.label("error"); + PinInfo::star().with_fill(COLOR_ERROR) + } } fn has_body(&mut self, _node: &AnyNode) -> bool { @@ -144,6 +164,23 @@ impl SnarlViewer<AnyNode> for Viewer { } } + #[cfg(feature = "debug-ui")] + fn has_footer(&mut self, _node: &AnyNode) -> bool { + true + } + + #[cfg(feature = "debug-ui")] + fn show_footer( + &mut self, + node: NodeId, + _inputs: &[InPin], + _outputs: &[OutPin], + ui: &mut Ui, + _snarl: &mut Snarl<AnyNode>, + ) { + ui.label(format!("node ID: {node:?}")); + } + fn node_frame( &mut self, mut default: egui::Frame, @@ -155,7 +192,7 @@ impl SnarlViewer<AnyNode> for Viewer { if Some(node) == self.error.and_then(|e| e.1) { default.stroke = egui::Stroke { width: 2.0, - color: ERROR_COLOR, + color: COLOR_ERROR, }; } @@ -419,6 +456,7 @@ impl eframe::App for App { }; egui::SidePanel::right("preview").show(ctx, |ui| { + #[cfg(not(feature = "debug-ui"))] egui::Frame::canvas(ui.style()).show(ui, |ui| { if self.preview.update(ui, shader) { self.viewer.dirty = false; diff --git a/src/node.rs b/src/node.rs index b63270e..a279eb9 100644 --- a/src/node.rs +++ b/src/node.rs @@ -8,6 +8,7 @@ use crate::types::{Type, TypeSignature}; #[derive(Debug, Copy, Clone)] pub enum CompileError { MissingArguments, + InvalidArguments, Codegen(fmt::Error), } @@ -18,26 +19,39 @@ impl From<fmt::Error> for CompileError { } /// an instantiable Node, parametrized by its connected input types +pub const MAX_INPUTS: usize = 16; + #[enum_dispatch] pub trait ConcreteNode { - fn max_inputs(&self) -> usize; - fn num_inputs(&self, _connected: &[Option<Type>]) -> usize { - self.max_inputs() - } - - // set of possible input type combinations given current connections - fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature>; + /// given the current connections, which inputs are available? + /// + /// This can return more inputs than those connected, indicating missing or optional connections. + /// This must not fail but fallback to a default signature that allows connections to be made. + fn visible_inputs(&self, connected: &[Option<Type>; MAX_INPUTS]) -> Vec<Option<Type>>; - fn signature(&self, connected: &[Option<Type>]) -> TypeSignature; + /// given the current connections, what is this node's signature? + /// + /// This determines the output types shown and is used for compilation. If no valid signature corresponds + /// to the set of inputs, this must fail. If the implementation accepts optional inputs, it may validate + /// them here or leave that to be done in `compile`. + fn signature( + &self, + connected: &[Option<Type>; MAX_INPUTS], + ) -> Result<TypeSignature, CompileError>; + /// generate code for this node. + /// + /// The number of `inputs` and `outputs` correspond to the types in `signature`. + /// This must fail if valid code can not be generated. fn compile( &self, + f: &mut dyn fmt::Write, signature: TypeSignature, inputs: Vec<Option<String>>, outputs: Vec<String>, - f: &mut dyn fmt::Write, ) -> Result<(), CompileError>; + /// optionaly render additional UI in the body of this node. fn show_body(&mut self, ui: &mut egui::Ui) -> egui::Response { ui.response() } diff --git a/src/snarl_ext.rs b/src/snarl_ext.rs index 5cbb065..5e0e4f1 100644 --- a/src/snarl_ext.rs +++ b/src/snarl_ext.rs @@ -1,63 +1,75 @@ use egui_snarl::{InPinId, NodeId, OutPinId, Snarl}; +use std::array::from_fn; use std::fmt; use topological_sort::TopologicalSort; -use crate::node::{AnyNode, CompileError, ConcreteNode}; +use crate::node::{AnyNode, CompileError, ConcreteNode, MAX_INPUTS}; use crate::types::{Type, TypeSignature}; pub trait WithSignatures { - fn in_pin_type(&self, pin: InPinId) -> Option<Type>; - fn out_pin_type(&self, pin: OutPinId) -> Type; - fn get_input_types(&self, node: NodeId) -> Vec<Option<Type>>; - fn get_node_signature(&self, node: NodeId) -> TypeSignature; - fn get_num_inputs(&self, node: NodeId) -> usize; -} - -#[derive(fmt::Debug, Copy, Clone)] -pub struct GraphError(pub CompileError, pub Option<NodeId>); -impl From<fmt::Error> for GraphError { - fn from(v: fmt::Error) -> Self { - Self(v.into(), None) - } -} - -pub trait Compilable { - fn compile_node(&self, node: NodeId, f: &mut dyn fmt::Write) -> Result<(), CompileError>; - fn compile(&self, f: &mut dyn fmt::Write) -> Result<(), GraphError>; + // internal + fn get_input_types(&self, node: NodeId) -> [Option<Type>; MAX_INPUTS]; + + // for rendering + fn get_visible_inputs(&self, node: NodeId) -> Vec<Option<Type>>; + + // for compiling + fn get_node_signature(&self, node: NodeId) -> Result<TypeSignature, CompileError>; + fn get_node_signature_with_overrides( + &self, + node: NodeId, + overrides: &[Option<Type>; MAX_INPUTS], + ) -> Result<TypeSignature, CompileError>; } impl WithSignatures for Snarl<AnyNode> { - fn in_pin_type(&self, pin: InPinId) -> Option<Type> { - let sig = &self.get_node_signature(pin.node); - sig.inputs.get(pin.input).copied() + fn get_input_types(&self, node: NodeId) -> [Option<Type>; MAX_INPUTS] { + from_fn( + move |input| match &*self.in_pin(InPinId { node, input }).remotes { + [] => None, + [out_pin] => self + .get_node_signature(out_pin.node) + .map(|s| s.outputs[out_pin.output]) + .ok(), + _ => unreachable!("cannot connect to multiple inputs"), + }, + ) } - fn out_pin_type(&self, pin: OutPinId) -> Type { - let sig = &self.get_node_signature(pin.node); - sig.outputs[pin.output] + fn get_visible_inputs(&self, node: NodeId) -> Vec<Option<Type>> { + self[node].visible_inputs(&self.get_input_types(node)) } - fn get_input_types(&self, node: NodeId) -> Vec<Option<Type>> { - (0..self[node].max_inputs()) - .map( - move |input| match &*self.in_pin(InPinId { node, input }).remotes { - [] => None, - [out_pin] => Some(self.out_pin_type(*out_pin)), - _ => unreachable!("cannot connect to multiple inputs"), - }, - ) - .collect() + fn get_node_signature(&self, node: NodeId) -> Result<TypeSignature, CompileError> { + self[node].signature(&self.get_input_types(node)) } - fn get_node_signature(&self, node: NodeId) -> TypeSignature { - self[node].signature(&self.get_input_types(node)) + fn get_node_signature_with_overrides( + &self, + node: NodeId, + overrides: &[Option<Type>; MAX_INPUTS], + ) -> Result<TypeSignature, CompileError> { + let mut inputs = self.get_input_types(node); + for (i, over) in overrides.iter().enumerate() { + inputs[i] = over.or(inputs[i]); + } + self[node].signature(&inputs) } +} - fn get_num_inputs(&self, node: NodeId) -> usize { - self[node].num_inputs(&self.get_input_types(node)) +#[derive(fmt::Debug, Copy, Clone)] +pub struct GraphError(pub CompileError, pub Option<NodeId>); +impl From<fmt::Error> for GraphError { + fn from(v: fmt::Error) -> Self { + Self(v.into(), None) } } +pub trait Compilable { + fn compile_node(&self, f: &mut dyn fmt::Write, node: NodeId) -> Result<(), CompileError>; + fn compile(&self, f: &mut dyn fmt::Write) -> Result<(), GraphError>; +} + fn compile_output(pin: OutPinId) -> String { let node_id = pin.node.0; let out_id = pin.output; @@ -88,7 +100,7 @@ void main() {{ " )?; while let Some(id) = order.pop() { - self.compile_node(id, f) + self.compile_node(f, id) .map_err(|inner| GraphError(inner, Some(id)))?; } writeln!(f, "}}")?; @@ -96,8 +108,10 @@ void main() {{ Ok(()) } - fn compile_node(&self, node: NodeId, f: &mut dyn fmt::Write) -> Result<(), CompileError> { - let signature = self.get_node_signature(node); + fn compile_node(&self, f: &mut dyn fmt::Write, node: NodeId) -> Result<(), CompileError> { + let signature = self.get_node_signature(node)?; + + // log::info!("{node} {self[node]}: {signature:?}"); let inputs: Vec<Option<String>> = (0..signature.inputs.len()) .map( @@ -113,6 +127,6 @@ void main() {{ .map(|output| compile_output(OutPinId { node, output })) .collect(); - self[node].compile(signature, inputs, outputs, f) + self[node].compile(f, signature, inputs, outputs) } } diff --git a/src/types.rs b/src/types.rs index febdace..6048ea4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,7 @@ use std::fmt; +use crate::node::CompileError; + #[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub enum FloatPrecision { #[default] @@ -17,13 +19,21 @@ pub enum ScalarType { impl ScalarType { const SCALARS: [Self; 5] = [ - ScalarType::Float(FloatPrecision::Single), - ScalarType::Float(FloatPrecision::Double), - ScalarType::Int, - ScalarType::UInt, - ScalarType::Bool, + Self::Float(FloatPrecision::Single), + Self::Float(FloatPrecision::Double), + Self::Int, + Self::UInt, + Self::Bool, ]; + pub fn default_value(&self) -> &'static str { + match self { + Self::Float(_) => "0.0", + Self::Int | Self::UInt => "0", + Self::Bool => "false", + } + } + pub fn pick(ui: &mut egui::Ui) -> Option<Self> { for value in Self::SCALARS { if ui.button(format!("{}", value)).clicked() { @@ -42,11 +52,11 @@ impl Default for ScalarType { impl fmt::Display for ScalarType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - ScalarType::Float(FloatPrecision::Single) => write!(f, "float"), - ScalarType::Float(FloatPrecision::Double) => write!(f, "double"), - ScalarType::Int => write!(f, "int"), - ScalarType::UInt => write!(f, "uint"), - ScalarType::Bool => write!(f, "bool"), + Self::Float(FloatPrecision::Single) => write!(f, "float"), + Self::Float(FloatPrecision::Double) => write!(f, "double"), + Self::Int => write!(f, "int"), + Self::UInt => write!(f, "uint"), + Self::Bool => write!(f, "bool"), } } } @@ -64,7 +74,7 @@ impl fmt::Display for Dimension { } } impl Dimension { - pub const fn from(v: usize) -> Dimension { + pub const fn from_const(v: usize) -> Dimension { match v { 2 => Self::D2, 3 => Self::D3, @@ -73,6 +83,18 @@ impl Dimension { } } } +impl TryFrom<usize> for Dimension { + type Error = (); + + fn try_from(value: usize) -> Result<Self, Self::Error> { + match value { + 2 => Ok(Self::D2), + 3 => Ok(Self::D3), + 4 => Ok(Self::D4), + _ => Err(()), + } + } +} /// a GLSL type #[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] @@ -97,7 +119,22 @@ impl Type { } } - pub fn upcast_gentype(self, other: Self) -> Result<Self, ()> { + pub fn num_components(&self) -> usize { + match self { + Type::Scalar(_) => 1, + Type::Vector(_, d) => *d as usize, + Type::Matrix(_, r, c) => (*r as usize) * (*c as usize), + } + } + + pub fn default_value(&self) -> String { + match self { + Type::Scalar(_) => self.scalar().default_value().to_string(), + _ => format!("{self}({})", self.scalar().default_value()), + } + } + + pub fn upcast_gentype(self, other: Self) -> Result<Self, CompileError> { if self == other { // same scalar or complex type Ok(self) @@ -108,7 +145,15 @@ impl Type { // complex and scalar type Ok(other) } else { - Err(()) + Err(CompileError::InvalidArguments) + } + } + + pub fn downcast_gentype(self, other: ScalarType) -> Result<ScalarType, CompileError> { + if self.scalar() == other { + Ok(other) + } else { + Err(CompileError::InvalidArguments) } } @@ -177,7 +222,7 @@ impl Default for Type { } /// a single concrete type signature for a function -#[derive(PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug)] pub struct TypeSignature { pub inputs: Box<[Type]>, pub outputs: Box<[Type]>, @@ -197,7 +242,7 @@ impl TypeSignature { let have_inputs = connected .iter() .enumerate() - .filter(|(_, t)| matches!(t, Some(_))) + .filter(|(_, t)| t.is_some()) .map(|(i, _)| i + 1) .max() .unwrap_or(0); diff --git a/src/wasm.rs b/src/wasm.rs index 05653ff..a273994 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -30,7 +30,7 @@ impl DownloadFile { pub fn save(&self) -> Result<(), JsValue> { let string = unsafe { str::from_utf8_unchecked(&self.data) }; - let parts = Array::of1(&JsValue::from_str(&string)); + let parts = Array::of1(&JsValue::from_str(string)); let blob = Blob::new_with_str_sequence_and_options(&parts, &self.options)?; let url = Url::create_object_url_with_blob(&blob)?; |
