Replace Footprint/() call arguments with dynamically-bound Contexts (#2232)

* Implement experimental Context struct and traits

* Add Ctx super trait

* Checkpoint

* Return Any instead of DynAny

* Fix send implementation for inputs with lifetimes

* Port more nodes

* Uncomment nodes

* Port more nodes

* Port vector nodes

* Partial progress (the stuff I'm more sure about)

* Partial progress (the stuff that's not compiling and I'm not sure about)

* Fix more errors

* First pass of fixing errors introduced by rebase

* Port wasm application io

* Fix brush node types

* Add type annotation

* Fix warnings and wasm compilation

* Change types for Document Node definitions

* Improve debugging for footprint not found errors

* Forward context in append artboard node

* Fix thumbnails

* Fix loading most demo artwork

* Wrap output type of all nodes in future

* Encode futures as part of the type

* Fix document node definitions for future types

* Remove Clippy warnings

* Fix more things

* Fix opening demo art with manual composition upgrading

* Set correct type for manual composition

* Fix brush

* Fix tests

* Update docs for deps

* Fix up some node signature issues

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
Dennis Kobert
2025-03-01 23:54:52 +01:00
committed by Keavon Chambers
parent 0c1e96b9c6
commit 4ff2bdb04f
43 changed files with 1338 additions and 1545 deletions

View File

@@ -293,9 +293,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
inputs: vec![
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(ArtboardGroup))), 0),
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroup))), 0),
NodeInput::node(NodeId(1), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
],
@@ -390,7 +390,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
identifier: "Load Image",
category: "Raster: Generator",
category: "Network",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
@@ -398,20 +398,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [
DocumentNode {
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
..Default::default()
},
]
@@ -484,7 +484,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
@@ -554,7 +554,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
@@ -638,20 +638,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")),
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
..Default::default()
},
]
@@ -724,7 +724,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Raster",
node_template: NodeTemplate {
document_node: DocumentNode {
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode")),
inputs: vec![
NodeInput::value(TaggedValue::Bool(false), false),
@@ -1000,7 +1000,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>), 2),
NodeInput::network(concrete!(BrushCache), 3),
],
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")),
..Default::default()
}]
@@ -1062,7 +1062,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -1081,7 +1081,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true)],
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -1103,7 +1103,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: vec![DocumentNode {
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::CullNode")),
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
..Default::default()
}]
.into_iter()
@@ -1158,12 +1158,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::node(NodeId(0), 0)],
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UniformNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
@@ -1242,7 +1242,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
@@ -1321,7 +1321,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
@@ -1406,7 +1406,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
@@ -1515,7 +1515,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
@@ -1595,7 +1595,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
@@ -1665,13 +1665,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
..Default::default()
@@ -1736,7 +1736,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
inputs: vec![
NodeInput::network(concrete!(ShaderInputFrame), 0),
NodeInput::network(concrete!(Arc<wgpu_executor::Surface>), 1),
@@ -1947,35 +1947,36 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files
DocumentNodeDefinition {
identifier: "Curves",
category: "Raster: Adjustment",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
NodeInput::value(TaggedValue::Curve(Default::default()), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec!["Image".into(), "Curve".into()],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
(*IMAGINATE_NODE).clone(),
// TODO: Fix this, it's currently broken
// DocumentNodeDefinition {
// identifier: "Curves",
// category: "Raster: Adjustment",
// node_template: NodeTemplate {
// document_node: DocumentNode {
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
// inputs: vec![
// NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::default()), true),
// NodeInput::value(TaggedValue::Curve(Default::default()), false),
// ],
// ..Default::default()
// },
// persistent_node_metadata: DocumentNodePersistentMetadata {
// input_properties: vec!["Image".into(), "Curve".into()],
// output_names: vec!["Image".to_string()],
// ..Default::default()
// },
// },
// description: Cow::Borrowed("TODO"),
// properties: None,
// },
// (*IMAGINATE_NODE).clone(),
DocumentNodeDefinition {
identifier: "Line",
category: "Vector: Shape",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::LineNode"),
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(0., -50.)), false),
@@ -2089,7 +2090,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"),
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
inputs: vec![
NodeInput::scope("editor-api"),
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
@@ -2190,7 +2191,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(DVec2), 4),
NodeInput::network(concrete!(DVec2), 5),
],
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::TransformNode")),
..Default::default()
},
@@ -2350,7 +2351,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::U32(0), false),
],
manual_composition: Some(concrete!(Footprint)),
manual_composition: Some(concrete!(Context)),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@@ -2699,7 +2700,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.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!(()) } else { field.exposed };
let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed };
match field.value_source {
RegistryValueSource::None => {}
@@ -2753,7 +2754,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentNodeDefinition {
identifier: "Imaginate",
category: "Raster: Generator",
category: "Raster",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
@@ -2762,7 +2763,7 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentN
DocumentNode {
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
manual_composition: Some(concrete!(())),
manual_composition: Some(concrete!(Context)),
skip_deduplication: true,
..Default::default()
},

View File

@@ -260,7 +260,7 @@ pub(crate) fn property_from_type(
}
Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(),
Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, context),
Type::Future(_) => vec![TextLabel::new("Future type (not supported)").widget_holder()].into(),
Type::Future(out) => return property_from_type(node_id, index, out, number_options, context),
};
extra_widgets.push(widgets);

View File

@@ -575,6 +575,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
};
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
log::error!("could not get reference in deserialize_document");
continue;
};
@@ -584,6 +585,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
};
let inputs_count = node.inputs.len();
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
document.network_interface.set_manual_compostion(node_id, &[], graph_craft::concrete!(graphene_std::Context).into());
}
// Upgrade Fill nodes to the format change in #1778
if reference == "Fill" && inputs_count == 8 {
let node_definition = resolve_document_node_type(reference).unwrap();

View File

@@ -15,6 +15,7 @@ use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_core::text::FontCache;
use graphene_core::transform::Footprint;
use graphene_core::vector::style::ViewMode;
use graphene_core::Context;
use graphene_std::renderer::{format_transform_matrix, RenderMetadata};
use graphene_std::vector::{VectorData, VectorDataTable};
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
@@ -286,17 +287,17 @@ impl NodeRuntime {
continue;
};
if let Some(io) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElement>>() {
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_core::GraphicElement>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), graphene_core::GraphicElement>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::Artboard>>() {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_core::Artboard>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), graphene_core::Artboard>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
}
// Insert the vector modify if we are dealing with vector data
else if let Some(record) = introspected_data.downcast_ref::<IORecord<Footprint, VectorDataTable>>() {
else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, VectorDataTable>>() {
self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone());
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<(), VectorDataTable>>() {
self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone());