aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-01-23 17:21:46 +0000
committers-ol <s+removethis@s-ol.nu>2026-01-23 17:21:46 +0000
commitd217d2d03ac8cb6046d3435226cbd9bcef13335a (patch)
treec262e171556c61711ebb76e5136ad4529a8531af
parentgeneric save / save as (diff)
downloadnodetoy-d217d2d03ac8cb6046d3435226cbd9bcef13335a.tar.gz
nodetoy-d217d2d03ac8cb6046d3435226cbd9bcef13335a.zip
variadic arithmetic
-rw-r--r--src/examples/shadertoy_plasma.nt.json2
-rw-r--r--src/library.rs99
-rw-r--r--src/main.rs3
-rw-r--r--src/node.rs5
-rw-r--r--src/snarl_ext.rs13
-rw-r--r--src/types.rs29
6 files changed, 112 insertions, 39 deletions
diff --git a/src/examples/shadertoy_plasma.nt.json b/src/examples/shadertoy_plasma.nt.json
index 0df1b75..cf2d53b 100644
--- a/src/examples/shadertoy_plasma.nt.json
+++ b/src/examples/shadertoy_plasma.nt.json
@@ -1 +1 @@
-{"nodes":{"0":{"value":{"Input":"UV"},"pos":{"x":163.98965,"y":294.79865},"open":true},"1":{"value":{"Output":null},"pos":{"x":1357.2115,"y":327.50165},"open":true},"2":{"value":{"Arithmetic":"Multiply"},"pos":{"x":1100.7025,"y":315.54364},"open":true},"3":{"value":{"Constant":{"Float":0.5}},"pos":{"x":922.1152,"y":389.79797},"open":true},"4":{"value":{"Arithmetic":"Add"},"pos":{"x":1241.6924,"y":324.00272},"open":true},"7":{"value":{"Input":"Time"},"pos":{"x":549.9027,"y":214.97101},"open":true},"8":{"value":{"Conversion":{"SplitVector":null}},"pos":{"x":357.21643,"y":295.80508},"open":true},"9":{"value":{"Conversion":{"CombineVector":null}},"pos":{"x":530.1639,"y":293.92538},"open":true},"10":{"value":{"Arithmetic":"Add"},"pos":{"x":761.3871,"y":255.3878},"open":true},"11":{"value":{"Arithmetic":"Add"},"pos":{"x":875.119,"y":307.0838},"open":true},"12":{"value":{"Constant":{"Vec3":[0.0,2.0,4.0]}},"pos":{"x":726.6099,"y":349.38098},"open":true},"13":{"value":{"Builtin":{"Thru1":"Cos"}},"pos":{"x":988.8504,"y":308.02408},"open":true}},"wires":[{"out_pin":{"node":12,"output":0},"in_pin":{"node":11,"input":1}},{"out_pin":{"node":4,"output":0},"in_pin":{"node":1,"input":0}},{"out_pin":{"node":8,"output":1},"in_pin":{"node":9,"input":1}},{"out_pin":{"node":11,"output":0},"in_pin":{"node":13,"input":0}},{"out_pin":{"node":9,"output":0},"in_pin":{"node":10,"input":1}},{"out_pin":{"node":3,"output":0},"in_pin":{"node":2,"input":1}},{"out_pin":{"node":8,"output":0},"in_pin":{"node":9,"input":2}},{"out_pin":{"node":3,"output":0},"in_pin":{"node":4,"input":1}},{"out_pin":{"node":7,"output":0},"in_pin":{"node":10,"input":0}},{"out_pin":{"node":13,"output":0},"in_pin":{"node":2,"input":0}},{"out_pin":{"node":0,"output":0},"in_pin":{"node":8,"input":0}},{"out_pin":{"node":2,"output":0},"in_pin":{"node":4,"input":0}},{"out_pin":{"node":8,"output":0},"in_pin":{"node":9,"input":0}},{"out_pin":{"node":10,"output":0},"in_pin":{"node":11,"input":0}}]}
+{"nodes":{"0":{"value":{"Input":"UV"},"pos":{"x":163.98965,"y":294.79865},"open":true},"1":{"value":{"Output":null},"pos":{"x":1357.2115,"y":327.50165},"open":true},"2":{"value":{"Arithmetic":"Multiply"},"pos":{"x":1093.4141,"y":307.34406},"open":true},"3":{"value":{"Constant":{"Float":0.5}},"pos":{"x":855.6077,"y":394.3533},"open":true},"4":{"value":{"Arithmetic":"Add"},"pos":{"x":1237.1371,"y":340.40186},"open":true},"7":{"value":{"Input":"Time"},"pos":{"x":549.9027,"y":214.97101},"open":true},"8":{"value":{"Conversion":{"SplitVector":null}},"pos":{"x":357.21643,"y":295.80508},"open":true},"9":{"value":{"Conversion":{"CombineVector":null}},"pos":{"x":530.1639,"y":293.92538},"open":true},"11":{"value":{"Arithmetic":"Add"},"pos":{"x":815.119,"y":269.0838},"open":true},"12":{"value":{"Constant":{"Vec3":[0.0,2.0,4.0]}},"pos":{"x":571.64056,"y":415.3007},"open":true},"13":{"value":{"Builtin":{"Thru1":"Cos"}},"pos":{"x":970.6291,"y":287.06967},"open":true}},"wires":[{"out_pin":{"node":11,"output":0},"in_pin":{"node":13,"input":0}},{"out_pin":{"node":4,"output":0},"in_pin":{"node":1,"input":0}},{"out_pin":{"node":13,"output":0},"in_pin":{"node":2,"input":0}},{"out_pin":{"node":2,"output":0},"in_pin":{"node":4,"input":0}},{"out_pin":{"node":12,"output":0},"in_pin":{"node":11,"input":2}},{"out_pin":{"node":7,"output":0},"in_pin":{"node":11,"input":0}},{"out_pin":{"node":9,"output":0},"in_pin":{"node":11,"input":1}},{"out_pin":{"node":0,"output":0},"in_pin":{"node":8,"input":0}},{"out_pin":{"node":8,"output":0},"in_pin":{"node":9,"input":0}},{"out_pin":{"node":8,"output":0},"in_pin":{"node":9,"input":2}},{"out_pin":{"node":8,"output":1},"in_pin":{"node":9,"input":1}},{"out_pin":{"node":3,"output":0},"in_pin":{"node":2,"input":1}},{"out_pin":{"node":3,"output":0},"in_pin":{"node":4,"input":1}}]} \ No newline at end of file
diff --git a/src/library.rs b/src/library.rs
index a45ff24..cd3b36c 100644
--- a/src/library.rs
+++ b/src/library.rs
@@ -108,11 +108,16 @@ impl<T: FixedNode> ConcreteNode for T {
fn compile(
&self,
signature: TypeSignature,
- inputs: Vec<String>,
+ inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
) -> fmt::Result {
- self.compile(signature, inputs, outputs, f)
+ let inputs: Option<Vec<String>> = inputs.into_iter().collect();
+ if let Some(inputs) = inputs {
+ self.compile(signature, inputs, outputs, f)
+ } else {
+ Err(fmt::Error)
+ }
}
}
@@ -152,7 +157,7 @@ impl<T: OutputOnlyNode> ConcreteNode for T {
fn compile(
&self,
signature: TypeSignature,
- inputs: Vec<String>,
+ inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
) -> fmt::Result {
@@ -170,43 +175,77 @@ pub enum BinArithmetic {
Divide,
}
-impl FixedNode for BinArithmetic {
- fn all_signatures(&self) -> Box<dyn Iterator<Item = TypeSignature>> {
- // @TODO: matrix and matrix/vector operations
- // componentwise and vector-scalar operations
- Box::new(
- [GEN_F_TYPES, GEN_D_TYPES, GEN_I_TYPES, GEN_U_TYPES]
- .into_iter()
- .flatten()
- .flat_map(|t| {
- [
- TypeSignature::new([t, t], [t]),
- TypeSignature::new([t, t.scalar().into()], [t]),
- TypeSignature::new([t.scalar().into(), t], [t]),
- ]
- })
- .dedup(),
- )
+fn check_gentype(inputs: &[Option<Type>]) -> Result<Option<Type>, ()> {
+ let mut seen: Option<Type> = None;
+
+ for input in inputs {
+ seen = match (seen, input) {
+ (a, None) => a,
+ (None, a) => *a,
+ (Some(a), Some(b)) => Some(Type::upcast_gentype(a, *b)?),
+ }
+ }
+
+ Ok(seen)
+}
+
+impl ConcreteNode for BinArithmetic {
+ fn max_inputs(&self) -> usize {
+ 10
+ }
+
+ fn num_inputs(&self, connected: &[Option<Type>]) -> usize {
+ connected
+ .iter()
+ .enumerate()
+ .filter(|(_, v)| v.is_some())
+ .map(|(i, _)| i + 2)
+ .last()
+ .unwrap_or(2)
+ }
+
+ fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature> {
+ if let Ok(out) = check_gentype(connected) {
+ let out = out.unwrap_or_default();
+
+ vec![TypeSignature {
+ inputs: connected.iter().map(|v| v.unwrap_or_default()).collect(),
+ outputs: Box::new([out]),
+ }]
+ } else {
+ vec![]
+ }
+ }
+
+ 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 compile(
&self,
signature: TypeSignature,
- inputs: Vec<String>,
+ inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
) -> fmt::Result {
let out_typ = &signature.outputs[0];
let out_name = &outputs[0];
- let left = &inputs[0];
- let right = &inputs[1];
let sym = match self {
- Self::Add => "+",
- Self::Subtract => "-",
- Self::Multiply => "*",
- Self::Divide => "/",
+ Self::Add => " + ",
+ Self::Subtract => " - ",
+ Self::Multiply => " * ",
+ Self::Divide => " / ",
};
- writeln!(f, "{out_typ} {out_name} = {left} {sym} {right};")
+ let expr = inputs.iter().filter_map(|s| s.as_ref()).join(sym);
+ writeln!(f, "{out_typ} {out_name} = {expr};")
}
}
@@ -926,7 +965,7 @@ where
fn compile(
&self,
_signature: TypeSignature,
- _inputs: Vec<String>,
+ _inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
) -> fmt::Result {
@@ -1080,7 +1119,7 @@ impl ConcreteNode for Input {
fn compile(
&self,
signature: TypeSignature,
- _inputs: Vec<String>,
+ _inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
) -> fmt::Result {
diff --git a/src/main.rs b/src/main.rs
index ba1c356..35a3ca3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -86,8 +86,7 @@ impl SnarlViewer<AnyNode> for Viewer {
}
fn inputs(&mut self, id: NodeId, snarl: &Snarl<AnyNode>) -> usize {
- let node = &snarl[id];
- node.max_inputs()
+ snarl.get_num_inputs(id)
}
fn outputs(&mut self, id: NodeId, snarl: &Snarl<AnyNode>) -> usize {
diff --git a/src/node.rs b/src/node.rs
index 659d67f..3bcd73b 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -9,6 +9,9 @@ use crate::types::{Type, TypeSignature};
#[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>;
@@ -18,7 +21,7 @@ pub trait ConcreteNode {
fn compile(
&self,
signature: TypeSignature,
- inputs: Vec<String>,
+ inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
) -> fmt::Result;
diff --git a/src/snarl_ext.rs b/src/snarl_ext.rs
index 7d0464d..e36eb2e 100644
--- a/src/snarl_ext.rs
+++ b/src/snarl_ext.rs
@@ -10,6 +10,7 @@ pub trait WithSignatures {
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;
}
pub trait Compilable {
@@ -43,6 +44,10 @@ impl WithSignatures for Snarl<AnyNode> {
fn get_node_signature(&self, node: NodeId) -> TypeSignature {
self[node].signature(&self.get_input_types(node))
}
+
+ fn get_num_inputs(&self, node: NodeId) -> usize {
+ self[node].num_inputs(&self.get_input_types(node))
+ }
}
fn compile_output(pin: OutPinId) -> String {
@@ -85,11 +90,11 @@ void main() {{
fn compile_node(&self, node: NodeId, f: &mut dyn fmt::Write) -> fmt::Result {
let signature = self.get_node_signature(node);
- let inputs: Result<Vec<String>, fmt::Error> = (0..signature.inputs.len())
+ let inputs: Vec<Option<String>> = (0..signature.inputs.len())
.map(
|input| match &*self.in_pin(InPinId { node, input }).remotes {
- [] => Err(fmt::Error),
- [pin] => Ok(compile_output(*pin)),
+ [] => None,
+ [pin] => Some(compile_output(*pin)),
_ => unreachable!("cannot connect to multiple inputs"),
},
)
@@ -99,6 +104,6 @@ void main() {{
.map(|output| compile_output(OutPinId { node, output }))
.collect();
- self[node].compile(signature, inputs?, outputs, f)
+ self[node].compile(signature, inputs, outputs, f)
}
}
diff --git a/src/types.rs b/src/types.rs
index 5614ab7..febdace 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -1,7 +1,8 @@
use std::fmt;
-#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+#[derive(Copy, Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FloatPrecision {
+ #[default]
Single,
Double,
}
@@ -33,6 +34,11 @@ impl ScalarType {
None
}
}
+impl Default for ScalarType {
+ fn default() -> Self {
+ Self::Float(Default::default())
+ }
+}
impl fmt::Display for ScalarType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -91,6 +97,21 @@ impl Type {
}
}
+ pub fn upcast_gentype(self, other: Self) -> Result<Self, ()> {
+ if self == other {
+ // same scalar or complex type
+ Ok(self)
+ } else if Self::Scalar(self.scalar()) == other {
+ // complex and scalar type
+ Ok(self)
+ } else if Self::Scalar(other.scalar()) == self {
+ // complex and scalar type
+ Ok(other)
+ } else {
+ Err(())
+ }
+ }
+
pub fn pick(ui: &mut egui::Ui) -> Option<Self> {
let mut result: Option<Self> = ScalarType::pick(ui).map(Self::Scalar);
@@ -149,6 +170,12 @@ impl fmt::Display for Type {
}
}
+impl Default for Type {
+ fn default() -> Self {
+ Self::Scalar(Default::default())
+ }
+}
+
/// a single concrete type signature for a function
#[derive(PartialEq, Debug)]
pub struct TypeSignature {