mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00: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:
parent
86da69e33f
commit
a40a760f27
15 changed files with 484 additions and 128 deletions
29
node-graph/preprocessor/Cargo.toml
Normal file
29
node-graph/preprocessor/Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
|||
[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 = { workspace = true }
|
||||
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 }
|
164
node-graph/preprocessor/src/lib.rs
Normal file
164
node-graph/preprocessor/src/lib.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
use graph_craft::document::value::*;
|
||||
use graph_craft::document::*;
|
||||
use graph_craft::proto::RegistryValueSource;
|
||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::registry::*;
|
||||
use graphene_std::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String, DocumentNode>) {
|
||||
if network.generated {
|
||||
return;
|
||||
}
|
||||
|
||||
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<String, DocumentNode> {
|
||||
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 generated_nodes = 0;
|
||||
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.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.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()),
|
||||
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();
|
||||
|
||||
if generated_nodes == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
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(),
|
||||
generated: true,
|
||||
}),
|
||||
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<NodeInput> {
|
||||
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()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue