From 34bc28a6bd478c113207699493d1df798e463b08 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sun, 1 Jun 2025 15:09:39 +0200 Subject: [PATCH 01/11] Prototype document network level into node insertion --- .../node_graph/document_node_definitions.rs | 112 ++++++++- .../document_node_derive.rs | 223 ++++++++++++++++++ .../utility_types/network_interface.rs | 6 + 3 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index d0be226889..825550d638 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -24,6 +24,8 @@ use graphene_std::vector::VectorDataTable; use graphene_std::*; use std::collections::{HashMap, HashSet, VecDeque}; +mod document_node_derive; + pub struct NodePropertiesContext<'a> { pub persistent_data: &'a PersistentData, pub responses: &'a mut VecDeque, @@ -91,7 +93,7 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy> = /// Defines the "signature" or "header file"-like metadata for the document nodes, but not the implementation (which is defined in the node registry). /// The [`DocumentNode`] is the instance while these [`DocumentNodeDefinition`]s are the "classes" or "blueprints" from which the instances are built. fn static_nodes() -> Vec { - let mut custom = vec![ + let custom = vec![ // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { identifier: "Identity", @@ -2114,6 +2116,7 @@ fn static_nodes() -> Vec { }, ]; +<<<<<<< HEAD // Remove struct generics for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() { let NodeTemplate { @@ -2217,6 +2220,113 @@ fn static_nodes() -> Vec { custom.push(node); } custom +||||||| parent of de38de1c7 (Prototype document network level into node insertion) + // Remove struct generics + for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() { + let NodeTemplate { + document_node: DocumentNode { implementation, .. }, + .. + } = node_template; + if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation { + if let Some((new_name, _suffix)) = name.rsplit_once("<") { + *name = Cow::Owned(new_name.to_string()) + } + }; + } + let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); + 'outer: for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() { + use graphene_core::registry::*; + let id = id.clone(); + + for node in custom.iter() { + let DocumentNodeDefinition { + node_template: NodeTemplate { + document_node: DocumentNode { implementation, .. }, + .. + }, + .. + } = node; + match implementation { + DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == &id => continue 'outer, + _ => (), + } + } + + let NodeMetadata { + display_name, + category, + fields, + description, + properties, + } = metadata; + let Some(implementations) = &node_registry.get(&id) else { continue }; + let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); + let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); + let mut input_type = &first_node_io.call_argument; + if valid_inputs.len() > 1 { + input_type = &const { generic!(D) }; + } + let output_type = &first_node_io.return_value; + + let inputs = fields + .iter() + .zip(first_node_io.inputs.iter()) + .enumerate() + .map(|(index, (field, node_io_ty))| { + let ty = field.default_type.as_ref().unwrap_or(node_io_ty); + let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; + + match field.value_source { + RegistryValueSource::None => {} + RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), + RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), + }; + + if let Some(type_default) = TaggedValue::from_type(ty) { + return NodeInput::value(type_default, exposed); + } + NodeInput::value(TaggedValue::None, true) + }) + .collect(); + + let node = DocumentNodeDefinition { + identifier: display_name, + node_template: NodeTemplate { + document_node: DocumentNode { + inputs, + manual_composition: Some(input_type.clone()), + implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), + visible: true, + skip_deduplication: false, + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + // TODO: Store information for input overrides in the node macro + input_properties: fields + .iter() + .map(|f| match f.widget_override { + RegistryWidgetOverride::None => (f.name, f.description).into(), + RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden), + RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())), + RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())), + }) + .collect(), + output_names: vec![output_type.to_string()], + has_primary_output: true, + locked: false, + ..Default::default() + }, + }, + category: category.unwrap_or("UNCATEGORIZED"), + description: Cow::Borrowed(description), + properties: *properties, + }; + custom.push(node); + } + custom +======= + document_node_derive::post_process_nodes(custom) +>>>>>>> de38de1c7 (Prototype document network level into node insertion) } // pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentNodeDefinition { diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs new file mode 100644 index 0000000000..a59276e545 --- /dev/null +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -0,0 +1,223 @@ +use super::DocumentNodeDefinition; +use crate::messages::portfolio::document::utility_types::network_interface::{ + DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodePersistentMetadata, NodePosition, NodeTemplate, NodeTypePersistentMetadata, + PropertiesRow, WidgetOverride, +}; +use graph_craft::ProtoNodeIdentifier; +use graph_craft::concrete; +use graph_craft::document::value::*; +use graph_craft::document::*; +use graphene_std::registry::*; +use graphene_std::*; +use std::collections::{HashMap, HashSet}; + +pub(super) fn post_process_nodes(mut custom: Vec) -> Vec { + // Remove struct generics + for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() { + let NodeTemplate { + document_node: DocumentNode { implementation, .. }, + .. + } = node_template; + if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation { + if let Some((new_name, _suffix)) = name.rsplit_once("<") { + *name = Cow::Owned(new_name.to_string()) + } + }; + } + let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); + 'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() { + let id = id.clone(); + + for node in custom.iter() { + let DocumentNodeDefinition { + node_template: NodeTemplate { + document_node: DocumentNode { implementation, .. }, + .. + }, + .. + } = node; + match implementation { + DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == &id => continue 'outer, + _ => (), + } + } + + let NodeMetadata { + display_name, + category, + fields, + description, + properties, + } = metadata; + let Some(implementations) = &node_registry.get(&id) else { continue }; + let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); + let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); + let mut node_io_types = vec![HashSet::new(); fields.len()]; + for (_, node_io) in implementations.iter() { + for (i, ty) in node_io.inputs.iter().enumerate() { + node_io_types[i].insert(ty.clone()); + } + } + let mut input_type = &first_node_io.call_argument; + if valid_inputs.len() > 1 { + input_type = &const { generic!(D) }; + } + let output_type = &first_node_io.return_value; + + let inputs: Vec<_> = fields + .iter() + .zip(first_node_io.inputs.iter()) + .enumerate() + .map(|(index, (field, node_io_ty))| { + let ty = field.default_type.as_ref().unwrap_or(node_io_ty); + let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; + + match field.value_source { + RegistryValueSource::None => {} + RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), + RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), + }; + + if let Some(type_default) = TaggedValue::from_type(ty) { + return NodeInput::value(type_default, exposed); + } + NodeInput::value(TaggedValue::None, true) + }) + .collect(); + let input_count = inputs.len(); + let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); + let identity_node = ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"); + let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; + let mut nodes: HashMap<_, _, _> = node_io_types + .iter() + .enumerate() + .map(|(i, inputs)| { + ( + NodeId(i as u64), + match inputs.len() { + 1 => { + let input = inputs.iter().next().unwrap(); + let input_ty = input.nested_type(); + let into_node_identifier = ProtoNodeIdentifier { + name: format!("graphene_core::ops::IntoNode<{}>", input_ty.clone()).into(), + }; + let proto_node = if into_node_registry.iter().any(|(ident, _)| { + let ident = ident.name.as_ref(); + // log::debug!("checking: {} against {}", ident, into_node_identifier.name.as_ref()); + ident == into_node_identifier.name.as_ref() + }) { + // log::debug!("placing {}", into_node_identifier.name.as_ref()); + into_node_identifier + } else { + identity_node.clone() + }; + DocumentNode { + inputs: vec![NodeInput::network(input.clone(), i)], + // manual_composition: Some(fn_input.clone()), + implementation: DocumentNodeImplementation::ProtoNode(proto_node), + visible: true, + ..Default::default() + } + } + _ => DocumentNode { + inputs: vec![NodeInput::network(generic!(X), i)], + implementation: DocumentNodeImplementation::ProtoNode(identity_node.clone()), + visible: true, + ..Default::default() + }, + }, + ) + }) + .collect(); + + let document_node = DocumentNode { + inputs: network_inputs, + manual_composition: Some(input_type.clone()), + implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), + visible: true, + skip_deduplication: false, + ..Default::default() + }; + let mut node_names: HashMap = nodes + .iter() + .map(|(id, node)| (*id, node.implementation.get_proto_node().unwrap().name.rsplit_once("::").unwrap().1.to_string())) + .collect(); + nodes.insert(NodeId(input_count as u64), document_node); + node_names.insert(NodeId(input_count as u64), display_name.to_string()); + let node_type_metadata = |id: NodeId| { + NodeTypePersistentMetadata::Node(NodePersistentMetadata::new(NodePosition::Absolute(if id.0 == input_count as u64 { + IVec2::default() + } else { + IVec2 { x: -10, y: id.0 as i32 } + }))) + }; + + let node = DocumentNodeDefinition { + identifier: display_name, + node_template: NodeTemplate { + document_node: DocumentNode { + inputs, + manual_composition: Some(input_type.clone()), + implementation: DocumentNodeImplementation::Network(NodeNetwork { + exports: vec![NodeInput::Node { + node_id: NodeId(input_count as u64), + output_index: 0, + lambda: false, + }], + nodes, + scope_injections: Default::default(), + }), + visible: true, + skip_deduplication: false, + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + // TODO: Store information for input overrides in the node macro + input_properties: fields + .iter() + .map(|f| match f.widget_override { + RegistryWidgetOverride::None => (f.name, f.description).into(), + RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden), + RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())), + RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())), + }) + .collect(), + output_names: vec![output_type.to_string()], + has_primary_output: true, + locked: false, + + network_metadata: Some(NodeNetworkMetadata { + persistent_metadata: NodeNetworkPersistentMetadata { + node_metadata: node_names + .into_iter() + .map(|(id, display_name)| { + let node_type_metadata = node_type_metadata(id); + ( + id, + DocumentNodeMetadata { + persistent_metadata: DocumentNodePersistentMetadata { + display_name, + node_type_metadata, + ..Default::default() + }, + ..Default::default() + }, + ) + }) + .collect(), + ..Default::default() + }, + ..Default::default() + }), + + ..Default::default() + }, + }, + category: category.unwrap_or("UNCATEGORIZED"), + description: Cow::Borrowed(description), + properties: *properties, + }; + custom.push(node); + } + custom +} diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 1052300bd3..640c7e64ae 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -6515,6 +6515,12 @@ pub struct NodePersistentMetadata { position: NodePosition, } +impl NodePersistentMetadata { + pub fn new(position: NodePosition) -> Self { + Self { position } + } +} + /// A layer can either be position as Absolute or in a Stack #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum LayerPosition { From a7699c79707b4955096a18f18759c68cf2b2bbbb Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 2 Jun 2025 10:17:00 +0200 Subject: [PATCH 02/11] Implement Convert trait / node for places we can't use Into --- .../document_node_derive.rs | 12 +++- node-graph/gcore/src/ops.rs | 62 ++++++++++++++++++ .../interpreted-executor/src/node_registry.rs | 64 ++++++++++++++++--- 3 files changed, 127 insertions(+), 11 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index a59276e545..6efeccd575 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -101,13 +101,19 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec let into_node_identifier = ProtoNodeIdentifier { name: format!("graphene_core::ops::IntoNode<{}>", input_ty.clone()).into(), }; + let convert_node_identifier = ProtoNodeIdentifier { + name: format!("graphene_core::ops::ConvertNode<{}>", input_ty.clone()).into(), + }; let proto_node = if into_node_registry.iter().any(|(ident, _)| { let ident = ident.name.as_ref(); - // log::debug!("checking: {} against {}", ident, into_node_identifier.name.as_ref()); ident == into_node_identifier.name.as_ref() }) { - // log::debug!("placing {}", into_node_identifier.name.as_ref()); into_node_identifier + } else if into_node_registry.iter().any(|(ident, _)| { + let ident = ident.name.as_ref(); + ident == convert_node_identifier.name.as_ref() + }) { + convert_node_identifier } else { identity_node.clone() }; @@ -122,7 +128,7 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec _ => DocumentNode { inputs: vec![NodeInput::network(generic!(X), i)], implementation: DocumentNodeImplementation::ProtoNode(identity_node.clone()), - visible: true, + visible: false, ..Default::default() }, }, diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index b5a06abfc6..70abb2cad9 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -570,6 +570,68 @@ where Box::pin(async move { input.into() }) } } +pub trait Convert: Sized { + /// Converts this type into the (usually inferred) input type. + #[must_use] + fn convert(self) -> T; +} + +macro_rules! impl_convert { + ($from:ty,$to:ty) => { + impl Convert<$to> for $from { + fn convert(self) -> $to { + self as $to + } + } + }; + ($to:ty) => { + impl_convert!(i8, $to); + impl_convert!(u8, $to); + impl_convert!(u16, $to); + impl_convert!(i16, $to); + impl_convert!(i32, $to); + impl_convert!(u32, $to); + impl_convert!(i64, $to); + impl_convert!(u64, $to); + impl_convert!(f32, $to); + impl_convert!(f64, $to); + }; +} +impl_convert!(i8); +impl_convert!(u8); +impl_convert!(u16); +impl_convert!(i16); +impl_convert!(i32); +impl_convert!(u32); +impl_convert!(i64); +impl_convert!(u64); +impl_convert!(f32); +impl_convert!(f64); + +// Convert +pub struct ConvertNode(PhantomData); +impl<_O> ConvertNode<_O> { + #[cfg(feature = "alloc")] + pub const fn new() -> Self { + Self(core::marker::PhantomData) + } +} +impl<_O> Default for ConvertNode<_O> { + fn default() -> Self { + Self::new() + } +} +impl<'input, I: 'input, _O: 'input> Node<'input, I> for ConvertNode<_O> +where + I: Convert<_O> + Sync + Send, +{ + type Output = ::dyn_any::DynFuture<'input, _O>; + + #[inline] + fn eval(&'input self, input: I) -> Self::Output { + Box::pin(async move { input.convert() }) + } +} #[cfg(test)] mod test { diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 3f151c95b9..7310154e55 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -15,7 +15,7 @@ use graphene_std::GraphicElement; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, IntoTypeErasedNode}; use graphene_std::application_io::{ImageTexture, SurfaceFrame}; use graphene_std::wasm_application_io::*; -use node_registry_macros::{async_node, into_node}; +use node_registry_macros::{async_node, convert_node, into_node}; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Arc; @@ -23,10 +23,7 @@ use wgpu_executor::{WgpuExecutor, WgpuSurface, WindowHandle}; // TODO: turn into hashmap fn node_registry() -> HashMap> { - let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ - into_node!(from: f64, to: f64), - into_node!(from: u32, to: f64), - into_node!(from: u8, to: u32), + let mut node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ into_node!(from: VectorDataTable, to: VectorDataTable), into_node!(from: VectorDataTable, to: GraphicElement), into_node!(from: VectorDataTable, to: GraphicGroupTable), @@ -137,6 +134,22 @@ fn node_registry() -> HashMap> = HashMap::new(); @@ -152,7 +165,7 @@ fn node_registry() -> HashMap { ( ProtoNodeIdentifier::new(concat!["graphene_core::ops::IntoNode<", stringify!($to), ">"]), - |mut args| { + |_| { Box::pin(async move { - args.reverse(); let node = graphene_core::ops::IntoNode::<$to>::new(); let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node); Box::new(any) as TypeErasedBox @@ -220,7 +232,43 @@ mod node_registry_macros { ) }; } + macro_rules! convert_node { + (from: $from:ty, to: numbers) => {{ + let x: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ + convert_node!(from: $from, to: i8), + convert_node!(from: $from, to: u8), + convert_node!(from: $from, to: u16), + convert_node!(from: $from, to: i16), + convert_node!(from: $from, to: i32), + convert_node!(from: $from, to: u32), + convert_node!(from: $from, to: i64), + convert_node!(from: $from, to: u64), + convert_node!(from: $from, to: f32), + convert_node!(from: $from, to: f64), + ]; + x + }}; + (from: $from:ty, to: $to:ty) => { + ( + ProtoNodeIdentifier::new(concat!["graphene_core::ops::ConvertNode<", stringify!($to), ">"]), + |_| { + Box::pin(async move { + let node = graphene_core::ops::ConvertNode::<$to>::new(); + let any: DynAnyNode<$from, _, _> = graphene_std::any::DynAnyNode::new(node); + Box::new(any) as TypeErasedBox + }) + }, + { + let node = graphene_core::ops::ConvertNode::<$to>::new(); + let mut node_io = NodeIO::<'_, $from>::to_async_node_io(&node, vec![]); + node_io.call_argument = future!(<$from as StaticType>::Static); + node_io + }, + ) + }; + } pub(crate) use async_node; + pub(crate) use convert_node; pub(crate) use into_node; } From 46bc43a6d7664a79423c300c55103e20f04f713c Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Mon, 2 Jun 2025 10:20:47 +0200 Subject: [PATCH 03/11] Add isize/usize and i128/u128 implementations for Convert trait --- node-graph/gcore/src/ops.rs | 8 ++++++++ node-graph/interpreted-executor/src/node_registry.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 70abb2cad9..54a2eebe4a 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -593,6 +593,10 @@ macro_rules! impl_convert { impl_convert!(u32, $to); impl_convert!(i64, $to); impl_convert!(u64, $to); + impl_convert!(isize, $to); + impl_convert!(usize, $to); + impl_convert!(i128, $to); + impl_convert!(u128, $to); impl_convert!(f32, $to); impl_convert!(f64, $to); }; @@ -605,6 +609,10 @@ impl_convert!(i32); impl_convert!(u32); impl_convert!(i64); impl_convert!(u64); +impl_convert!(isize); +impl_convert!(usize); +impl_convert!(i128); +impl_convert!(u128); impl_convert!(f32); impl_convert!(f64); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 7310154e55..dd7930b2bd 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -144,6 +144,10 @@ fn node_registry() -> HashMap Date: Tue, 10 Jun 2025 16:52:17 +0200 Subject: [PATCH 04/11] Factor out substitutions into preprocessor crate --- Cargo.lock | 19 ++ Cargo.toml | 4 +- editor/Cargo.toml | 1 + .../node_graph/document_node_definitions.rs | 223 +----------------- .../document_node_derive.rs | 143 +---------- editor/src/node_graph_executor/runtime.rs | 8 +- node-graph/gcore/src/ops.rs | 1 - node-graph/graphene-cli/Cargo.toml | 1 + node-graph/graphene-cli/src/main.rs | 3 + node-graph/preprocessor/Cargo.toml | 30 +++ node-graph/preprocessor/src/lib.rs | 154 ++++++++++++ 11 files changed, 224 insertions(+), 363 deletions(-) create mode 100644 node-graph/preprocessor/Cargo.toml create mode 100644 node-graph/preprocessor/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e98a3270c5..cc89fe8b23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2175,6 +2175,7 @@ dependencies = [ "graphene-std", "interpreted-executor", "log", + "preprocessor", "tokio", "wgpu", "wgpu-executor", @@ -2282,6 +2283,7 @@ dependencies = [ "log", "num_enum", "once_cell", + "preprocessor", "ron", "serde", "serde_json", @@ -4475,6 +4477,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "preprocessor" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "dyn-any", + "futures", + "glam", + "graph-craft", + "graphene-std", + "interpreted-executor", + "log", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "presser" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 5d3730d0a7..520e46e670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,12 @@ members = [ "node-graph/interpreted-executor", "node-graph/node-macro", "node-graph/wgpu-executor", + "node-graph/preprocessor", "libraries/dyn-any", "libraries/path-bool", "libraries/bezier-rs", "libraries/math-parser", - "website/other/bezier-rs-demos/wasm", + "website/other/bezier-rs-demos/wasm", "node-graph/preprocessor", ] default-members = [ "editor", @@ -34,6 +35,7 @@ resolver = "2" # Local dependencies bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any", "serde"] } dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam", "reqwest", "log-bad-types", "rc"] } +preprocessor = { path = "node-graph/preprocessor"} math-parser = { path = "libraries/math-parser" } path-bool = { path = "libraries/path-bool" } graphene-application-io = { path = "node-graph/gapplication-io" } diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 31e54a7a16..1ac6a9de11 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -29,6 +29,7 @@ graphite-proc-macros = { workspace = true } graph-craft = { workspace = true } interpreted-executor = { workspace = true } graphene-std = { workspace = true } +preprocessor = { path = "../node-graph/preprocessor" } # Workspace dependencies js-sys = { workspace = true } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 825550d638..eeb236a441 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -237,15 +237,8 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(3), 0)], + exports: vec![NodeInput::node(NodeId(2), 0)], nodes: [ - // Secondary (left) input type coercion - DocumentNode { - inputs: vec![NodeInput::network(generic!(T), 1)], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"), - manual_composition: Some(generic!(T)), - ..Default::default() - }, // Primary (bottom) input type coercion DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0)], @@ -255,7 +248,7 @@ fn static_nodes() -> Vec { }, // The monitor node is used to display a thumbnail in the UI DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0)], + inputs: vec![NodeInput::network(generic!(T), 1)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), manual_composition: Some(generic!(T)), skip_deduplication: true, @@ -264,8 +257,8 @@ fn static_nodes() -> Vec { DocumentNode { manual_composition: Some(generic!(T)), inputs: vec![ + NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0), - NodeInput::node(NodeId(2), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::LayerNode"), @@ -2116,217 +2109,7 @@ fn static_nodes() -> Vec { }, ]; -<<<<<<< HEAD - // Remove struct generics - for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() { - let NodeTemplate { - document_node: DocumentNode { implementation, .. }, - .. - } = node_template; - if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation { - if let Some((new_name, _suffix)) = name.rsplit_once("<") { - *name = Cow::Owned(new_name.to_string()) - } - }; - } - let node_registry = graphene_std::registry::NODE_REGISTRY.lock().unwrap(); - 'outer: for (id, metadata) in graphene_std::registry::NODE_METADATA.lock().unwrap().iter() { - use graphene_std::registry::*; - let id = id.clone(); - - for node in custom.iter() { - let DocumentNodeDefinition { - node_template: NodeTemplate { - document_node: DocumentNode { implementation, .. }, - .. - }, - .. - } = node; - match implementation { - DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == &id => continue 'outer, - _ => (), - } - } - - let NodeMetadata { - display_name, - category, - fields, - description, - properties, - } = metadata; - let Some(implementations) = &node_registry.get(&id) else { continue }; - let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); - let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); - let mut input_type = &first_node_io.call_argument; - if valid_inputs.len() > 1 { - input_type = &const { generic!(D) }; - } - let output_type = &first_node_io.return_value; - - let inputs = fields - .iter() - .zip(first_node_io.inputs.iter()) - .enumerate() - .map(|(index, (field, node_io_ty))| { - let ty = field.default_type.as_ref().unwrap_or(node_io_ty); - let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; - - match field.value_source { - RegistryValueSource::None => {} - RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), - RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), - }; - - if let Some(type_default) = TaggedValue::from_type(ty) { - return NodeInput::value(type_default, exposed); - } - NodeInput::value(TaggedValue::None, true) - }) - .collect(); - - let node = DocumentNodeDefinition { - identifier: display_name, - node_template: NodeTemplate { - document_node: DocumentNode { - inputs, - manual_composition: Some(input_type.clone()), - implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), - visible: true, - skip_deduplication: false, - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - // TODO: Store information for input overrides in the node macro - input_properties: fields - .iter() - .map(|f| match f.widget_override { - RegistryWidgetOverride::None => (f.name, f.description).into(), - RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden), - RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())), - RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())), - }) - .collect(), - output_names: vec![output_type.to_string()], - has_primary_output: true, - locked: false, - ..Default::default() - }, - }, - category: category.unwrap_or("UNCATEGORIZED"), - description: Cow::Borrowed(description), - properties: *properties, - }; - custom.push(node); - } - custom -||||||| parent of de38de1c7 (Prototype document network level into node insertion) - // Remove struct generics - for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() { - let NodeTemplate { - document_node: DocumentNode { implementation, .. }, - .. - } = node_template; - if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation { - if let Some((new_name, _suffix)) = name.rsplit_once("<") { - *name = Cow::Owned(new_name.to_string()) - } - }; - } - let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); - 'outer: for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() { - use graphene_core::registry::*; - let id = id.clone(); - - for node in custom.iter() { - let DocumentNodeDefinition { - node_template: NodeTemplate { - document_node: DocumentNode { implementation, .. }, - .. - }, - .. - } = node; - match implementation { - DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == &id => continue 'outer, - _ => (), - } - } - - let NodeMetadata { - display_name, - category, - fields, - description, - properties, - } = metadata; - let Some(implementations) = &node_registry.get(&id) else { continue }; - let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); - let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); - let mut input_type = &first_node_io.call_argument; - if valid_inputs.len() > 1 { - input_type = &const { generic!(D) }; - } - let output_type = &first_node_io.return_value; - - let inputs = fields - .iter() - .zip(first_node_io.inputs.iter()) - .enumerate() - .map(|(index, (field, node_io_ty))| { - let ty = field.default_type.as_ref().unwrap_or(node_io_ty); - let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; - - match field.value_source { - RegistryValueSource::None => {} - RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), - RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), - }; - - if let Some(type_default) = TaggedValue::from_type(ty) { - return NodeInput::value(type_default, exposed); - } - NodeInput::value(TaggedValue::None, true) - }) - .collect(); - - let node = DocumentNodeDefinition { - identifier: display_name, - node_template: NodeTemplate { - document_node: DocumentNode { - inputs, - manual_composition: Some(input_type.clone()), - implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), - visible: true, - skip_deduplication: false, - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - // TODO: Store information for input overrides in the node macro - input_properties: fields - .iter() - .map(|f| match f.widget_override { - RegistryWidgetOverride::None => (f.name, f.description).into(), - RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden), - RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())), - RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())), - }) - .collect(), - output_names: vec![output_type.to_string()], - has_primary_output: true, - locked: false, - ..Default::default() - }, - }, - category: category.unwrap_or("UNCATEGORIZED"), - description: Cow::Borrowed(description), - properties: *properties, - }; - custom.push(node); - } - custom -======= document_node_derive::post_process_nodes(custom) ->>>>>>> de38de1c7 (Prototype document network level into node insertion) } // pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentNodeDefinition { diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index 6efeccd575..f15edc181a 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -1,11 +1,6 @@ use super::DocumentNodeDefinition; -use crate::messages::portfolio::document::utility_types::network_interface::{ - DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodePersistentMetadata, NodePosition, NodeTemplate, NodeTypePersistentMetadata, - PropertiesRow, WidgetOverride, -}; +use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride}; use graph_craft::ProtoNodeIdentifier; -use graph_craft::concrete; -use graph_craft::document::value::*; use graph_craft::document::*; use graphene_std::registry::*; use graphene_std::*; @@ -52,127 +47,20 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec let Some(implementations) = &node_registry.get(&id) else { continue }; let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); - let mut node_io_types = vec![HashSet::new(); fields.len()]; - for (_, node_io) in implementations.iter() { - for (i, ty) in node_io.inputs.iter().enumerate() { - node_io_types[i].insert(ty.clone()); - } - } let mut input_type = &first_node_io.call_argument; if valid_inputs.len() > 1 { input_type = &const { generic!(D) }; } let output_type = &first_node_io.return_value; - let inputs: Vec<_> = fields - .iter() - .zip(first_node_io.inputs.iter()) - .enumerate() - .map(|(index, (field, node_io_ty))| { - let ty = field.default_type.as_ref().unwrap_or(node_io_ty); - let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; - - match field.value_source { - RegistryValueSource::None => {} - RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), - RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), - }; - - if let Some(type_default) = TaggedValue::from_type(ty) { - return NodeInput::value(type_default, exposed); - } - NodeInput::value(TaggedValue::None, true) - }) - .collect(); - let input_count = inputs.len(); - let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); - let identity_node = ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"); - let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; - let mut nodes: HashMap<_, _, _> = node_io_types - .iter() - .enumerate() - .map(|(i, inputs)| { - ( - NodeId(i as u64), - match inputs.len() { - 1 => { - let input = inputs.iter().next().unwrap(); - let input_ty = input.nested_type(); - let into_node_identifier = ProtoNodeIdentifier { - name: format!("graphene_core::ops::IntoNode<{}>", input_ty.clone()).into(), - }; - let convert_node_identifier = ProtoNodeIdentifier { - name: format!("graphene_core::ops::ConvertNode<{}>", input_ty.clone()).into(), - }; - let proto_node = if into_node_registry.iter().any(|(ident, _)| { - let ident = ident.name.as_ref(); - ident == into_node_identifier.name.as_ref() - }) { - into_node_identifier - } else if into_node_registry.iter().any(|(ident, _)| { - let ident = ident.name.as_ref(); - ident == convert_node_identifier.name.as_ref() - }) { - convert_node_identifier - } else { - identity_node.clone() - }; - DocumentNode { - inputs: vec![NodeInput::network(input.clone(), i)], - // manual_composition: Some(fn_input.clone()), - implementation: DocumentNodeImplementation::ProtoNode(proto_node), - visible: true, - ..Default::default() - } - } - _ => DocumentNode { - inputs: vec![NodeInput::network(generic!(X), i)], - implementation: DocumentNodeImplementation::ProtoNode(identity_node.clone()), - visible: false, - ..Default::default() - }, - }, - ) - }) - .collect(); - - let document_node = DocumentNode { - inputs: network_inputs, - manual_composition: Some(input_type.clone()), - implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), - visible: true, - skip_deduplication: false, - ..Default::default() - }; - let mut node_names: HashMap = nodes - .iter() - .map(|(id, node)| (*id, node.implementation.get_proto_node().unwrap().name.rsplit_once("::").unwrap().1.to_string())) - .collect(); - nodes.insert(NodeId(input_count as u64), document_node); - node_names.insert(NodeId(input_count as u64), display_name.to_string()); - let node_type_metadata = |id: NodeId| { - NodeTypePersistentMetadata::Node(NodePersistentMetadata::new(NodePosition::Absolute(if id.0 == input_count as u64 { - IVec2::default() - } else { - IVec2 { x: -10, y: id.0 as i32 } - }))) - }; - + let inputs = preprocessor::node_inputs(fields, first_node_io); let node = DocumentNodeDefinition { identifier: display_name, node_template: NodeTemplate { document_node: DocumentNode { inputs, manual_composition: Some(input_type.clone()), - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::Node { - node_id: NodeId(input_count as u64), - output_index: 0, - lambda: false, - }], - nodes, - scope_injections: Default::default(), - }), + implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), visible: true, skip_deduplication: false, ..Default::default() @@ -191,31 +79,6 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec output_names: vec![output_type.to_string()], has_primary_output: true, locked: false, - - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: node_names - .into_iter() - .map(|(id, display_name)| { - let node_type_metadata = node_type_metadata(id); - ( - id, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name, - node_type_metadata, - ..Default::default() - }, - ..Default::default() - }, - ) - }) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() }, }, diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 1bd207aa13..92d8dbe1cf 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -45,6 +45,8 @@ pub struct NodeRuntime { /// Which node is inspected and which monitor node is used (if any) for the current execution inspect_state: Option, + substitutions: HashMap, + // TODO: Remove, it doesn't need to be persisted anymore /// The current renders of the thumbnails for layer nodes. thumbnail_renders: HashMap>, @@ -120,6 +122,8 @@ impl NodeRuntime { node_graph_errors: Vec::new(), monitor_nodes: Vec::new(), + substitutions: preprocessor::generate_node_substitutions(), + thumbnail_renders: Default::default(), vector_modify: Default::default(), inspect_state: None, @@ -221,11 +225,13 @@ impl NodeRuntime { } } - async fn update_network(&mut self, graph: NodeNetwork) -> Result { + async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { + preprocessor::expand_network(&mut graph, &self.substitutions); let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone()); // We assume only one output assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled"); + let c = Compiler {}; let proto_network = match c.compile_single(scoped_network) { Ok(network) => network, diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 54a2eebe4a..64c9ed16fa 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -619,7 +619,6 @@ impl_convert!(f64); // Convert pub struct ConvertNode(PhantomData); impl<_O> ConvertNode<_O> { - #[cfg(feature = "alloc")] pub const fn new() -> Self { Self(core::marker::PhantomData) } diff --git a/node-graph/graphene-cli/Cargo.toml b/node-graph/graphene-cli/Cargo.toml index f3c30fe122..125757f26c 100644 --- a/node-graph/graphene-cli/Cargo.toml +++ b/node-graph/graphene-cli/Cargo.toml @@ -24,6 +24,7 @@ graphene-core = { workspace = true } graphene-std = { workspace = true } interpreted-executor = { workspace = true } graph-craft = { workspace = true, features = ["loading"] } +preprocessor = { path = "../preprocessor" } # Workspace dependencies log = { workspace = true } diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index aab5ab85c1..d3eb13d0f7 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -183,8 +183,11 @@ fn fix_nodes(network: &mut NodeNetwork) { fn compile_graph(document_string: String, editor_api: Arc) -> Result> { let mut network = load_network(&document_string); fix_nodes(&mut network); + let substitutions = preprocessor::generate_node_substitutions(); + preprocessor::expand_network(&mut network, &substitutions); let wrapped_network = wrap_network_in_scope(network.clone(), editor_api); + let compiler = Compiler {}; compiler.compile_single(wrapped_network).map_err(|x| x.into()) } diff --git a/node-graph/preprocessor/Cargo.toml b/node-graph/preprocessor/Cargo.toml new file mode 100644 index 0000000000..9848f3b3ef --- /dev/null +++ b/node-graph/preprocessor/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "preprocessor" +version = "0.1.0" +edition = "2024" +license = "MIT OR Apache-2.0" + +[features] + +[dependencies] +# Local dependencies +dyn-any = { path = "../../libraries/dyn-any", features = [ + "log-bad-types", + "rc", + "glam", +] } + +# Workspace dependencies +graphene-std = { workspace = true, features = ["gpu"] } +graph-craft = { workspace = true} +interpreted-executor = { path = "../interpreted-executor" } +log = { workspace = true } +futures = { workspace = true } +glam = { workspace = true } +base64 = { workspace = true } + +# Optional workspace dependencies +serde = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } +serde_json = { workspace = true, optional = true } + diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs new file mode 100644 index 0000000000..1f43d22723 --- /dev/null +++ b/node-graph/preprocessor/src/lib.rs @@ -0,0 +1,154 @@ +use graph_craft::ProtoNodeIdentifier; +use graph_craft::concrete; +use graph_craft::document::value::*; +use graph_craft::document::*; +use graph_craft::proto::RegistryValueSource; +use graphene_std::registry::*; +use graphene_std::*; +use std::collections::{HashMap, HashSet}; + +pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap) { + for node in network.nodes.values_mut() { + match &mut node.implementation { + DocumentNodeImplementation::Network(node_network) => expand_network(node_network, substitutions), + DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { + if let Some(new_node) = substitutions.get(proto_node_identifier.name.as_ref()) { + node.implementation = new_node.implementation.clone(); + } + } + DocumentNodeImplementation::Extract => (), + } + } +} + +pub fn generate_node_substitutions() -> HashMap { + let mut custom = HashMap::new(); + let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); + for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() { + let id = id.clone(); + + let NodeMetadata { fields, .. } = metadata; + let Some(implementations) = &node_registry.get(&id) else { continue }; + let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); + let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); + let mut node_io_types = vec![HashSet::new(); fields.len()]; + for (_, node_io) in implementations.iter() { + for (i, ty) in node_io.inputs.iter().enumerate() { + node_io_types[i].insert(ty.clone()); + } + } + let mut input_type = &first_node_io.call_argument; + if valid_inputs.len() > 1 { + input_type = &const { generic!(D) }; + } + + let inputs: Vec<_> = node_inputs(fields, first_node_io); + let input_count = inputs.len(); + let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); + + let identity_node = ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"); + + let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; + + let mut nodes: HashMap<_, _, _> = node_io_types + .iter() + .enumerate() + .map(|(i, inputs)| { + ( + NodeId(i as u64), + match inputs.len() { + 1 => { + let input = inputs.iter().next().unwrap(); + let input_ty = input.nested_type(); + let into_node_identifier = ProtoNodeIdentifier { + name: format!("graphene_core::ops::IntoNode<{}>", input_ty.clone()).into(), + }; + let convert_node_identifier = ProtoNodeIdentifier { + name: format!("graphene_core::ops::ConvertNode<{}>", input_ty.clone()).into(), + }; + let proto_node = if into_node_registry.iter().any(|(ident, _)| { + let ident = ident.name.as_ref(); + ident == into_node_identifier.name.as_ref() + }) { + into_node_identifier + } else if into_node_registry.iter().any(|(ident, _)| { + let ident = ident.name.as_ref(); + ident == convert_node_identifier.name.as_ref() + }) { + convert_node_identifier + } else { + identity_node.clone() + }; + DocumentNode { + inputs: vec![NodeInput::network(input.clone(), i)], + // manual_composition: Some(fn_input.clone()), + implementation: DocumentNodeImplementation::ProtoNode(proto_node), + visible: true, + ..Default::default() + } + } + _ => DocumentNode { + inputs: vec![NodeInput::network(generic!(X), i)], + implementation: DocumentNodeImplementation::ProtoNode(identity_node.clone()), + visible: false, + ..Default::default() + }, + }, + ) + }) + .collect(); + + let document_node = DocumentNode { + inputs: network_inputs, + manual_composition: Some(input_type.clone()), + implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()), + visible: true, + skip_deduplication: false, + ..Default::default() + }; + nodes.insert(NodeId(input_count as u64), document_node); + + let node = DocumentNode { + inputs, + manual_composition: Some(input_type.clone()), + implementation: DocumentNodeImplementation::Network(NodeNetwork { + exports: vec![NodeInput::Node { + node_id: NodeId(input_count as u64), + output_index: 0, + lambda: false, + }], + nodes, + scope_injections: Default::default(), + }), + visible: true, + skip_deduplication: false, + ..Default::default() + }; + + custom.insert(id.clone(), node); + } + custom +} + +pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTypes) -> Vec { + fields + .iter() + .zip(first_node_io.inputs.iter()) + .enumerate() + .map(|(index, (field, node_io_ty))| { + let ty = field.default_type.as_ref().unwrap_or(node_io_ty); + let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed }; + + match field.value_source { + RegistryValueSource::None => {} + RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), + RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), + }; + + if let Some(type_default) = TaggedValue::from_type(ty) { + return NodeInput::value(type_default, exposed); + } + NodeInput::value(TaggedValue::None, true) + }) + .collect() +} From ffa7f1a7759e1718d539f64686a83a84e057d645 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sun, 15 Jun 2025 10:01:33 +0200 Subject: [PATCH 05/11] Simplify layer node further --- .../node_graph/document_node_definitions.rs | 31 +++---------------- node-graph/preprocessor/src/lib.rs | 9 ++++++ 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index eeb236a441..3a56183c29 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -237,28 +237,21 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], + exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ - // Primary (bottom) input type coercion - DocumentNode { - inputs: vec![NodeInput::network(generic!(T), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToGroupNode"), - manual_composition: Some(generic!(T)), - ..Default::default() - }, // The monitor node is used to display a thumbnail in the UI DocumentNode { - inputs: vec![NodeInput::network(generic!(T), 1)], + inputs: vec![NodeInput::network(concrete!(GraphicElement), 1)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), - manual_composition: Some(generic!(T)), + manual_composition: Some(concrete!(Context)), skip_deduplication: true, ..Default::default() }, DocumentNode { manual_composition: Some(generic!(T)), inputs: vec![ + NodeInput::network(concrete!(GraphicGroupTable), 0), NodeInput::node(NodeId(0), 0), - NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::LayerNode"), @@ -284,22 +277,6 @@ fn static_nodes() -> Vec { network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "To Element".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "To Group".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)), - ..Default::default() - }, - ..Default::default() - }, DocumentNodeMetadata { persistent_metadata: DocumentNodePersistentMetadata { display_name: "Monitor".to_string(), diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 1f43d22723..a09e7fa5e3 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -50,6 +50,7 @@ pub fn generate_node_substitutions() -> HashMap { let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; + let mut generated_nodes = 0; let mut nodes: HashMap<_, _, _> = node_io_types .iter() .enumerate() @@ -70,11 +71,13 @@ pub fn generate_node_substitutions() -> HashMap { let ident = ident.name.as_ref(); ident == into_node_identifier.name.as_ref() }) { + generated_nodes += 1; into_node_identifier } else if into_node_registry.iter().any(|(ident, _)| { let ident = ident.name.as_ref(); ident == convert_node_identifier.name.as_ref() }) { + generated_nodes += 1; convert_node_identifier } else { identity_node.clone() @@ -98,6 +101,11 @@ pub fn generate_node_substitutions() -> HashMap { }) .collect(); + if generated_nodes == 0 { + log::debug!("skipping {}", id); + continue; + } + let document_node = DocumentNode { inputs: network_inputs, manual_composition: Some(input_type.clone()), @@ -127,6 +135,7 @@ pub fn generate_node_substitutions() -> HashMap { custom.insert(id.clone(), node); } + custom } From 67a8739b7cf653c322cb85e376779312689587f0 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 15 Jun 2025 01:05:52 -0700 Subject: [PATCH 06/11] Code review --- Cargo.toml | 3 +-- .../document/node_graph/document_node_definitions.rs | 4 ++-- .../document_node_definitions/document_node_derive.rs | 2 +- node-graph/preprocessor/Cargo.toml | 5 ++--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 520e46e670..cb81f0d147 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,12 @@ members = [ "node-graph/graphene-cli", "node-graph/interpreted-executor", "node-graph/node-macro", - "node-graph/wgpu-executor", "node-graph/preprocessor", "libraries/dyn-any", "libraries/path-bool", "libraries/bezier-rs", "libraries/math-parser", - "website/other/bezier-rs-demos/wasm", "node-graph/preprocessor", + "website/other/bezier-rs-demos/wasm", ] default-members = [ "editor", diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 3a56183c29..97e4352936 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -1,3 +1,5 @@ +mod document_node_derive; + use super::node_properties::choice::enum_choice; use super::node_properties::{self, ParameterWidgetsInfo}; use super::utility_types::FrontendNodeType; @@ -24,8 +26,6 @@ use graphene_std::vector::VectorDataTable; use graphene_std::*; use std::collections::{HashMap, HashSet, VecDeque}; -mod document_node_derive; - pub struct NodePropertiesContext<'a> { pub persistent_data: &'a PersistentData, pub responses: &'a mut VecDeque, diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index f15edc181a..c97c91a1b1 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -4,7 +4,7 @@ use graph_craft::ProtoNodeIdentifier; use graph_craft::document::*; use graphene_std::registry::*; use graphene_std::*; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; pub(super) fn post_process_nodes(mut custom: Vec) -> Vec { // Remove struct generics diff --git a/node-graph/preprocessor/Cargo.toml b/node-graph/preprocessor/Cargo.toml index 9848f3b3ef..4596e6025c 100644 --- a/node-graph/preprocessor/Cargo.toml +++ b/node-graph/preprocessor/Cargo.toml @@ -16,8 +16,8 @@ dyn-any = { path = "../../libraries/dyn-any", features = [ # Workspace dependencies graphene-std = { workspace = true, features = ["gpu"] } -graph-craft = { workspace = true} -interpreted-executor = { path = "../interpreted-executor" } +graph-craft = { workspace = true } +interpreted-executor = { path = "../interpreted-executor" } log = { workspace = true } futures = { workspace = true } glam = { workspace = true } @@ -27,4 +27,3 @@ base64 = { workspace = true } serde = { workspace = true, optional = true } tokio = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } - From 70931b12f14ac2ca199bc124e512b87dbefdbc69 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 21 Jun 2025 23:32:00 +0200 Subject: [PATCH 07/11] Mark preprocessed networks as generated --- node-graph/graph-craft/src/document.rs | 5 ++++- node-graph/interpreted-executor/src/util.rs | 3 +++ node-graph/preprocessor/src/lib.rs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 1ff4b4e35a..327eb7dcf9 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -683,6 +683,7 @@ pub struct NodeNetwork { #[serde(default)] #[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")] pub scope_injections: FxHashMap, + pub generated: bool, } impl Hash for NodeNetwork { @@ -797,7 +798,9 @@ impl NodeNetwork { pub fn generate_node_paths(&mut self, prefix: &[NodeId]) { for (node_id, node) in &mut self.nodes { let mut new_path = prefix.to_vec(); - new_path.push(*node_id); + if !self.generated { + new_path.push(*node_id); + } if let DocumentNodeImplementation::Network(network) = &mut node.implementation { network.generate_node_paths(new_path.as_slice()); } diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index 5d4e624a49..903f4fc0dc 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -74,9 +74,12 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc HashMap { .collect(); if generated_nodes == 0 { - log::debug!("skipping {}", id); continue; } @@ -127,6 +126,7 @@ pub fn generate_node_substitutions() -> HashMap { }], nodes, scope_injections: Default::default(), + generated: true, }), visible: true, skip_deduplication: false, From b26a17e377c69f5a0aa4dd6db02986cb50c5110d Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 26 Jun 2025 10:54:27 +0200 Subject: [PATCH 08/11] Revert changes to layer node definition --- .../node_graph/document_node_definitions.rs | 22 +++++++++++++++---- .../interpreted-executor/src/node_registry.rs | 1 + node-graph/preprocessor/src/lib.rs | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 97e4352936..60ad2e82fc 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -237,11 +237,25 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0)], + exports: vec![NodeInput::node(NodeId(3), 0)], nodes: [ + // Secondary (left) input type coercion + DocumentNode { + inputs: vec![NodeInput::network(generic!(T), 1)], + implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"), + manual_composition: Some(concrete!(Context)), + ..Default::default() + }, + // Primary (bottom) input type coercion + DocumentNode { + inputs: vec![NodeInput::network(generic!(T), 0)], + implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToGroupNode"), + manual_composition: Some(concrete!(Context)), + ..Default::default() + }, // The monitor node is used to display a thumbnail in the UI DocumentNode { - inputs: vec![NodeInput::network(concrete!(GraphicElement), 1)], + inputs: vec![NodeInput::node(NodeId(0), 0)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), manual_composition: Some(concrete!(Context)), skip_deduplication: true, @@ -250,8 +264,8 @@ fn static_nodes() -> Vec { DocumentNode { manual_composition: Some(generic!(T)), inputs: vec![ - NodeInput::network(concrete!(GraphicGroupTable), 0), - NodeInput::node(NodeId(0), 0), + NodeInput::node(NodeId(1), 0), + NodeInput::node(NodeId(2), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::LayerNode"), diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index dd7930b2bd..311707af85 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -32,6 +32,7 @@ fn node_registry() -> HashMap, to: RasterDataTable), // into_node!(from: RasterDataTable, to: RasterDataTable), into_node!(from: RasterDataTable, to: GraphicElement), + into_node!(from: RasterDataTable, to: GraphicElement), into_node!(from: RasterDataTable, to: GraphicGroupTable), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => RasterDataTable]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]), diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 9fc02a7255..21fc5d8082 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -8,6 +8,9 @@ use graphene_std::*; use std::collections::{HashMap, HashSet}; pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap) { + if network.generated { + return; + } for node in network.nodes.values_mut() { match &mut node.implementation { DocumentNodeImplementation::Network(node_network) => expand_network(node_network, substitutions), From 021b388b5879412cd436f7230febb6ef708aff28 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 26 Jun 2025 11:31:11 +0200 Subject: [PATCH 09/11] Skip generated flag for serialization --- .../node_graph/document_node_definitions.rs | 16 ++++++++++++++++ node-graph/graph-craft/src/document.rs | 1 + node-graph/interpreted-executor/src/util.rs | 1 - 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 60ad2e82fc..ea7fefff21 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -291,6 +291,22 @@ fn static_nodes() -> Vec { network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { node_metadata: [ + DocumentNodeMetadata { + persistent_metadata: DocumentNodePersistentMetadata { + display_name: "To Element".to_string(), + node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)), + ..Default::default() + }, + ..Default::default() + }, + DocumentNodeMetadata { + persistent_metadata: DocumentNodePersistentMetadata { + display_name: "To Group".to_string(), + node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)), + ..Default::default() + }, + ..Default::default() + }, DocumentNodeMetadata { persistent_metadata: DocumentNodePersistentMetadata { display_name: "Monitor".to_string(), diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 327eb7dcf9..85c15343df 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -683,6 +683,7 @@ pub struct NodeNetwork { #[serde(default)] #[serde(serialize_with = "graphene_core::vector::serialize_hashmap", deserialize_with = "graphene_core::vector::deserialize_hashmap")] pub scope_injections: FxHashMap, + #[serde(skip)] pub generated: bool, } diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index 903f4fc0dc..4eb4f4041a 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -74,7 +74,6 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc Date: Thu, 26 Jun 2025 13:13:46 +0200 Subject: [PATCH 10/11] Don't expand for tests --- editor/src/node_graph_executor/runtime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 92d8dbe1cf..dfa360e0bf 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -226,6 +226,7 @@ impl NodeRuntime { } async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { + #[cfg(not(test))] preprocessor::expand_network(&mut graph, &self.substitutions); let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone()); From ce8387aa7dcfdc8bb97e91bf03167fb4b4caff4f Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Thu, 26 Jun 2025 15:51:18 -0700 Subject: [PATCH 11/11] Code review --- editor/Cargo.toml | 7 ++--- .../document_node_derive.rs | 18 +++++++------ editor/src/node_graph_executor/runtime.rs | 2 ++ node-graph/gcore/src/ops.rs | 27 ++++++++++--------- node-graph/graphene-cli/Cargo.toml | 8 ++---- node-graph/graphene-cli/src/main.rs | 1 + .../interpreted-executor/src/node_registry.rs | 20 +++++++------- node-graph/interpreted-executor/src/util.rs | 2 +- node-graph/preprocessor/Cargo.toml | 2 +- node-graph/preprocessor/src/lib.rs | 18 ++++++------- 10 files changed, 52 insertions(+), 53 deletions(-) diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 1ac6a9de11..ffc95a9270 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -13,10 +13,7 @@ license = "Apache-2.0" [features] default = ["wasm"] wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"] -gpu = [ - "interpreted-executor/gpu", - "wgpu-executor", -] +gpu = ["interpreted-executor/gpu", "wgpu-executor"] tauri = ["ron", "decouple-execution"] decouple-execution = [] resvg = ["graphene-std/resvg"] @@ -29,7 +26,7 @@ graphite-proc-macros = { workspace = true } graph-craft = { workspace = true } interpreted-executor = { workspace = true } graphene-std = { workspace = true } -preprocessor = { path = "../node-graph/preprocessor" } +preprocessor = { workspace = true } # Workspace dependencies js-sys = { workspace = true } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index c97c91a1b1..1339621dc4 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -13,16 +13,16 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec document_node: DocumentNode { implementation, .. }, .. } = node_template; + if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation { if let Some((new_name, _suffix)) = name.rsplit_once("<") { *name = Cow::Owned(new_name.to_string()) } }; } + let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); 'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() { - let id = id.clone(); - for node in custom.iter() { let DocumentNodeDefinition { node_template: NodeTemplate { @@ -32,7 +32,7 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec .. } = node; match implementation { - DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == &id => continue 'outer, + DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer, _ => (), } } @@ -44,13 +44,13 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec description, properties, } = metadata; - let Some(implementations) = &node_registry.get(&id) else { continue }; + + let Some(implementations) = &node_registry.get(id) else { continue }; + let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() }); - let mut input_type = &first_node_io.call_argument; - if valid_inputs.len() > 1 { - input_type = &const { generic!(D) }; - } + + let input_type = if valid_inputs.len() > 1 { &const { generic!(D) } } else { &first_node_io.call_argument }; let output_type = &first_node_io.return_value; let inputs = preprocessor::node_inputs(fields, first_node_io); @@ -86,7 +86,9 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec description: Cow::Borrowed(description), properties: *properties, }; + custom.push(node); } + custom } diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index dfa360e0bf..c949e32dd0 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -45,6 +45,7 @@ pub struct NodeRuntime { /// Which node is inspected and which monitor node is used (if any) for the current execution inspect_state: Option, + /// Mapping of the fully-qualified node paths to their preprocessor substitutions. substitutions: HashMap, // TODO: Remove, it doesn't need to be persisted anymore @@ -228,6 +229,7 @@ impl NodeRuntime { async fn update_network(&mut self, mut graph: NodeNetwork) -> Result { #[cfg(not(test))] preprocessor::expand_network(&mut graph, &self.substitutions); + let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone()); // We assume only one output diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 64c9ed16fa..6b968ab242 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -570,12 +570,16 @@ where Box::pin(async move { input.into() }) } } + +/// The [`Convert`] trait allows for conversion between Rust primitive numeric types. +/// Because number casting is lossy, we cannot use the normal [`Into`] trait like we do for other types. pub trait Convert: Sized { - /// Converts this type into the (usually inferred) input type. + /// Converts this type into the (usually inferred) output type. #[must_use] fn convert(self) -> T; } +/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types. macro_rules! impl_convert { ($from:ty,$to:ty) => { impl Convert<$to> for $from { @@ -585,6 +589,8 @@ macro_rules! impl_convert { } }; ($to:ty) => { + impl_convert!(f32, $to); + impl_convert!(f64, $to); impl_convert!(i8, $to); impl_convert!(u8, $to); impl_convert!(u16, $to); @@ -593,14 +599,14 @@ macro_rules! impl_convert { impl_convert!(u32, $to); impl_convert!(i64, $to); impl_convert!(u64, $to); - impl_convert!(isize, $to); - impl_convert!(usize, $to); impl_convert!(i128, $to); impl_convert!(u128, $to); - impl_convert!(f32, $to); - impl_convert!(f64, $to); + impl_convert!(isize, $to); + impl_convert!(usize, $to); }; } +impl_convert!(f32); +impl_convert!(f64); impl_convert!(i8); impl_convert!(u8); impl_convert!(u16); @@ -609,12 +615,10 @@ impl_convert!(i32); impl_convert!(u32); impl_convert!(i64); impl_convert!(u64); -impl_convert!(isize); -impl_convert!(usize); impl_convert!(i128); impl_convert!(u128); -impl_convert!(f32); -impl_convert!(f64); +impl_convert!(isize); +impl_convert!(usize); // Convert pub struct ConvertNode(PhantomData); @@ -628,10 +632,7 @@ impl<_O> Default for ConvertNode<_O> { Self::new() } } -impl<'input, I: 'input, _O: 'input> Node<'input, I> for ConvertNode<_O> -where - I: Convert<_O> + Sync + Send, -{ +impl<'input, I: 'input + Convert<_O> + Sync + Send, _O: 'input> Node<'input, I> for ConvertNode<_O> { type Output = ::dyn_any::DynFuture<'input, _O>; #[inline] diff --git a/node-graph/graphene-cli/Cargo.toml b/node-graph/graphene-cli/Cargo.toml index 125757f26c..84bf96e7cd 100644 --- a/node-graph/graphene-cli/Cargo.toml +++ b/node-graph/graphene-cli/Cargo.toml @@ -12,11 +12,7 @@ wgpu = ["wgpu-executor", "gpu", "graphene-std/wgpu"] wayland = ["graphene-std/wayland"] profiling = ["wgpu-executor/profiling"] passthrough = ["wgpu-executor/passthrough"] -gpu = [ - "interpreted-executor/gpu", - "graphene-std/gpu", - "wgpu-executor", -] +gpu = ["interpreted-executor/gpu", "graphene-std/gpu", "wgpu-executor"] [dependencies] # Local dependencies @@ -24,7 +20,7 @@ graphene-core = { workspace = true } graphene-std = { workspace = true } interpreted-executor = { workspace = true } graph-craft = { workspace = true, features = ["loading"] } -preprocessor = { path = "../preprocessor" } +preprocessor = { workspace = true } # Workspace dependencies log = { workspace = true } diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index d3eb13d0f7..af535b7363 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -183,6 +183,7 @@ fn fix_nodes(network: &mut NodeNetwork) { fn compile_graph(document_string: String, editor_api: Arc) -> Result> { let mut network = load_network(&document_string); fix_nodes(&mut network); + let substitutions = preprocessor::generate_node_substitutions(); preprocessor::expand_network(&mut network, &substitutions); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 311707af85..4f45992c20 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -137,6 +137,8 @@ fn node_registry() -> HashMap HashMap HashMap {{ let x: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ + convert_node!(from: $from, to: f32), + convert_node!(from: $from, to: f64), convert_node!(from: $from, to: i8), convert_node!(from: $from, to: u8), convert_node!(from: $from, to: u16), @@ -248,12 +252,10 @@ mod node_registry_macros { convert_node!(from: $from, to: u32), convert_node!(from: $from, to: i64), convert_node!(from: $from, to: u64), - convert_node!(from: $from, to: isize), - convert_node!(from: $from, to: usize), convert_node!(from: $from, to: i128), convert_node!(from: $from, to: u128), - convert_node!(from: $from, to: f32), - convert_node!(from: $from, to: f64), + convert_node!(from: $from, to: isize), + convert_node!(from: $from, to: usize), ]; x }}; diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index 4eb4f4041a..ab4c744e36 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -78,7 +78,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc expand_network(node_network, substitutions), @@ -64,27 +64,24 @@ pub fn generate_node_substitutions() -> HashMap { 1 => { let input = inputs.iter().next().unwrap(); let input_ty = input.nested_type(); + let into_node_identifier = ProtoNodeIdentifier { name: format!("graphene_core::ops::IntoNode<{}>", input_ty.clone()).into(), }; let convert_node_identifier = ProtoNodeIdentifier { name: format!("graphene_core::ops::ConvertNode<{}>", input_ty.clone()).into(), }; - let proto_node = if into_node_registry.iter().any(|(ident, _)| { - let ident = ident.name.as_ref(); - ident == into_node_identifier.name.as_ref() - }) { + + let proto_node = if into_node_registry.keys().any(|ident: &ProtoNodeIdentifier| ident.name.as_ref() == into_node_identifier.name.as_ref()) { generated_nodes += 1; into_node_identifier - } else if into_node_registry.iter().any(|(ident, _)| { - let ident = ident.name.as_ref(); - ident == convert_node_identifier.name.as_ref() - }) { + } else if into_node_registry.keys().any(|ident| ident.name.as_ref() == convert_node_identifier.name.as_ref()) { generated_nodes += 1; convert_node_identifier } else { identity_node.clone() }; + DocumentNode { inputs: vec![NodeInput::network(input.clone(), i)], // manual_composition: Some(fn_input.clone()), @@ -116,6 +113,7 @@ pub fn generate_node_substitutions() -> HashMap { skip_deduplication: false, ..Default::default() }; + nodes.insert(NodeId(input_count as u64), document_node); let node = DocumentNode {