mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 01:48:12 +08:00
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>
This commit is contained in:
co-authored by
Keavon Chambers
parent
86da69e33f
commit
a40a760f27
@@ -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;
|
||||
@@ -91,7 +93,7 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<Vec<DocumentNodeDefinition>> =
|
||||
/// 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<DocumentNodeDefinition> {
|
||||
let mut custom = vec![
|
||||
let custom = vec![
|
||||
// TODO: Auto-generate this from its proto node macro
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Identity",
|
||||
@@ -241,21 +243,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"),
|
||||
manual_composition: Some(generic!(T)),
|
||||
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(generic!(T)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
manual_composition: Some(generic!(T)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -2114,109 +2116,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
];
|
||||
|
||||
// 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
|
||||
document_node_derive::post_process_nodes(custom)
|
||||
}
|
||||
|
||||
// pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentNodeDefinition {
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
use super::DocumentNodeDefinition;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride};
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::document::*;
|
||||
use graphene_std::registry::*;
|
||||
use graphene_std::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec<DocumentNodeDefinition> {
|
||||
// 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() {
|
||||
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 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);
|
||||
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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -45,6 +45,9 @@ pub struct NodeRuntime {
|
||||
/// Which node is inspected and which monitor node is used (if any) for the current execution
|
||||
inspect_state: Option<InspectState>,
|
||||
|
||||
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
||||
substitutions: HashMap<String, DocumentNode>,
|
||||
|
||||
// TODO: Remove, it doesn't need to be persisted anymore
|
||||
/// The current renders of the thumbnails for layer nodes.
|
||||
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
|
||||
@@ -120,6 +123,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 +226,15 @@ impl NodeRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_network(&mut self, graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
|
||||
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
|
||||
#[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
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user