Cut over to the graphene execution model

This commit is contained in:
Dennis Kobert
2026-07-31 23:19:42 +02:00
parent 1fb6fcb447
commit 81a319430b
71 changed files with 3544 additions and 2378 deletions
@@ -14,7 +14,7 @@ use graphene_std::memo::IORecord;
use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::vector::Vector;
use graphene_std::vector::style::{FillChoice, FillChoiceUI, GradientSpreadMethod, GradientType};
use graphene_std::{Artboard, Color, Context, Graphic};
use graphene_std::{Artboard, Color, CtxSnapshot, Graphic};
use std::any::Any;
use std::sync::Arc;
@@ -167,7 +167,7 @@ macro_rules! generate_layout_downcast {
($introspected_data:expr, $data:expr, [ $($ty:ty),* $(,)? ]) => {
if false { None }
$(
else if let Some(io) = $introspected_data.downcast_ref::<IORecord<Context, $ty>>() {
else if let Some(io) = $introspected_data.downcast_ref::<IORecord<CtxSnapshot, $ty>>() {
Some(io.output.layout_with_breadcrumb($data))
}
)*
@@ -178,7 +178,7 @@ macro_rules! generate_layout_downcast {
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
// `List<NodeId>` is interpreted as a path (e.g. the value produced by `path_of_subgraph`), shown as a
// `List` where each item's NodeId resolves against the prefix made up of the items above it.
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, List<NodeId>>>() {
if let Some(io) = introspected_data.downcast_ref::<IORecord<CtxSnapshot, List<NodeId>>>() {
return Some(table_node_id_path_layout_with_breadcrumb(&io.output, data));
}
generate_layout_downcast!(introspected_data, data, [
@@ -919,7 +919,12 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::import(concrete!(String), 1)],
inputs: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::import(concrete!(String), 1),
NodeInput::scope("graphene_std::runtime::RuntimeNode"),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::SourceId),
],
implementation: DocumentNodeImplementation::ProtoNode(platform_application_io::load_resource::IDENTIFIER),
..Default::default()
},
@@ -994,7 +999,13 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::import(generic!(T), 0), NodeInput::import(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
inputs: vec![
NodeInput::import(generic!(T), 0),
NodeInput::import(concrete!(Footprint), 1),
NodeInput::node(NodeId(1), 0),
NodeInput::scope("graphene_std::runtime::RuntimeNode"),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::SourceId),
],
implementation: DocumentNodeImplementation::ProtoNode(platform_application_io::rasterize::IDENTIFIER),
..Default::default()
},
@@ -29,7 +29,7 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
// fallback when deriving `call_argument` so it reflects the impls actually registered, which will usually be `Context`.
let extended_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY;
let node_registry = NODE_REGISTRY.lock().unwrap();
let empty_implementations: Vec<(NodeConstructor, NodeIOTypes)> = Vec::new();
let empty_implementations: Vec<RegistryEntry> = Vec::new();
let context_type = concrete!(Context);
for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
let identifier = DefinitionIdentifier::ProtoNode(id.clone());
@@ -48,12 +48,12 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
let implementations = node_registry.get(id).unwrap_or(&empty_implementations);
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
let first_node_io = implementations.first().map(|entry| &entry.io).unwrap_or(const { &NodeIOTypes::empty() });
let call_arguments: Vec<&Type> = if !implementations.is_empty() {
implementations.iter().map(|(_, io)| &io.call_argument).collect()
implementations.iter().map(|entry| &entry.io.call_argument).collect()
} else if let Some(impls) = extended_node_registry.get(id) {
impls.keys().map(|io| &io.call_argument).collect()
impls.iter().map(|entry| &entry.io.call_argument).collect()
} else {
Vec::new()
};
@@ -2360,7 +2360,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
return Vec::new();
};
let mut input_types = implementations.keys().filter_map(|item| item.inputs.get(input_index)).collect::<Vec<_>>();
let mut input_types = implementations.iter().filter_map(|entry| entry.io.inputs.get(input_index)).collect::<Vec<_>>();
input_types.sort_by_key(|ty| ty.type_name());
let input_type = input_types.first().cloned();
@@ -95,6 +95,34 @@ impl NodeNetworkInterface {
}
}
}
/// Append the hidden runtime and source-id inputs to async-source protonodes saved before their injection.
/// Runs after the identifier replacement pass, so it matches only current identifier spellings.
pub fn migrate_async_source_inputs(&mut self) {
const PRE_INJECTION_ARITIES: [(&str, usize); 5] = [
("graphene_std::platform_application_io::GetRequestNode", 4),
("graphene_std::platform_application_io::PostRequestNode", 5),
("graphene_std::platform_application_io::LoadResourceNode", 2),
("graphene_std::platform_application_io::RasterizeNode", 3),
("graphene_std::platform_application_io::ResourceNode", 2),
];
fix_network(self.document_network_mut());
fn fix_network(network: &mut NodeNetwork) {
for node in network.nodes.values_mut() {
if let Some(network) = node.implementation.get_network_mut() {
fix_network(network);
}
if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation
&& let Some(base) = protonode.as_str().split('<').next()
&& let Some((_, arity)) = PRE_INJECTION_ARITIES.iter().find(|(identifier, _)| *identifier == base)
&& node.inputs.len() == *arity
{
node.inputs.push(NodeInput::scope("graphene_std::runtime::RuntimeNode"));
node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::SourceId));
}
}
}
}
}
// Public immutable getters for the network interface
@@ -253,8 +253,9 @@ impl NodeNetworkInterface {
};
let number_of_inputs = self.number_of_inputs(node_id, network_path);
implementations
.keys()
.filter_map(|node_io| {
.iter()
.filter_map(|entry| {
let node_io = &entry.io;
// Check if this NodeIOTypes implementation is valid for the other inputs
let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| {
let input_type = self.input_type_not_invalid(&InputConnector::node(*node_id, iterator_index), network_path);
@@ -293,8 +294,9 @@ impl NodeNetworkInterface {
let valid_output_types = self.valid_output_types(&OutputConnector::node(*node_id, 0), network_path);
implementations
.keys()
.filter_map(|node_io| {
.iter()
.filter_map(|entry| {
let node_io = &entry.io;
if !valid_output_types.iter().any(|output_type| output_type.nested_type() == node_io.return_value.nested_type()) {
return None;
}
@@ -323,7 +325,7 @@ impl NodeNetworkInterface {
log::error!("Protonode {render_node:?} not found in registry");
return Vec::new();
};
implementations.keys().map(|types| types.inputs[1].clone()).collect()
implementations.iter().map(|entry| entry.io.inputs[1].clone()).collect()
}
}
}
@@ -1148,6 +1148,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
}
}
document.network_interface.migrate_async_source_inputs();
// The "Brush" wrapper network was replaced with the `brush` proto node directly. Convert old `Network("Brush")` instances to the proto node, forwarding all 3 inputs (Background, Trace, Cache) one-to-one.
// This must run as a pre-pass before the recursive iteration below: replacing the outer Brush's network impl orphans its child paths, and the recursive iteration would log errors for those stale paths.
let brush_layers: Vec<(NodeId, Vec<NodeId>)> = document