Skip to content

Commit a40a760

Browse files
TrueDoctorKeavon
andauthored
Add automatic type conversion and the node graph preprocessor (#2478)
* Prototype document network level into node insertion * Implement Convert trait / node for places we can't use Into * Add isize/usize and i128/u128 implementations for Convert trait * Factor out substitutions into preprocessor crate * Simplify layer node further * Code review * Mark preprocessed networks as generated * Revert changes to layer node definition * Skip generated flag for serialization * Don't expand for tests * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 86da69e commit a40a760

File tree

15 files changed

+484
-128
lines changed

15 files changed

+484
-128
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ members = [
1111
"node-graph/graphene-cli",
1212
"node-graph/interpreted-executor",
1313
"node-graph/node-macro",
14-
"node-graph/wgpu-executor",
14+
"node-graph/preprocessor",
1515
"libraries/dyn-any",
1616
"libraries/path-bool",
1717
"libraries/bezier-rs",
@@ -34,6 +34,7 @@ resolver = "2"
3434
# Local dependencies
3535
bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any", "serde"] }
3636
dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam", "reqwest", "log-bad-types", "rc"] }
37+
preprocessor = { path = "node-graph/preprocessor"}
3738
math-parser = { path = "libraries/math-parser" }
3839
path-bool = { path = "libraries/path-bool" }
3940
graphene-application-io = { path = "node-graph/gapplication-io" }

editor/Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ license = "Apache-2.0"
1313
[features]
1414
default = ["wasm"]
1515
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
16-
gpu = [
17-
"interpreted-executor/gpu",
18-
"wgpu-executor",
19-
]
16+
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
2017
tauri = ["ron", "decouple-execution"]
2118
decouple-execution = []
2219
resvg = ["graphene-std/resvg"]
@@ -29,6 +26,7 @@ graphite-proc-macros = { workspace = true }
2926
graph-craft = { workspace = true }
3027
interpreted-executor = { workspace = true }
3128
graphene-std = { workspace = true }
29+
preprocessor = { workspace = true }
3230

3331
# Workspace dependencies
3432
js-sys = { workspace = true }

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 7 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
mod document_node_derive;
2+
13
use super::node_properties::choice::enum_choice;
24
use super::node_properties::{self, ParameterWidgetsInfo};
35
use super::utility_types::FrontendNodeType;
@@ -91,7 +93,7 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<Vec<DocumentNodeDefinition>> =
9193
/// Defines the "signature" or "header file"-like metadata for the document nodes, but not the implementation (which is defined in the node registry).
9294
/// The [`DocumentNode`] is the instance while these [`DocumentNodeDefinition`]s are the "classes" or "blueprints" from which the instances are built.
9395
fn static_nodes() -> Vec<DocumentNodeDefinition> {
94-
let mut custom = vec![
96+
let custom = vec![
9597
// TODO: Auto-generate this from its proto node macro
9698
DocumentNodeDefinition {
9799
identifier: "Identity",
@@ -241,21 +243,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
241243
DocumentNode {
242244
inputs: vec![NodeInput::network(generic!(T), 1)],
243245
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"),
244-
manual_composition: Some(generic!(T)),
246+
manual_composition: Some(concrete!(Context)),
245247
..Default::default()
246248
},
247249
// Primary (bottom) input type coercion
248250
DocumentNode {
249251
inputs: vec![NodeInput::network(generic!(T), 0)],
250252
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToGroupNode"),
251-
manual_composition: Some(generic!(T)),
253+
manual_composition: Some(concrete!(Context)),
252254
..Default::default()
253255
},
254256
// The monitor node is used to display a thumbnail in the UI
255257
DocumentNode {
256258
inputs: vec![NodeInput::node(NodeId(0), 0)],
257259
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
258-
manual_composition: Some(generic!(T)),
260+
manual_composition: Some(concrete!(Context)),
259261
skip_deduplication: true,
260262
..Default::default()
261263
},
@@ -2114,109 +2116,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
21142116
},
21152117
];
21162118

2117-
// Remove struct generics
2118-
for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() {
2119-
let NodeTemplate {
2120-
document_node: DocumentNode { implementation, .. },
2121-
..
2122-
} = node_template;
2123-
if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation {
2124-
if let Some((new_name, _suffix)) = name.rsplit_once("<") {
2125-
*name = Cow::Owned(new_name.to_string())
2126-
}
2127-
};
2128-
}
2129-
let node_registry = graphene_std::registry::NODE_REGISTRY.lock().unwrap();
2130-
'outer: for (id, metadata) in graphene_std::registry::NODE_METADATA.lock().unwrap().iter() {
2131-
use graphene_std::registry::*;
2132-
let id = id.clone();
2133-
2134-
for node in custom.iter() {
2135-
let DocumentNodeDefinition {
2136-
node_template: NodeTemplate {
2137-
document_node: DocumentNode { implementation, .. },
2138-
..
2139-
},
2140-
..
2141-
} = node;
2142-
match implementation {
2143-
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == &id => continue 'outer,
2144-
_ => (),
2145-
}
2146-
}
2147-
2148-
let NodeMetadata {
2149-
display_name,
2150-
category,
2151-
fields,
2152-
description,
2153-
properties,
2154-
} = metadata;
2155-
let Some(implementations) = &node_registry.get(&id) else { continue };
2156-
let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
2157-
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
2158-
let mut input_type = &first_node_io.call_argument;
2159-
if valid_inputs.len() > 1 {
2160-
input_type = &const { generic!(D) };
2161-
}
2162-
let output_type = &first_node_io.return_value;
2163-
2164-
let inputs = fields
2165-
.iter()
2166-
.zip(first_node_io.inputs.iter())
2167-
.enumerate()
2168-
.map(|(index, (field, node_io_ty))| {
2169-
let ty = field.default_type.as_ref().unwrap_or(node_io_ty);
2170-
let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed };
2171-
2172-
match field.value_source {
2173-
RegistryValueSource::None => {}
2174-
RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed),
2175-
RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)),
2176-
};
2177-
2178-
if let Some(type_default) = TaggedValue::from_type(ty) {
2179-
return NodeInput::value(type_default, exposed);
2180-
}
2181-
NodeInput::value(TaggedValue::None, true)
2182-
})
2183-
.collect();
2184-
2185-
let node = DocumentNodeDefinition {
2186-
identifier: display_name,
2187-
node_template: NodeTemplate {
2188-
document_node: DocumentNode {
2189-
inputs,
2190-
manual_composition: Some(input_type.clone()),
2191-
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()),
2192-
visible: true,
2193-
skip_deduplication: false,
2194-
..Default::default()
2195-
},
2196-
persistent_node_metadata: DocumentNodePersistentMetadata {
2197-
// TODO: Store information for input overrides in the node macro
2198-
input_properties: fields
2199-
.iter()
2200-
.map(|f| match f.widget_override {
2201-
RegistryWidgetOverride::None => (f.name, f.description).into(),
2202-
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden),
2203-
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())),
2204-
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
2205-
})
2206-
.collect(),
2207-
output_names: vec![output_type.to_string()],
2208-
has_primary_output: true,
2209-
locked: false,
2210-
..Default::default()
2211-
},
2212-
},
2213-
category: category.unwrap_or("UNCATEGORIZED"),
2214-
description: Cow::Borrowed(description),
2215-
properties: *properties,
2216-
};
2217-
custom.push(node);
2218-
}
2219-
custom
2119+
document_node_derive::post_process_nodes(custom)
22202120
}
22212121

22222122
// pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentNodeDefinition {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
use super::DocumentNodeDefinition;
2+
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride};
3+
use graph_craft::ProtoNodeIdentifier;
4+
use graph_craft::document::*;
5+
use graphene_std::registry::*;
6+
use graphene_std::*;
7+
use std::collections::HashSet;
8+
9+
pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec<DocumentNodeDefinition> {
10+
// Remove struct generics
11+
for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() {
12+
let NodeTemplate {
13+
document_node: DocumentNode { implementation, .. },
14+
..
15+
} = node_template;
16+
17+
if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation {
18+
if let Some((new_name, _suffix)) = name.rsplit_once("<") {
19+
*name = Cow::Owned(new_name.to_string())
20+
}
21+
};
22+
}
23+
24+
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
25+
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
26+
for node in custom.iter() {
27+
let DocumentNodeDefinition {
28+
node_template: NodeTemplate {
29+
document_node: DocumentNode { implementation, .. },
30+
..
31+
},
32+
..
33+
} = node;
34+
match implementation {
35+
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer,
36+
_ => (),
37+
}
38+
}
39+
40+
let NodeMetadata {
41+
display_name,
42+
category,
43+
fields,
44+
description,
45+
properties,
46+
} = metadata;
47+
48+
let Some(implementations) = &node_registry.get(id) else { continue };
49+
50+
let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
51+
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
52+
53+
let input_type = if valid_inputs.len() > 1 { &const { generic!(D) } } else { &first_node_io.call_argument };
54+
let output_type = &first_node_io.return_value;
55+
56+
let inputs = preprocessor::node_inputs(fields, first_node_io);
57+
let node = DocumentNodeDefinition {
58+
identifier: display_name,
59+
node_template: NodeTemplate {
60+
document_node: DocumentNode {
61+
inputs,
62+
manual_composition: Some(input_type.clone()),
63+
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()),
64+
visible: true,
65+
skip_deduplication: false,
66+
..Default::default()
67+
},
68+
persistent_node_metadata: DocumentNodePersistentMetadata {
69+
// TODO: Store information for input overrides in the node macro
70+
input_properties: fields
71+
.iter()
72+
.map(|f| match f.widget_override {
73+
RegistryWidgetOverride::None => (f.name, f.description).into(),
74+
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden),
75+
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())),
76+
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
77+
})
78+
.collect(),
79+
output_names: vec![output_type.to_string()],
80+
has_primary_output: true,
81+
locked: false,
82+
..Default::default()
83+
},
84+
},
85+
category: category.unwrap_or("UNCATEGORIZED"),
86+
description: Cow::Borrowed(description),
87+
properties: *properties,
88+
};
89+
90+
custom.push(node);
91+
}
92+
93+
custom
94+
}

editor/src/messages/portfolio/document/utility_types/network_interface.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6515,6 +6515,12 @@ pub struct NodePersistentMetadata {
65156515
position: NodePosition,
65166516
}
65176517

6518+
impl NodePersistentMetadata {
6519+
pub fn new(position: NodePosition) -> Self {
6520+
Self { position }
6521+
}
6522+
}
6523+
65186524
/// A layer can either be position as Absolute or in a Stack
65196525
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
65206526
pub enum LayerPosition {

editor/src/node_graph_executor/runtime.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ pub struct NodeRuntime {
4545
/// Which node is inspected and which monitor node is used (if any) for the current execution
4646
inspect_state: Option<InspectState>,
4747

48+
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
49+
substitutions: HashMap<String, DocumentNode>,
50+
4851
// TODO: Remove, it doesn't need to be persisted anymore
4952
/// The current renders of the thumbnails for layer nodes.
5053
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
@@ -120,6 +123,8 @@ impl NodeRuntime {
120123
node_graph_errors: Vec::new(),
121124
monitor_nodes: Vec::new(),
122125

126+
substitutions: preprocessor::generate_node_substitutions(),
127+
123128
thumbnail_renders: Default::default(),
124129
vector_modify: Default::default(),
125130
inspect_state: None,
@@ -221,11 +226,15 @@ impl NodeRuntime {
221226
}
222227
}
223228

224-
async fn update_network(&mut self, graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
229+
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
230+
#[cfg(not(test))]
231+
preprocessor::expand_network(&mut graph, &self.substitutions);
232+
225233
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
226234

227235
// We assume only one output
228236
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
237+
229238
let c = Compiler {};
230239
let proto_network = match c.compile_single(scoped_network) {
231240
Ok(network) => network,

0 commit comments

Comments
 (0)