aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-01-23 19:41:46 +0000
committers-ol <s+removethis@s-ol.nu>2026-01-24 15:26:05 +0000
commit7155e060a45af38f3c2b141cc898452762584f60 (patch)
tree8c797f7436d66cb855900aa3feb81eb4d905b36e /src
parentError checking (diff)
downloadnodetoy-7155e060a45af38f3c2b141cc898452762584f60.tar.gz
nodetoy-7155e060a45af38f3c2b141cc898452762584f60.zip
refactor node signature interface
Diffstat (limited to 'src')
-rw-r--r--src/library.rs251
-rw-r--r--src/main.rs84
-rw-r--r--src/node.rs32
-rw-r--r--src/snarl_ext.rs100
-rw-r--r--src/types.rs75
-rw-r--r--src/wasm.rs2
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)?;