aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-01-17 12:34:26 +0000
committers-ol <s+removethis@s-ol.nu>2026-01-17 12:34:26 +0000
commit87fecd4c87a3b4f33c795beead1b8f9f19fd7e47 (patch)
tree2ce0f0f107ced3547a8e1ebecdd2bab3e82b0518
parentVector combine (diff)
downloadnodetoy-87fecd4c87a3b4f33c795beead1b8f9f19fd7e47.tar.gz
nodetoy-87fecd4c87a3b4f33c795beead1b8f9f19fd7e47.zip
split main into smaller modules
-rw-r--r--src/library.rs7
-rw-r--r--src/main.rs392
-rw-r--r--src/node.rs75
-rw-r--r--src/snarl_ext.rs104
-rw-r--r--src/types.rs145
5 files changed, 371 insertions, 352 deletions
diff --git a/src/library.rs b/src/library.rs
index 4c3a8c1..9764ad5 100644
--- a/src/library.rs
+++ b/src/library.rs
@@ -3,9 +3,10 @@ use itertools::{Itertools, izip};
use serde::{Deserialize, Serialize};
use std::fmt;
-use crate::{
- ConcreteNode, Dimension, Dimension::*, FloatPrecision::*, ScalarType, ScalarType::*, Type,
- Type::*, TypeSignature,
+use crate::node::ConcreteNode;
+use crate::types::{
+ Dimension, Dimension::*, FloatPrecision::*, ScalarType, ScalarType::*, Type, Type::*,
+ TypeSignature,
};
const GEN_F_TYPES: [Type; 4] = [
diff --git a/src/main.rs b/src/main.rs
index e94bcdb..0ca84de 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,75 +1,21 @@
#![allow(clippy::use_self)]
-use eframe::{App, CreationContext};
use egui::{Color32, Id, Ui};
use egui_snarl::{
- InPin, InPinId, NodeId, OutPin, OutPinId, Snarl,
+ InPin, NodeId, OutPin, Snarl,
ui::{NodeLayout, PinInfo, PinPlacement, SnarlStyle, SnarlViewer, SnarlWidget},
};
-use enum_dispatch::enum_dispatch;
use library::NodeIndex;
use log::*;
-use std::fmt;
-#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
-enum FloatPrecision {
- Single,
- Double,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
-enum ScalarType {
- Float(FloatPrecision),
- Int,
- UInt,
- Bool,
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
-#[repr(u8)]
-enum Dimension {
- D2 = 2,
- D3 = 3,
- D4 = 4,
-}
-impl fmt::Display for Dimension {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{}", *self as u8)
- }
-}
-impl Dimension {
- const fn from(v: usize) -> Dimension {
- match v {
- 2 => Self::D2,
- 3 => Self::D3,
- 4 => Self::D4,
- _ => panic!("invalid dimension value"),
- }
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
-enum Type {
- Scalar(ScalarType),
- Vector(ScalarType, Dimension),
- Matrix(FloatPrecision, Dimension, Dimension),
-}
-
-impl From<ScalarType> for Type {
- fn from(val: ScalarType) -> Self {
- Self::Scalar(val)
- }
-}
-
-impl Type {
- fn scalar(&self) -> ScalarType {
- match self {
- Type::Scalar(s) => *s,
- Type::Vector(s, _) => *s,
- Type::Matrix(p, _, _) => ScalarType::Float(*p),
- }
- }
-}
+mod library;
+mod node;
+mod preview;
+mod snarl_ext;
+mod types;
+use node::{AnyNode, ConcreteNode};
+use snarl_ext::{Compilable, WithSignatures};
+use types::{Dimension, FloatPrecision, ScalarType, Type};
impl Into<PinInfo> for Type {
fn into(self) -> PinInfo {
@@ -90,267 +36,17 @@ impl Into<PinInfo> for Type {
}
}
-impl fmt::Display for Type {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Type::Scalar(s) => write!(
- f,
- "{}",
- match s {
- ScalarType::Float(FloatPrecision::Single) => "float",
- ScalarType::Float(FloatPrecision::Double) => "double",
- ScalarType::Int => "int",
- ScalarType::UInt => "uint",
- ScalarType::Bool => "bool",
- }
- ),
- Type::Vector(s, d) => write!(
- f,
- "{}vec{d}",
- match s {
- ScalarType::Float(FloatPrecision::Single) => "",
- ScalarType::Float(FloatPrecision::Double) => "d",
- ScalarType::Int => "i",
- ScalarType::UInt => "u",
- ScalarType::Bool => "b",
- }
- ),
- Type::Matrix(p, r, c) => {
- let pr = match p {
- FloatPrecision::Single => "",
- FloatPrecision::Double => "d",
- };
- if r == c {
- write!(f, "{pr}mat{r}")
- } else {
- write!(f, "{pr}mat{r}{c}")
- }
- }
- }
- }
-}
-
-/// a single concrete type signature for a function
-#[derive(PartialEq, Debug)]
-struct TypeSignature {
- inputs: Box<[Type]>,
- outputs: Box<[Type]>,
-}
-
-impl TypeSignature {
- pub fn new<const NI: usize, const NO: usize>(inputs: [Type; NI], outputs: [Type; NO]) -> Self {
- Self {
- inputs: Box::new(inputs),
- outputs: Box::new(outputs),
- }
- }
-}
-
-#[enum_dispatch]
-trait ConcreteNode {
- fn max_inputs(&self) -> usize;
-
- // set of possible input type combinations given current connections
- fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature>;
-
- fn signature(&self, connected: &[Option<Type>]) -> TypeSignature;
-
- fn compile(
- &self,
- signature: TypeSignature,
- inputs: Vec<String>,
- outputs: Vec<String>,
- f: &mut dyn fmt::Write,
- ) -> fmt::Result;
-
- fn show_body(&mut self, ui: &mut egui::Ui) -> egui::Response {
- ui.response()
- }
-}
-
-impl TypeSignature {
- fn matches_inputs(&self, connected: &[Option<Type>]) -> bool {
- let have_inputs = connected
- .iter()
- .enumerate()
- .filter(|(_, t)| matches!(t, Some(_)))
- .map(|(i, _)| i + 1)
- .max()
- .unwrap_or(0);
-
- if have_inputs > self.inputs.len() {
- false
- } else {
- connected
- .iter()
- .zip(self.inputs.iter())
- .all(|(connected_input, expected)| {
- if let Some(input) = connected_input {
- *input == *expected
- } else {
- true
- }
- })
- }
- }
-}
-
-mod library;
-mod preview;
-
-#[enum_dispatch(ConcreteNode)]
-#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
-enum DemoNode {
- Constant(library::AnyConstant),
- Conversion(library::Conversion),
- Input(library::Input),
- Builtin(library::BuiltinFunction),
- Output(library::Output),
-}
-impl DemoNode {
- fn get_input_types(&self, node: NodeId, snarl: &Snarl<DemoNode>) -> Vec<Option<Type>> {
- (0..snarl[node].max_inputs())
- .map(
- move |input| match &*snarl.in_pin(InPinId { node, input }).remotes {
- [] => None,
- [out_pin] => Some(DemoViewer::get_out_type(*out_pin, snarl)),
- _ => unreachable!("cannot connect to multiple inputs"),
- },
- )
- .collect()
- }
-
- fn compile_output(pin: OutPinId) -> String {
- let node_id = pin.node.0;
- let out_id = pin.output;
- format!("n{node_id}_o{out_id}")
- }
-
- pub fn compile_node(
- &self,
- node: NodeId,
- snarl: &Snarl<DemoNode>,
- f: &mut dyn fmt::Write,
- ) -> fmt::Result {
- let inp = self.get_input_types(node, snarl);
- let sig = self.signature(&inp);
- info!("compile {self} input types {inp:?} -> sig {sig:?}");
-
- let signature = self.get_node_signature(node, snarl);
-
- let inputs: Result<Vec<String>, fmt::Error> = (0..signature.inputs.len())
- .map(
- |input| match &*snarl.in_pin(InPinId { node, input }).remotes {
- [] => Err(fmt::Error),
- [pin] => Ok(DemoNode::compile_output(*pin)),
- _ => unreachable!("cannot connect to multiple inputs"),
- },
- )
- .collect();
-
- let outputs: Vec<String> = (0..signature.outputs.len())
- .map(|output| DemoNode::compile_output(OutPinId { node, output }))
- .collect();
-
- snarl[node].compile(signature, inputs?, outputs, f)
- }
-
- pub fn get_node_signature(&self, node: NodeId, snarl: &Snarl<DemoNode>) -> TypeSignature {
- self.signature(&self.get_input_types(node, snarl))
- }
-}
-
-impl NodeIndex for DemoNode {
- const TITLE: &'static str = "";
-
- fn all() -> impl Iterator<Item = Self> {
- (library::AnyConstant::all().map(DemoNode::from))
- .chain(library::Conversion::all().map(DemoNode::from))
- .chain(library::Input::all().map(DemoNode::from))
- .chain(library::Output::all().map(DemoNode::from))
- .chain(library::BuiltinFunction::all().map(DemoNode::from))
- }
-
- fn pick_node(ui: &mut egui::Ui) -> Option<Self> {
- ui.label("Add node");
-
- let a = library::AnyConstant::pick_node(ui).map(DemoNode::from);
- let b = library::Conversion::pick_node(ui).map(DemoNode::from);
- let c = library::Input::pick_node(ui).map(DemoNode::from);
- let d = library::Output::pick_node(ui).map(DemoNode::from);
- let e = library::BuiltinFunction::pick_node(ui).map(DemoNode::from);
-
- a.or(b).or(c).or(d).or(e)
- }
-}
-
-impl fmt::Display for DemoNode {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self {
- Self::Constant(x) => fmt::Display::fmt(x, f),
- Self::Conversion(x) => fmt::Display::fmt(x, f),
- Self::Input(x) => fmt::Display::fmt(x, f),
- Self::Builtin(x) => fmt::Display::fmt(x, f),
- Self::Output(x) => fmt::Display::fmt(x, f),
- }
- }
-}
-
-struct DemoViewer {
+pub struct Viewer {
dirty: bool,
}
-impl DemoViewer {
- fn get_in_type(pin: InPinId, snarl: &Snarl<DemoNode>) -> Option<Type> {
- let node = &snarl[pin.node];
- let sig = &node.get_node_signature(pin.node, snarl);
- sig.inputs.get(pin.input).copied()
- }
-
- fn get_out_type(pin: OutPinId, snarl: &Snarl<DemoNode>) -> Type {
- let node = &snarl[pin.node];
- let sig = &node.get_node_signature(pin.node, snarl);
- sig.outputs[pin.output]
- }
-
- pub fn compile(snarl: &Snarl<DemoNode>, f: &mut dyn fmt::Write) -> fmt::Result {
- let mut order = topological_sort::TopologicalSort::<NodeId>::new();
- for (out, inp) in snarl.wires() {
- order.add_dependency(out.node, inp.node);
- }
-
- write!(
- f,
- "
-#version 450
-in vec2 in_uv;
-out vec4 out_color;
-
-layout(std140, binding = 0) uniform Uniforms {{
- vec2 in_resolution;
- float in_time;
- float _pad3;
-}} uniforms;
-
-void main() {{
-"
- )?;
- while let Some(id) = order.pop() {
- snarl[id].compile_node(id, snarl, f)?;
- }
- writeln!(f, "}}")?;
-
- Ok(())
- }
-}
-
-impl SnarlViewer<DemoNode> for DemoViewer {
+impl SnarlViewer<AnyNode> for Viewer {
#[inline]
- fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<DemoNode>) {
+ fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<AnyNode>) {
let to_node = &snarl[to.id.node];
- let mut inputs = to_node.get_input_types(to.id.node, snarl);
- inputs[to.id.input] = Some(DemoViewer::get_out_type(from.id, snarl));
+ 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 signatures.is_empty() {
@@ -365,51 +61,49 @@ impl SnarlViewer<DemoNode> for DemoViewer {
self.dirty = true;
}
- fn disconnect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<DemoNode>) {
+ fn disconnect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<AnyNode>) {
snarl.disconnect(from.id, to.id);
self.dirty = true;
}
- fn drop_outputs(&mut self, pin: &OutPin, snarl: &mut Snarl<DemoNode>) {
+ fn drop_outputs(&mut self, pin: &OutPin, snarl: &mut Snarl<AnyNode>) {
snarl.drop_outputs(pin.id);
}
- fn drop_inputs(&mut self, pin: &InPin, snarl: &mut Snarl<DemoNode>) {
+ fn drop_inputs(&mut self, pin: &InPin, snarl: &mut Snarl<AnyNode>) {
snarl.drop_inputs(pin.id);
}
- fn title(&mut self, node: &DemoNode) -> String {
+ fn title(&mut self, node: &AnyNode) -> String {
format!("{node}")
}
- fn inputs(&mut self, id: NodeId, snarl: &Snarl<DemoNode>) -> usize {
+ fn inputs(&mut self, id: NodeId, snarl: &Snarl<AnyNode>) -> usize {
let node = &snarl[id];
node.max_inputs()
}
- fn outputs(&mut self, id: NodeId, snarl: &Snarl<DemoNode>) -> usize {
- let node = &snarl[id];
- let sig = &node.get_node_signature(id, snarl);
- sig.outputs.len()
+ fn outputs(&mut self, id: NodeId, snarl: &Snarl<AnyNode>) -> usize {
+ snarl.get_node_signature(id).outputs.len()
}
#[allow(clippy::too_many_lines)]
#[allow(refining_impl_trait)]
- fn show_input(&mut self, pin: &InPin, _ui: &mut Ui, snarl: &mut Snarl<DemoNode>) -> PinInfo {
- match DemoViewer::get_in_type(pin.id, snarl) {
+ 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)),
Some(t) => t.into(),
}
}
#[allow(refining_impl_trait)]
- fn show_output(&mut self, pin: &OutPin, ui: &mut Ui, snarl: &mut Snarl<DemoNode>) -> PinInfo {
- let typ = DemoViewer::get_out_type(pin.id, snarl);
+ 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()
}
- fn has_body(&mut self, _node: &DemoNode) -> bool {
+ fn has_body(&mut self, _node: &AnyNode) -> bool {
true
}
@@ -419,25 +113,25 @@ impl SnarlViewer<DemoNode> for DemoViewer {
_inputs: &[InPin],
_outputs: &[OutPin],
ui: &mut Ui,
- snarl: &mut Snarl<DemoNode>,
+ snarl: &mut Snarl<AnyNode>,
) {
if snarl[node].show_body(ui).changed() {
self.dirty = true;
}
}
- fn has_graph_menu(&mut self, _pos: egui::Pos2, _snarl: &mut Snarl<DemoNode>) -> bool {
+ fn has_graph_menu(&mut self, _pos: egui::Pos2, _snarl: &mut Snarl<AnyNode>) -> bool {
true
}
- fn show_graph_menu(&mut self, pos: egui::Pos2, ui: &mut Ui, snarl: &mut Snarl<DemoNode>) {
- if let Some(node) = DemoNode::pick_node(ui) {
+ fn show_graph_menu(&mut self, pos: egui::Pos2, ui: &mut Ui, snarl: &mut Snarl<AnyNode>) {
+ if let Some(node) = AnyNode::pick_node(ui) {
snarl.insert_node(pos, node);
ui.close();
}
}
- fn has_node_menu(&mut self, _node: &DemoNode) -> bool {
+ fn has_node_menu(&mut self, _node: &AnyNode) -> bool {
true
}
@@ -447,7 +141,7 @@ impl SnarlViewer<DemoNode> for DemoViewer {
_inputs: &[InPin],
_outputs: &[OutPin],
ui: &mut Ui,
- snarl: &mut Snarl<DemoNode>,
+ snarl: &mut Snarl<AnyNode>,
) {
ui.label("Node menu");
if ui.button("Remove").clicked() {
@@ -457,9 +151,9 @@ impl SnarlViewer<DemoNode> for DemoViewer {
}
}
-pub struct DemoApp {
- snarl: Snarl<DemoNode>,
- viewer: DemoViewer,
+pub struct App {
+ snarl: Snarl<AnyNode>,
+ viewer: Viewer,
preview: preview::Custom3d,
}
@@ -493,8 +187,8 @@ const fn default_style() -> SnarlStyle {
}
}
-impl DemoApp {
- pub fn new(cx: &CreationContext) -> Self {
+impl App {
+ pub fn new(cx: &eframe::CreationContext) -> Self {
cx.egui_ctx.style_mut(|style| style.animation_time *= 10.0);
let snarl = cx.storage.map_or_else(Snarl::new, |storage| {
@@ -507,15 +201,15 @@ impl DemoApp {
info!("about to init");
- DemoApp {
+ App {
snarl,
- viewer: DemoViewer { dirty: true },
+ viewer: Viewer { dirty: true },
preview: preview::Custom3d::new(cx).expect("Failed to init preview"),
}
}
}
-impl App for DemoApp {
+impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
@@ -550,7 +244,7 @@ impl App for DemoApp {
if self.viewer.dirty {
self.viewer.dirty = false;
let mut buf = String::new();
- match DemoViewer::compile(&self.snarl, &mut buf) {
+ match self.snarl.compile(&mut buf) {
Ok(()) => {
info!("{}", buf);
shader = Some(buf);
@@ -591,7 +285,7 @@ fn main() -> eframe::Result<()> {
eframe::run_native(
"egui-snarl demo",
native_options,
- Box::new(|cx| Ok(Box::new(DemoApp::new(cx)))),
+ Box::new(|cx| Ok(Box::new(App::new(cx)))),
)
}
@@ -618,7 +312,7 @@ fn main() {
.start(
canvas,
web_options,
- Box::new(|cx| Ok(Box::new(DemoApp::new(cx)))),
+ Box::new(|cx| Ok(Box::new(App::new(cx)))),
)
.await
.expect("failed to start eframe");
diff --git a/src/node.rs b/src/node.rs
new file mode 100644
index 0000000..859799b
--- /dev/null
+++ b/src/node.rs
@@ -0,0 +1,75 @@
+use enum_dispatch::enum_dispatch;
+use std::fmt;
+
+use crate::library;
+use crate::library::*;
+use crate::types::{Type, TypeSignature};
+
+/// an instantiable Node, parametrized by its connected input types
+#[enum_dispatch]
+pub trait ConcreteNode {
+ fn max_inputs(&self) -> usize;
+
+ // set of possible input type combinations given current connections
+ fn signatures_matching(&self, connected: &[Option<Type>]) -> Vec<TypeSignature>;
+
+ fn signature(&self, connected: &[Option<Type>]) -> TypeSignature;
+
+ fn compile(
+ &self,
+ signature: TypeSignature,
+ inputs: Vec<String>,
+ outputs: Vec<String>,
+ f: &mut dyn fmt::Write,
+ ) -> fmt::Result;
+
+ fn show_body(&mut self, ui: &mut egui::Ui) -> egui::Response {
+ ui.response()
+ }
+}
+
+#[enum_dispatch(ConcreteNode)]
+#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
+pub enum AnyNode {
+ Constant(library::AnyConstant),
+ Conversion(library::Conversion),
+ Input(library::Input),
+ Builtin(library::BuiltinFunction),
+ Output(library::Output),
+}
+
+impl NodeIndex for AnyNode {
+ const TITLE: &'static str = "";
+
+ fn all() -> impl Iterator<Item = Self> {
+ (library::AnyConstant::all().map(AnyNode::from))
+ .chain(library::Conversion::all().map(AnyNode::from))
+ .chain(library::Input::all().map(AnyNode::from))
+ .chain(library::Output::all().map(AnyNode::from))
+ .chain(library::BuiltinFunction::all().map(AnyNode::from))
+ }
+
+ fn pick_node(ui: &mut egui::Ui) -> Option<Self> {
+ ui.label("Add node");
+
+ let a = library::AnyConstant::pick_node(ui).map(AnyNode::from);
+ let b = library::Conversion::pick_node(ui).map(AnyNode::from);
+ let c = library::Input::pick_node(ui).map(AnyNode::from);
+ let d = library::Output::pick_node(ui).map(AnyNode::from);
+ let e = library::BuiltinFunction::pick_node(ui).map(AnyNode::from);
+
+ a.or(b).or(c).or(d).or(e)
+ }
+}
+
+impl fmt::Display for AnyNode {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Constant(x) => fmt::Display::fmt(x, f),
+ Self::Conversion(x) => fmt::Display::fmt(x, f),
+ Self::Input(x) => fmt::Display::fmt(x, f),
+ Self::Builtin(x) => fmt::Display::fmt(x, f),
+ Self::Output(x) => fmt::Display::fmt(x, f),
+ }
+ }
+}
diff --git a/src/snarl_ext.rs b/src/snarl_ext.rs
new file mode 100644
index 0000000..7d0464d
--- /dev/null
+++ b/src/snarl_ext.rs
@@ -0,0 +1,104 @@
+use egui_snarl::{InPinId, NodeId, OutPinId, Snarl};
+use std::fmt;
+use topological_sort::TopologicalSort;
+
+use crate::node::{AnyNode, ConcreteNode};
+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;
+}
+
+pub trait Compilable {
+ fn compile_node(&self, node: NodeId, f: &mut dyn fmt::Write) -> fmt::Result;
+ fn compile(&self, f: &mut dyn fmt::Write) -> fmt::Result;
+}
+
+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 out_pin_type(&self, pin: OutPinId) -> Type {
+ let sig = &self.get_node_signature(pin.node);
+ sig.outputs[pin.output]
+ }
+
+ 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) -> TypeSignature {
+ self[node].signature(&self.get_input_types(node))
+ }
+}
+
+fn compile_output(pin: OutPinId) -> String {
+ let node_id = pin.node.0;
+ let out_id = pin.output;
+ format!("n{node_id}_o{out_id}")
+}
+
+impl Compilable for Snarl<AnyNode> {
+ fn compile(&self, f: &mut dyn fmt::Write) -> fmt::Result {
+ let mut order = TopologicalSort::<NodeId>::new();
+ for (out, inp) in self.wires() {
+ order.add_dependency(out.node, inp.node);
+ }
+
+ write!(
+ f,
+ "
+#version 450
+in vec2 in_uv;
+out vec4 out_color;
+
+layout(std140, binding = 0) uniform Uniforms {{
+ vec2 in_resolution;
+ float in_time;
+ float _pad3;
+}} uniforms;
+
+void main() {{
+"
+ )?;
+ while let Some(id) = order.pop() {
+ self.compile_node(id, f)?;
+ }
+ writeln!(f, "}}")?;
+
+ Ok(())
+ }
+
+ 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())
+ .map(
+ |input| match &*self.in_pin(InPinId { node, input }).remotes {
+ [] => Err(fmt::Error),
+ [pin] => Ok(compile_output(*pin)),
+ _ => unreachable!("cannot connect to multiple inputs"),
+ },
+ )
+ .collect();
+
+ let outputs: Vec<String> = (0..signature.outputs.len())
+ .map(|output| compile_output(OutPinId { node, output }))
+ .collect();
+
+ self[node].compile(signature, inputs?, outputs, f)
+ }
+}
diff --git a/src/types.rs b/src/types.rs
new file mode 100644
index 0000000..b1ed2fb
--- /dev/null
+++ b/src/types.rs
@@ -0,0 +1,145 @@
+use std::fmt;
+
+#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+pub enum FloatPrecision {
+ Single,
+ Double,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+pub enum ScalarType {
+ Float(FloatPrecision),
+ Int,
+ UInt,
+ Bool,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+#[repr(u8)]
+pub enum Dimension {
+ D2 = 2,
+ D3 = 3,
+ D4 = 4,
+}
+impl fmt::Display for Dimension {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", *self as u8)
+ }
+}
+impl Dimension {
+ pub const fn from(v: usize) -> Dimension {
+ match v {
+ 2 => Self::D2,
+ 3 => Self::D3,
+ 4 => Self::D4,
+ _ => panic!("invalid dimension value"),
+ }
+ }
+}
+
+/// a GLSL type
+#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
+pub enum Type {
+ Scalar(ScalarType),
+ Vector(ScalarType, Dimension),
+ Matrix(FloatPrecision, Dimension, Dimension),
+}
+
+impl From<ScalarType> for Type {
+ fn from(val: ScalarType) -> Self {
+ Self::Scalar(val)
+ }
+}
+
+impl Type {
+ pub fn scalar(&self) -> ScalarType {
+ match self {
+ Type::Scalar(s) => *s,
+ Type::Vector(s, _) => *s,
+ Type::Matrix(p, _, _) => ScalarType::Float(*p),
+ }
+ }
+}
+
+impl fmt::Display for Type {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Type::Scalar(s) => write!(
+ f,
+ "{}",
+ match s {
+ ScalarType::Float(FloatPrecision::Single) => "float",
+ ScalarType::Float(FloatPrecision::Double) => "double",
+ ScalarType::Int => "int",
+ ScalarType::UInt => "uint",
+ ScalarType::Bool => "bool",
+ }
+ ),
+ Type::Vector(s, d) => write!(
+ f,
+ "{}vec{d}",
+ match s {
+ ScalarType::Float(FloatPrecision::Single) => "",
+ ScalarType::Float(FloatPrecision::Double) => "d",
+ ScalarType::Int => "i",
+ ScalarType::UInt => "u",
+ ScalarType::Bool => "b",
+ }
+ ),
+ Type::Matrix(p, r, c) => {
+ let pr = match p {
+ FloatPrecision::Single => "",
+ FloatPrecision::Double => "d",
+ };
+ if r == c {
+ write!(f, "{pr}mat{r}")
+ } else {
+ write!(f, "{pr}mat{r}{c}")
+ }
+ }
+ }
+ }
+}
+
+/// a single concrete type signature for a function
+#[derive(PartialEq, Debug)]
+pub struct TypeSignature {
+ pub inputs: Box<[Type]>,
+ pub outputs: Box<[Type]>,
+}
+
+impl TypeSignature {
+ pub fn new<const NI: usize, const NO: usize>(inputs: [Type; NI], outputs: [Type; NO]) -> Self {
+ Self {
+ inputs: Box::new(inputs),
+ outputs: Box::new(outputs),
+ }
+ }
+}
+
+impl TypeSignature {
+ pub fn matches_inputs(&self, connected: &[Option<Type>]) -> bool {
+ let have_inputs = connected
+ .iter()
+ .enumerate()
+ .filter(|(_, t)| matches!(t, Some(_)))
+ .map(|(i, _)| i + 1)
+ .max()
+ .unwrap_or(0);
+
+ if have_inputs > self.inputs.len() {
+ false
+ } else {
+ connected
+ .iter()
+ .zip(self.inputs.iter())
+ .all(|(connected_input, expected)| {
+ if let Some(input) = connected_input {
+ *input == *expected
+ } else {
+ true
+ }
+ })
+ }
+ }
+}