aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-01-23 18:10:39 +0000
committers-ol <s+removethis@s-ol.nu>2026-01-23 18:10:39 +0000
commitd4366a4c12224da7ef3c08b9ca9d119da11b302a (patch)
tree4b3f746e6fb071863bec80cf2e763e830226a9eb /src
parentvariadic arithmetic (diff)
downloadnodetoy-d4366a4c12224da7ef3c08b9ca9d119da11b302a.tar.gz
nodetoy-d4366a4c12224da7ef3c08b9ca9d119da11b302a.zip
Error checking
Diffstat (limited to 'src')
-rw-r--r--src/library.rs50
-rw-r--r--src/main.rs59
-rw-r--r--src/node.rs14
-rw-r--r--src/snarl_ext.rs21
4 files changed, 108 insertions, 36 deletions
diff --git a/src/library.rs b/src/library.rs
index cd3b36c..65929a0 100644
--- a/src/library.rs
+++ b/src/library.rs
@@ -3,7 +3,7 @@ use itertools::{Itertools, izip};
use serde::{Deserialize, Serialize};
use std::fmt;
-use crate::node::ConcreteNode;
+use crate::node::{CompileError, ConcreteNode};
use crate::types::{
Dimension, Dimension::*, FloatPrecision::*, ScalarType, ScalarType::*, Type, Type::*,
TypeSignature,
@@ -70,7 +70,7 @@ trait FixedNode: fmt::Debug {
inputs: Vec<String>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
let mut pairs = signature.outputs.iter().zip(outputs.iter());
let (out_typ, out_name) = pairs.next().expect("no output");
@@ -82,7 +82,8 @@ trait FixedNode: fmt::Debug {
let first = func[..1].to_lowercase();
let rest = func[1..].to_string();
let params = inputs.iter().chain(outputs.iter().skip(1)).join(", ");
- writeln!(f, "{out_typ} {out_name} = {first}{rest}({params});")
+ writeln!(f, "{out_typ} {out_name} = {first}{rest}({params});")?;
+ Ok(())
}
}
@@ -111,12 +112,12 @@ impl<T: FixedNode> ConcreteNode for T {
inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
let inputs: Option<Vec<String>> = inputs.into_iter().collect();
if let Some(inputs) = inputs {
self.compile(signature, inputs, outputs, f)
} else {
- Err(fmt::Error)
+ Err(CompileError::MissingArguments)
}
}
}
@@ -135,7 +136,7 @@ impl ConcreteNode for ProvideImpl {
trait OutputOnlyNode: fmt::Debug {
fn output_type(&self) -> Type;
- fn compile(&self, out_type: Type, output: &str, f: &mut dyn fmt::Write) -> fmt::Result;
+ fn compile(&self, out_type: Type, output: &str, f: &mut dyn fmt::Write) -> Result<(), CompileError>;
}
impl<T: OutputOnlyNode> ConcreteNode for T {
@@ -160,7 +161,7 @@ impl<T: OutputOnlyNode> ConcreteNode for T {
inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
self.compile(signature.outputs[0], &outputs[0], f)
}
}
@@ -235,7 +236,7 @@ impl ConcreteNode for BinArithmetic {
inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
let out_typ = &signature.outputs[0];
let out_name = &outputs[0];
let sym = match self {
@@ -245,7 +246,8 @@ impl ConcreteNode for BinArithmetic {
Self::Divide => " / ",
};
let expr = inputs.iter().filter_map(|s| s.as_ref()).join(sym);
- writeln!(f, "{out_typ} {out_name} = {expr};")
+ writeln!(f, "{out_typ} {out_name} = {expr};")?;
+ Ok(())
}
}
@@ -791,14 +793,15 @@ impl FixedNode for Output {
inputs: Vec<String>,
_outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
match signature.inputs[0] {
- Vector(_, D2) => writeln!(f, "out_color = vec4({}, 0.0, 1.0);", inputs[0]),
- Vector(_, D3) => writeln!(f, "out_color = vec4({}, 1.0);", inputs[0]),
- Vector(_, D4) => writeln!(f, "out_color = {};", inputs[0]),
- Scalar(_) => writeln!(f, "out_color = vec4(vec3({}), 1.0);", inputs[0]),
+ Vector(_, D2) => writeln!(f, "out_color = vec4({}, 0.0, 1.0);", inputs[0])?,
+ Vector(_, D3) => writeln!(f, "out_color = vec4({}, 1.0);", inputs[0])?,
+ Vector(_, D4) => writeln!(f, "out_color = {};", inputs[0])?,
+ Scalar(_) => writeln!(f, "out_color = vec4(vec3({}), 1.0);", inputs[0])?,
_ => unreachable!("invalid output type"),
- }
+ };
+ Ok(())
}
}
impl NodeIndex for Output {
@@ -968,10 +971,11 @@ where
_inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
let out_typ = Self::TYPE;
let out_name = &outputs[0];
- writeln!(f, "{out_typ} {out_name} = {};", self)
+ writeln!(f, "{out_typ} {out_name} = {};", self)?;
+ Ok(())
}
fn show_body(&mut self, ui: &mut egui::Ui) -> egui::Response {
@@ -1122,7 +1126,7 @@ impl ConcreteNode for Input {
_inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
let out_typ = &signature.outputs[0];
let out_name = &outputs[0];
writeln!(
@@ -1133,7 +1137,8 @@ impl ConcreteNode for Input {
Self::Resolution => "in_resolution",
Self::Time => "in_time",
}
- )
+ )?;
+ Ok(())
}
}
impl NodeIndex for Input {
@@ -1173,7 +1178,7 @@ impl FixedNode for SplitVector {
inputs: Vec<String>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
let input = &inputs[0];
for (typ, name, component) in izip!(
signature.outputs.into_iter(),
@@ -1223,11 +1228,12 @@ impl FixedNode for CombineVector {
inputs: Vec<String>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result {
+ ) -> Result<(), CompileError> {
let out_typ = &signature.outputs[0];
let out_name = &outputs[0];
let params = inputs.iter().join(", ");
- writeln!(f, "{out_typ} {out_name} = {out_typ}({params});")
+ writeln!(f, "{out_typ} {out_name} = {out_typ}({params});")?;
+ Ok(())
}
}
impl fmt::Display for CombineVector {
diff --git a/src/main.rs b/src/main.rs
index 35a3ca3..ad81cea 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -19,7 +19,7 @@ mod wasm;
use library::NodeIndex;
use node::{AnyNode, ConcreteNode};
-use snarl_ext::{Compilable, WithSignatures};
+use snarl_ext::{Compilable, GraphError, WithSignatures};
use types::{Dimension, FloatPrecision, ScalarType, Type};
impl Into<PinInfo> for Type {
@@ -41,12 +41,30 @@ impl Into<PinInfo> for Type {
}
}
+static ERROR_COLOR: Color32 = Color32::from_rgb(0xc0, 0x00, 0x00);
+
pub struct Viewer {
dirty: bool,
filename: Option<PathBuf>,
+ error: Option<GraphError>,
+
+ #[cfg(target_arch = "wasm32")]
save_as_dialog: Option<String>,
}
+impl Default for Viewer {
+ fn default() -> Self {
+ Self {
+ dirty: true,
+ filename: None,
+ error: None,
+
+ #[cfg(target_arch = "wasm32")]
+ save_as_dialog: None,
+ }
+ }
+}
+
impl SnarlViewer<AnyNode> for Viewer {
#[inline]
fn connect(&mut self, from: &OutPin, to: &InPin, snarl: &mut Snarl<AnyNode>) {
@@ -126,6 +144,23 @@ impl SnarlViewer<AnyNode> for Viewer {
}
}
+ fn node_frame(
+ &mut self,
+ mut default: egui::Frame,
+ node: NodeId,
+ _inputs: &[InPin],
+ _outputs: &[OutPin],
+ _snarl: &Snarl<AnyNode>,
+ ) -> egui::Frame {
+ if Some(node) == self.error.and_then(|e| e.1) {
+ default.stroke = egui::Stroke {
+ width: 2.0,
+ color: ERROR_COLOR,
+ };
+ }
+
+ default
+ }
fn has_graph_menu(&mut self, _pos: egui::Pos2, _snarl: &mut Snarl<AnyNode>) -> bool {
true
}
@@ -133,6 +168,7 @@ impl SnarlViewer<AnyNode> for Viewer {
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);
+ self.dirty = true;
ui.close();
}
}
@@ -152,6 +188,7 @@ impl SnarlViewer<AnyNode> for Viewer {
ui.label("Node menu");
if ui.button("Remove").clicked() {
snarl.remove_node(node);
+ self.dirty = true;
ui.close();
}
}
@@ -221,11 +258,7 @@ impl App {
App {
snarl,
- viewer: Viewer {
- dirty: true,
- filename: None,
- save_as_dialog: None,
- },
+ viewer: Default::default(),
preview: preview::Preview::new(cx).expect("Failed to init preview"),
}
}
@@ -371,10 +404,13 @@ impl eframe::App for App {
match self.snarl.compile(&mut buf) {
Ok(()) => {
info!("{}", buf);
+ self.viewer.error = None;
Some(buf)
}
Err(x) => {
- error!("Error compiling: {}", x);
+ error!("Error compiling: {:?}", x);
+ self.viewer.dirty = false;
+ self.viewer.error = Some(x);
None
}
}
@@ -388,6 +424,15 @@ impl eframe::App for App {
self.viewer.dirty = false;
}
});
+
+ if let Some(err) = &self.viewer.error {
+ let message = if let Some(node) = err.1 {
+ format!("Error in node {}: {:?}", node.0, err.0)
+ } else {
+ format!("Error while compiling: {:?}", err.0)
+ };
+ ui.colored_label(Color32::from_rgb(0xff, 0, 0), message);
+ }
});
egui::CentralPanel::default().show(ctx, |ui| {
diff --git a/src/node.rs b/src/node.rs
index 3bcd73b..b63270e 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -5,6 +5,18 @@ use crate::library;
use crate::library::*;
use crate::types::{Type, TypeSignature};
+#[derive(Debug, Copy, Clone)]
+pub enum CompileError {
+ MissingArguments,
+ Codegen(fmt::Error),
+}
+
+impl From<fmt::Error> for CompileError {
+ fn from(v: fmt::Error) -> Self {
+ Self::Codegen(v)
+ }
+}
+
/// an instantiable Node, parametrized by its connected input types
#[enum_dispatch]
pub trait ConcreteNode {
@@ -24,7 +36,7 @@ pub trait ConcreteNode {
inputs: Vec<Option<String>>,
outputs: Vec<String>,
f: &mut dyn fmt::Write,
- ) -> fmt::Result;
+ ) -> Result<(), CompileError>;
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 e36eb2e..5cbb065 100644
--- a/src/snarl_ext.rs
+++ b/src/snarl_ext.rs
@@ -2,7 +2,7 @@ use egui_snarl::{InPinId, NodeId, OutPinId, Snarl};
use std::fmt;
use topological_sort::TopologicalSort;
-use crate::node::{AnyNode, ConcreteNode};
+use crate::node::{AnyNode, CompileError, ConcreteNode};
use crate::types::{Type, TypeSignature};
pub trait WithSignatures {
@@ -13,9 +13,17 @@ pub trait WithSignatures {
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) -> fmt::Result;
- fn compile(&self, f: &mut dyn fmt::Write) -> fmt::Result;
+ fn compile_node(&self, node: NodeId, f: &mut dyn fmt::Write) -> Result<(), CompileError>;
+ fn compile(&self, f: &mut dyn fmt::Write) -> Result<(), GraphError>;
}
impl WithSignatures for Snarl<AnyNode> {
@@ -57,7 +65,7 @@ fn compile_output(pin: OutPinId) -> String {
}
impl Compilable for Snarl<AnyNode> {
- fn compile(&self, f: &mut dyn fmt::Write) -> fmt::Result {
+ fn compile(&self, f: &mut dyn fmt::Write) -> Result<(), GraphError> {
let mut order = TopologicalSort::<NodeId>::new();
for (out, inp) in self.wires() {
order.add_dependency(out.node, inp.node);
@@ -80,14 +88,15 @@ void main() {{
"
)?;
while let Some(id) = order.pop() {
- self.compile_node(id, f)?;
+ self.compile_node(id, f)
+ .map_err(|inner| GraphError(inner, Some(id)))?;
}
writeln!(f, "}}")?;
Ok(())
}
- fn compile_node(&self, node: NodeId, f: &mut dyn fmt::Write) -> fmt::Result {
+ fn compile_node(&self, node: NodeId, f: &mut dyn fmt::Write) -> Result<(), CompileError> {
let signature = self.get_node_signature(node);
let inputs: Vec<Option<String>> = (0..signature.inputs.len())