1use std::collections::HashMap;
5use super::nodes::{
6 Connection, DataType, NodeId, NodeType, ParamValue, ShaderGraph, ShaderNode, Socket,
7 SocketDirection,
8};
9
10#[derive(Debug, Clone)]
16pub enum SerializeError {
17 MissingField(String),
19 ParseError(String),
21 UnknownNodeType(String),
23 FormatError(String),
25 InvalidNodeId(u64),
27 IoError(String),
29}
30
31impl std::fmt::Display for SerializeError {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 match self {
34 SerializeError::MissingField(s) => write!(f, "Missing field: {}", s),
35 SerializeError::ParseError(s) => write!(f, "Parse error: {}", s),
36 SerializeError::UnknownNodeType(s) => write!(f, "Unknown node type: {}", s),
37 SerializeError::FormatError(s) => write!(f, "Format error: {}", s),
38 SerializeError::InvalidNodeId(id) => write!(f, "Invalid node ID: {}", id),
39 SerializeError::IoError(s) => write!(f, "IO error: {}", s),
40 }
41 }
42}
43
44pub struct GraphSerializer;
72
73impl GraphSerializer {
74 pub fn serialize(graph: &ShaderGraph) -> String {
76 let mut out = String::new();
77
78 out.push_str("[graph]\n");
80 out.push_str(&format!("name = \"{}\"\n", escape_string(&graph.name)));
81 out.push_str(&format!("next_id = {}\n", graph.next_id_counter()));
82 out.push('\n');
83
84 let mut node_ids: Vec<NodeId> = graph.node_ids().collect();
86 node_ids.sort_by_key(|id| id.0);
87
88 for nid in &node_ids {
89 let node = match graph.node(nid) {
90 Some(n) => n,
91 None => continue,
92 };
93
94 out.push_str("[[node]]\n");
95 out.push_str(&format!("id = {}\n", node.id.0));
96 out.push_str(&format!("type = \"{}\"\n", node_type_to_string(&node.node_type)));
97 out.push_str(&format!("label = \"{}\"\n", escape_string(&node.label)));
98 out.push_str(&format!("enabled = {}\n", node.enabled));
99 out.push_str(&format!("editor_x = {}\n", format_f32(node.editor_x)));
100 out.push_str(&format!("editor_y = {}\n", format_f32(node.editor_y)));
101
102 if let Some(ref var) = node.conditional_var {
104 out.push_str(&format!("conditional_var = \"{}\"\n", escape_string(var)));
105 out.push_str(&format!("conditional_threshold = {}\n",
106 format_f32(node.conditional_threshold)));
107 }
108
109 for (idx, socket) in node.inputs.iter().enumerate() {
111 if let Some(ref val) = socket.default_value {
112 out.push_str(&format!("input.{}.default = \"{}\"\n",
113 idx, serialize_param_value(val)));
114 }
115 }
116
117 let mut prop_keys: Vec<&String> = node.properties.keys().collect();
119 prop_keys.sort();
120 for key in prop_keys {
121 let val = &node.properties[key];
122 out.push_str(&format!("property.{} = \"{}\"\n",
123 escape_string(key), serialize_param_value(val)));
124 }
125
126 out.push('\n');
127 }
128
129 for conn in graph.connections() {
131 out.push_str("[[connection]]\n");
132 out.push_str(&format!("from = {}:{}\n", conn.from_node.0, conn.from_socket));
133 out.push_str(&format!("to = {}:{}\n", conn.to_node.0, conn.to_socket));
134 out.push('\n');
135 }
136
137 out
138 }
139
140 pub fn deserialize(input: &str) -> Result<ShaderGraph, SerializeError> {
142 let mut graph_name = String::from("untitled");
143 let mut next_id: u64 = 1;
144 let mut nodes: Vec<ShaderNode> = Vec::new();
145 let mut connections: Vec<Connection> = Vec::new();
146
147 let mut current_section = Section::None;
148 let mut current_node: Option<NodeBuilder> = None;
149 let mut current_conn: Option<ConnBuilder> = None;
150
151 for (line_num, raw_line) in input.lines().enumerate() {
152 let line = raw_line.trim();
153
154 if line.is_empty() || line.starts_with('#') {
156 continue;
157 }
158
159 if line == "[graph]" {
161 flush_node(&mut current_node, &mut nodes)?;
162 flush_conn(&mut current_conn, &mut connections)?;
163 current_section = Section::Graph;
164 continue;
165 }
166 if line == "[[node]]" {
167 flush_node(&mut current_node, &mut nodes)?;
168 flush_conn(&mut current_conn, &mut connections)?;
169 current_section = Section::Node;
170 current_node = Some(NodeBuilder::default());
171 continue;
172 }
173 if line == "[[connection]]" {
174 flush_node(&mut current_node, &mut nodes)?;
175 flush_conn(&mut current_conn, &mut connections)?;
176 current_section = Section::Connection;
177 current_conn = Some(ConnBuilder::default());
178 continue;
179 }
180
181 let (key, value) = parse_kv(line)
183 .ok_or_else(|| SerializeError::FormatError(
184 format!("Invalid line {}: '{}'", line_num + 1, line)))?;
185
186 match current_section {
187 Section::Graph => {
188 match key.as_str() {
189 "name" => graph_name = unquote(&value),
190 "next_id" => next_id = value.parse().map_err(|e| {
191 SerializeError::ParseError(format!("next_id: {}", e))
192 })?,
193 _ => {} }
195 }
196 Section::Node => {
197 if let Some(ref mut nb) = current_node {
198 parse_node_field(nb, &key, &value)?;
199 }
200 }
201 Section::Connection => {
202 if let Some(ref mut cb) = current_conn {
203 parse_conn_field(cb, &key, &value)?;
204 }
205 }
206 Section::None => {
207 }
209 }
210 }
211
212 flush_node(&mut current_node, &mut nodes)?;
214 flush_conn(&mut current_conn, &mut connections)?;
215
216 let mut graph = ShaderGraph::new(&graph_name);
218 graph.set_next_id(next_id);
219
220 for node in nodes {
221 graph.insert_node(node);
222 }
223 for conn in connections {
224 graph.add_connection_raw(conn);
225 }
226
227 Ok(graph)
228 }
229
230 pub fn round_trip(graph: &ShaderGraph) -> Result<ShaderGraph, SerializeError> {
233 let serialized = Self::serialize(graph);
234 Self::deserialize(&serialized)
235 }
236
237 pub fn verify_round_trip(graph: &ShaderGraph) -> Result<Vec<String>, SerializeError> {
239 let restored = Self::round_trip(graph)?;
240 let mut diffs = Vec::new();
241
242 if graph.name != restored.name {
244 diffs.push(format!("Name mismatch: '{}' vs '{}'", graph.name, restored.name));
245 }
246
247 if graph.node_count() != restored.node_count() {
249 diffs.push(format!("Node count mismatch: {} vs {}",
250 graph.node_count(), restored.node_count()));
251 }
252
253 if graph.connections().len() != restored.connections().len() {
255 diffs.push(format!("Connection count mismatch: {} vs {}",
256 graph.connections().len(), restored.connections().len()));
257 }
258
259 for nid in graph.node_ids() {
261 let orig = graph.node(&nid);
262 let rest = restored.node(&nid);
263 match (orig, rest) {
264 (Some(o), Some(r)) => {
265 if o.node_type != r.node_type {
266 diffs.push(format!("Node {} type mismatch: {:?} vs {:?}",
267 nid.0, o.node_type, r.node_type));
268 }
269 if o.label != r.label {
270 diffs.push(format!("Node {} label mismatch: '{}' vs '{}'",
271 nid.0, o.label, r.label));
272 }
273 if o.enabled != r.enabled {
274 diffs.push(format!("Node {} enabled mismatch: {} vs {}",
275 nid.0, o.enabled, r.enabled));
276 }
277 for (idx, (os, rs)) in o.inputs.iter().zip(r.inputs.iter()).enumerate() {
279 if os.default_value != rs.default_value {
280 diffs.push(format!("Node {} input {} default mismatch",
281 nid.0, idx));
282 }
283 }
284 if o.properties.len() != r.properties.len() {
286 diffs.push(format!("Node {} property count mismatch: {} vs {}",
287 nid.0, o.properties.len(), r.properties.len()));
288 }
289 }
290 (Some(_), None) => {
291 diffs.push(format!("Node {} missing in restored graph", nid.0));
292 }
293 (None, Some(_)) => {
294 diffs.push(format!("Node {} unexpected in restored graph", nid.0));
295 }
296 (None, None) => {}
297 }
298 }
299
300 let orig_conns: std::collections::HashSet<_> = graph.connections().iter()
302 .map(|c| (c.from_node.0, c.from_socket, c.to_node.0, c.to_socket))
303 .collect();
304 let rest_conns: std::collections::HashSet<_> = restored.connections().iter()
305 .map(|c| (c.from_node.0, c.from_socket, c.to_node.0, c.to_socket))
306 .collect();
307
308 for c in &orig_conns {
309 if !rest_conns.contains(c) {
310 diffs.push(format!("Connection {}:{} -> {}:{} missing in restored",
311 c.0, c.1, c.2, c.3));
312 }
313 }
314 for c in &rest_conns {
315 if !orig_conns.contains(c) {
316 diffs.push(format!("Connection {}:{} -> {}:{} unexpected in restored",
317 c.0, c.1, c.2, c.3));
318 }
319 }
320
321 Ok(diffs)
322 }
323}
324
325fn serialize_param_value(val: &ParamValue) -> String {
331 match val {
332 ParamValue::Float(v) => format!("float:{}", format_f32(*v)),
333 ParamValue::Vec2(v) => format!("vec2:{},{}", format_f32(v[0]), format_f32(v[1])),
334 ParamValue::Vec3(v) => format!("vec3:{},{},{}", format_f32(v[0]), format_f32(v[1]), format_f32(v[2])),
335 ParamValue::Vec4(v) => format!("vec4:{},{},{},{}", format_f32(v[0]), format_f32(v[1]), format_f32(v[2]), format_f32(v[3])),
336 ParamValue::Int(v) => format!("int:{}", v),
337 ParamValue::Bool(v) => format!("bool:{}", v),
338 ParamValue::String(v) => format!("string:{}", v),
339 }
340}
341
342fn deserialize_param_value(s: &str) -> Result<ParamValue, SerializeError> {
344 let colon_pos = s.find(':')
345 .ok_or_else(|| SerializeError::ParseError(format!("No type tag in value: '{}'", s)))?;
346 let type_tag = &s[..colon_pos];
347 let value_str = &s[colon_pos + 1..];
348
349 match type_tag {
350 "float" => {
351 let v: f32 = value_str.parse()
352 .map_err(|e| SerializeError::ParseError(format!("float: {}", e)))?;
353 Ok(ParamValue::Float(v))
354 }
355 "vec2" => {
356 let parts: Vec<&str> = value_str.split(',').collect();
357 if parts.len() != 2 {
358 return Err(SerializeError::ParseError("vec2 needs 2 components".to_string()));
359 }
360 let x: f32 = parts[0].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec2.x: {}", e)))?;
361 let y: f32 = parts[1].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec2.y: {}", e)))?;
362 Ok(ParamValue::Vec2([x, y]))
363 }
364 "vec3" => {
365 let parts: Vec<&str> = value_str.split(',').collect();
366 if parts.len() != 3 {
367 return Err(SerializeError::ParseError("vec3 needs 3 components".to_string()));
368 }
369 let x: f32 = parts[0].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec3.x: {}", e)))?;
370 let y: f32 = parts[1].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec3.y: {}", e)))?;
371 let z: f32 = parts[2].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec3.z: {}", e)))?;
372 Ok(ParamValue::Vec3([x, y, z]))
373 }
374 "vec4" => {
375 let parts: Vec<&str> = value_str.split(',').collect();
376 if parts.len() != 4 {
377 return Err(SerializeError::ParseError("vec4 needs 4 components".to_string()));
378 }
379 let x: f32 = parts[0].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec4.x: {}", e)))?;
380 let y: f32 = parts[1].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec4.y: {}", e)))?;
381 let z: f32 = parts[2].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec4.z: {}", e)))?;
382 let w: f32 = parts[3].trim().parse().map_err(|e| SerializeError::ParseError(format!("vec4.w: {}", e)))?;
383 Ok(ParamValue::Vec4([x, y, z, w]))
384 }
385 "int" => {
386 let v: i32 = value_str.parse()
387 .map_err(|e| SerializeError::ParseError(format!("int: {}", e)))?;
388 Ok(ParamValue::Int(v))
389 }
390 "bool" => {
391 let v: bool = value_str.parse()
392 .map_err(|e| SerializeError::ParseError(format!("bool: {}", e)))?;
393 Ok(ParamValue::Bool(v))
394 }
395 "string" => {
396 Ok(ParamValue::String(value_str.to_string()))
397 }
398 _ => Err(SerializeError::ParseError(format!("Unknown type tag: '{}'", type_tag))),
399 }
400}
401
402fn node_type_to_string(nt: &NodeType) -> &'static str {
407 match nt {
408 NodeType::Color => "Color",
409 NodeType::Texture => "Texture",
410 NodeType::VertexPosition => "VertexPosition",
411 NodeType::VertexNormal => "VertexNormal",
412 NodeType::Time => "Time",
413 NodeType::CameraPos => "CameraPos",
414 NodeType::GameStateVar => "GameStateVar",
415 NodeType::Translate => "Translate",
416 NodeType::Rotate => "Rotate",
417 NodeType::Scale => "Scale",
418 NodeType::WorldToLocal => "WorldToLocal",
419 NodeType::LocalToWorld => "LocalToWorld",
420 NodeType::Add => "Add",
421 NodeType::Sub => "Sub",
422 NodeType::Mul => "Mul",
423 NodeType::Div => "Div",
424 NodeType::Dot => "Dot",
425 NodeType::Cross => "Cross",
426 NodeType::Normalize => "Normalize",
427 NodeType::Length => "Length",
428 NodeType::Abs => "Abs",
429 NodeType::Floor => "Floor",
430 NodeType::Ceil => "Ceil",
431 NodeType::Fract => "Fract",
432 NodeType::Mod => "Mod",
433 NodeType::Pow => "Pow",
434 NodeType::Sqrt => "Sqrt",
435 NodeType::Sin => "Sin",
436 NodeType::Cos => "Cos",
437 NodeType::Tan => "Tan",
438 NodeType::Atan2 => "Atan2",
439 NodeType::Lerp => "Lerp",
440 NodeType::Clamp => "Clamp",
441 NodeType::Smoothstep => "Smoothstep",
442 NodeType::Remap => "Remap",
443 NodeType::Step => "Step",
444 NodeType::Fresnel => "Fresnel",
445 NodeType::Dissolve => "Dissolve",
446 NodeType::Distortion => "Distortion",
447 NodeType::Blur => "Blur",
448 NodeType::Sharpen => "Sharpen",
449 NodeType::EdgeDetect => "EdgeDetect",
450 NodeType::Outline => "Outline",
451 NodeType::Bloom => "Bloom",
452 NodeType::ChromaticAberration => "ChromaticAberration",
453 NodeType::HSVToRGB => "HSVToRGB",
454 NodeType::RGBToHSV => "RGBToHSV",
455 NodeType::Contrast => "Contrast",
456 NodeType::Saturation => "Saturation",
457 NodeType::Hue => "Hue",
458 NodeType::Invert => "Invert",
459 NodeType::Posterize => "Posterize",
460 NodeType::GradientMap => "GradientMap",
461 NodeType::Perlin => "Perlin",
462 NodeType::Simplex => "Simplex",
463 NodeType::Voronoi => "Voronoi",
464 NodeType::FBM => "FBM",
465 NodeType::Turbulence => "Turbulence",
466 NodeType::MainColor => "MainColor",
467 NodeType::EmissionBuffer => "EmissionBuffer",
468 NodeType::BloomBuffer => "BloomBuffer",
469 NodeType::NormalOutput => "NormalOutput",
470 }
471}
472
473fn string_to_node_type(s: &str) -> Result<NodeType, SerializeError> {
474 match s {
475 "Color" => Ok(NodeType::Color),
476 "Texture" => Ok(NodeType::Texture),
477 "VertexPosition" => Ok(NodeType::VertexPosition),
478 "VertexNormal" => Ok(NodeType::VertexNormal),
479 "Time" => Ok(NodeType::Time),
480 "CameraPos" => Ok(NodeType::CameraPos),
481 "GameStateVar" => Ok(NodeType::GameStateVar),
482 "Translate" => Ok(NodeType::Translate),
483 "Rotate" => Ok(NodeType::Rotate),
484 "Scale" => Ok(NodeType::Scale),
485 "WorldToLocal" => Ok(NodeType::WorldToLocal),
486 "LocalToWorld" => Ok(NodeType::LocalToWorld),
487 "Add" => Ok(NodeType::Add),
488 "Sub" => Ok(NodeType::Sub),
489 "Mul" => Ok(NodeType::Mul),
490 "Div" => Ok(NodeType::Div),
491 "Dot" => Ok(NodeType::Dot),
492 "Cross" => Ok(NodeType::Cross),
493 "Normalize" => Ok(NodeType::Normalize),
494 "Length" => Ok(NodeType::Length),
495 "Abs" => Ok(NodeType::Abs),
496 "Floor" => Ok(NodeType::Floor),
497 "Ceil" => Ok(NodeType::Ceil),
498 "Fract" => Ok(NodeType::Fract),
499 "Mod" => Ok(NodeType::Mod),
500 "Pow" => Ok(NodeType::Pow),
501 "Sqrt" => Ok(NodeType::Sqrt),
502 "Sin" => Ok(NodeType::Sin),
503 "Cos" => Ok(NodeType::Cos),
504 "Tan" => Ok(NodeType::Tan),
505 "Atan2" => Ok(NodeType::Atan2),
506 "Lerp" => Ok(NodeType::Lerp),
507 "Clamp" => Ok(NodeType::Clamp),
508 "Smoothstep" => Ok(NodeType::Smoothstep),
509 "Remap" => Ok(NodeType::Remap),
510 "Step" => Ok(NodeType::Step),
511 "Fresnel" => Ok(NodeType::Fresnel),
512 "Dissolve" => Ok(NodeType::Dissolve),
513 "Distortion" => Ok(NodeType::Distortion),
514 "Blur" => Ok(NodeType::Blur),
515 "Sharpen" => Ok(NodeType::Sharpen),
516 "EdgeDetect" => Ok(NodeType::EdgeDetect),
517 "Outline" => Ok(NodeType::Outline),
518 "Bloom" => Ok(NodeType::Bloom),
519 "ChromaticAberration" => Ok(NodeType::ChromaticAberration),
520 "HSVToRGB" => Ok(NodeType::HSVToRGB),
521 "RGBToHSV" => Ok(NodeType::RGBToHSV),
522 "Contrast" => Ok(NodeType::Contrast),
523 "Saturation" => Ok(NodeType::Saturation),
524 "Hue" => Ok(NodeType::Hue),
525 "Invert" => Ok(NodeType::Invert),
526 "Posterize" => Ok(NodeType::Posterize),
527 "GradientMap" => Ok(NodeType::GradientMap),
528 "Perlin" => Ok(NodeType::Perlin),
529 "Simplex" => Ok(NodeType::Simplex),
530 "Voronoi" => Ok(NodeType::Voronoi),
531 "FBM" => Ok(NodeType::FBM),
532 "Turbulence" => Ok(NodeType::Turbulence),
533 "MainColor" => Ok(NodeType::MainColor),
534 "EmissionBuffer" => Ok(NodeType::EmissionBuffer),
535 "BloomBuffer" => Ok(NodeType::BloomBuffer),
536 "NormalOutput" => Ok(NodeType::NormalOutput),
537 _ => Err(SerializeError::UnknownNodeType(s.to_string())),
538 }
539}
540
541#[derive(Debug, Clone, Copy)]
546enum Section {
547 None,
548 Graph,
549 Node,
550 Connection,
551}
552
553#[derive(Default)]
555struct NodeBuilder {
556 id: Option<u64>,
557 node_type: Option<String>,
558 label: Option<String>,
559 enabled: Option<bool>,
560 editor_x: Option<f32>,
561 editor_y: Option<f32>,
562 conditional_var: Option<String>,
563 conditional_threshold: Option<f32>,
564 input_defaults: HashMap<usize, ParamValue>,
565 properties: HashMap<String, ParamValue>,
566}
567
568#[derive(Default)]
570struct ConnBuilder {
571 from_node: Option<u64>,
572 from_socket: Option<usize>,
573 to_node: Option<u64>,
574 to_socket: Option<usize>,
575}
576
577fn parse_kv(line: &str) -> Option<(String, String)> {
579 let eq_pos = line.find('=')?;
580 let key = line[..eq_pos].trim().to_string();
581 let value = line[eq_pos + 1..].trim().to_string();
582 Some((key, value))
583}
584
585fn unquote(s: &str) -> String {
587 let s = s.trim();
588 if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') {
589 unescape_string(&s[1..s.len() - 1])
590 } else {
591 s.to_string()
592 }
593}
594
595fn escape_string(s: &str) -> String {
597 s.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n")
598}
599
600fn unescape_string(s: &str) -> String {
602 let mut result = String::new();
603 let mut chars = s.chars();
604 while let Some(c) = chars.next() {
605 if c == '\\' {
606 match chars.next() {
607 Some('n') => result.push('\n'),
608 Some('"') => result.push('"'),
609 Some('\\') => result.push('\\'),
610 Some(other) => { result.push('\\'); result.push(other); }
611 None => result.push('\\'),
612 }
613 } else {
614 result.push(c);
615 }
616 }
617 result
618}
619
620fn format_f32(v: f32) -> String {
622 if v == 0.0 {
623 "0".to_string()
624 } else if v == v.floor() && v.abs() < 1e7 {
625 format!("{}", v as i64)
626 } else {
627 format!("{:.6}", v)
628 }
629}
630
631fn parse_node_field(nb: &mut NodeBuilder, key: &str, value: &str) -> Result<(), SerializeError> {
632 match key {
633 "id" => {
634 nb.id = Some(value.parse().map_err(|e| {
635 SerializeError::ParseError(format!("node id: {}", e))
636 })?);
637 }
638 "type" => {
639 nb.node_type = Some(unquote(value));
640 }
641 "label" => {
642 nb.label = Some(unquote(value));
643 }
644 "enabled" => {
645 nb.enabled = Some(value.parse().map_err(|e| {
646 SerializeError::ParseError(format!("enabled: {}", e))
647 })?);
648 }
649 "editor_x" => {
650 nb.editor_x = Some(value.parse().map_err(|e| {
651 SerializeError::ParseError(format!("editor_x: {}", e))
652 })?);
653 }
654 "editor_y" => {
655 nb.editor_y = Some(value.parse().map_err(|e| {
656 SerializeError::ParseError(format!("editor_y: {}", e))
657 })?);
658 }
659 "conditional_var" => {
660 nb.conditional_var = Some(unquote(value));
661 }
662 "conditional_threshold" => {
663 nb.conditional_threshold = Some(value.parse().map_err(|e| {
664 SerializeError::ParseError(format!("conditional_threshold: {}", e))
665 })?);
666 }
667 _ if key.starts_with("input.") => {
668 let rest = &key["input.".len()..];
670 if let Some(dot_pos) = rest.find('.') {
671 let idx: usize = rest[..dot_pos].parse().map_err(|e| {
672 SerializeError::ParseError(format!("input index: {}", e))
673 })?;
674 let field = &rest[dot_pos + 1..];
675 if field == "default" {
676 let val_str = unquote(value);
677 let pv = deserialize_param_value(&val_str)?;
678 nb.input_defaults.insert(idx, pv);
679 }
680 }
681 }
682 _ if key.starts_with("property.") => {
683 let prop_name = key["property.".len()..].to_string();
684 let val_str = unquote(value);
685 let pv = deserialize_param_value(&val_str)?;
686 nb.properties.insert(prop_name, pv);
687 }
688 _ => {} }
690 Ok(())
691}
692
693fn parse_conn_field(cb: &mut ConnBuilder, key: &str, value: &str) -> Result<(), SerializeError> {
694 match key {
695 "from" => {
696 let (node, socket) = parse_node_socket(value)?;
697 cb.from_node = Some(node);
698 cb.from_socket = Some(socket);
699 }
700 "to" => {
701 let (node, socket) = parse_node_socket(value)?;
702 cb.to_node = Some(node);
703 cb.to_socket = Some(socket);
704 }
705 _ => {}
706 }
707 Ok(())
708}
709
710fn parse_node_socket(s: &str) -> Result<(u64, usize), SerializeError> {
712 let parts: Vec<&str> = s.trim().split(':').collect();
713 if parts.len() != 2 {
714 return Err(SerializeError::ParseError(format!("Expected node:socket format, got '{}'", s)));
715 }
716 let node: u64 = parts[0].parse().map_err(|e| {
717 SerializeError::ParseError(format!("node id in connection: {}", e))
718 })?;
719 let socket: usize = parts[1].parse().map_err(|e| {
720 SerializeError::ParseError(format!("socket index in connection: {}", e))
721 })?;
722 Ok((node, socket))
723}
724
725fn flush_node(current: &mut Option<NodeBuilder>, nodes: &mut Vec<ShaderNode>) -> Result<(), SerializeError> {
727 if let Some(nb) = current.take() {
728 let id = nb.id.ok_or_else(|| SerializeError::MissingField("node id".to_string()))?;
729 let type_str = nb.node_type.ok_or_else(|| SerializeError::MissingField("node type".to_string()))?;
730 let node_type = string_to_node_type(&type_str)?;
731
732 let mut node = ShaderNode::new(NodeId(id), node_type);
733 if let Some(label) = nb.label {
734 node.label = label;
735 }
736 if let Some(enabled) = nb.enabled {
737 node.enabled = enabled;
738 }
739 if let Some(x) = nb.editor_x {
740 node.editor_x = x;
741 }
742 if let Some(y) = nb.editor_y {
743 node.editor_y = y;
744 }
745 node.conditional_var = nb.conditional_var;
746 node.conditional_threshold = nb.conditional_threshold.unwrap_or(0.0);
747
748 for (idx, val) in nb.input_defaults {
750 if idx < node.inputs.len() {
751 node.inputs[idx].default_value = Some(val);
752 }
753 }
754
755 node.properties = nb.properties;
757
758 nodes.push(node);
759 }
760 Ok(())
761}
762
763fn flush_conn(current: &mut Option<ConnBuilder>, conns: &mut Vec<Connection>) -> Result<(), SerializeError> {
765 if let Some(cb) = current.take() {
766 let from_node = cb.from_node.ok_or_else(|| SerializeError::MissingField("connection from".to_string()))?;
767 let from_socket = cb.from_socket.ok_or_else(|| SerializeError::MissingField("connection from socket".to_string()))?;
768 let to_node = cb.to_node.ok_or_else(|| SerializeError::MissingField("connection to".to_string()))?;
769 let to_socket = cb.to_socket.ok_or_else(|| SerializeError::MissingField("connection to socket".to_string()))?;
770
771 conns.push(Connection::new(
772 NodeId(from_node), from_socket,
773 NodeId(to_node), to_socket,
774 ));
775 }
776 Ok(())
777}