diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 18c54c412..6b4ee383f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ }, "ghcr.io/devcontainers/features/node:1": {} }, - "onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.99", + "onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f wasm-bindgen-cli@0.2.100", "customizations": { "vscode": { // NOTE: Keep this in sync with `.vscode/extensions.json` diff --git a/Cargo.lock b/Cargo.lock index e04ac9aea..7f3657621 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,6 +1397,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -2400,6 +2406,7 @@ dependencies = [ "js-sys", "log", "num-traits", + "pretty_assertions", "reqwest 0.12.12", "rustc-hash 2.1.0", "serde", @@ -3505,9 +3512,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -4907,6 +4914,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -7502,20 +7519,21 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", @@ -7527,9 +7545,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -7540,9 +7558,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7550,9 +7568,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -7563,9 +7581,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" @@ -7691,9 +7712,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -8824,6 +8845,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index c949a405b..fe7e330db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,10 +63,10 @@ spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git" } wgpu-types = "23" wgpu = "23" once_cell = "1.13" # Remove when `core::cell::LazyCell` () is stabilized in Rust 1.80 and we bump our MSRV -wasm-bindgen = "=0.2.99" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things. +wasm-bindgen = "=0.2.100" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things. wasm-bindgen-futures = "0.4" -js-sys = "=0.3.76" -web-sys = "=0.3.76" +js-sys = "=0.3.77" +web-sys = "=0.3.77" winit = "0.29" url = "2.5" tokio = { version = "1.29", features = ["fs", "io-std"] } @@ -80,6 +80,7 @@ base64 = "0.22" image = { version = "0.25", default-features = false, features = ["png"] } rustybuzz = "0.20" spirv = "0.3" +pretty_assertions = "1.4.1" fern = { version = "0.7", features = ["colored"] } num_enum = "0.7" num-derive = "0.4" diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 6e850b976..848807322 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -293,9 +293,9 @@ fn static_nodes() -> Vec { ..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 { 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 { 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 { ..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 { ..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 { 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 { 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 { NodeInput::network(concrete!(Vec), 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 { 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 { 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 { nodes: vec![DocumentNode { inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 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 { }, 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 { ..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 { ..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 { ..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 { ..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 { ..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 { 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 { ..Default::default() }, DocumentNode { - manual_composition: Some(concrete!(Footprint)), + manual_composition: Some(concrete!(Context)), inputs: vec![ NodeInput::network(concrete!(ShaderInputFrame), 0), NodeInput::network(concrete!(Arc), 1), @@ -1947,35 +1947,36 @@ fn static_nodes() -> Vec { // 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 { 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 { 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 { 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 { .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 { pub static IMAGINATE_NODE: Lazy = 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 = Lazy::new(|| DocumentN DocumentNode { inputs: vec![NodeInput::network(concrete!(ImageFrameTable), 0)], implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), - manual_composition: Some(concrete!(())), + manual_composition: Some(concrete!(Context)), skip_deduplication: true, ..Default::default() }, diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 96c853e38..ee3c56f72 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -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); diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 2e87897f2..8828dbc91 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -575,6 +575,7 @@ impl MessageHandler> 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> 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(); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 28ab819b8..00269dd47 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -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::>() { + if let Some(io) = introspected_data.downcast_ref::>() { 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::>() { 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::>() { + } else if let Some(io) = introspected_data.downcast_ref::>() { 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::>() { 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::>() { + else if let Some(record) = introspected_data.downcast_ref::>() { self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone()); } else if let Some(record) = introspected_data.downcast_ref::>() { self.vector_modify.insert(parent_network_node_id, record.output.one_item().clone()); diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index 41e0c6050..2da3cd93b 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -58,6 +58,13 @@ pub trait DynAny<'a>: 'a { fn type_id(&self) -> TypeId; #[cfg(feature = "log-bad-types")] fn type_name(&self) -> &'static str; + fn reborrow_box<'short>(self: Box) -> Box + 'short> + where + 'a: 'short; + fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + Send + Sync + 'short) + where + 'a: 'short, + Self: Send + Sync; } impl<'a, T: StaticType + 'a> DynAny<'a> for T { @@ -68,6 +75,20 @@ impl<'a, T: StaticType + 'a> DynAny<'a> for T { fn type_name(&self) -> &'static str { core::any::type_name::() } + fn reborrow_box<'short>(self: Box) -> Box + 'short> + where + 'a: 'short, + { + self + } + + fn reborrow_ref<'short>(&'a self) -> &'short (dyn DynAny<'short> + Send + Sync + 'short) + where + 'a: 'short, + Self: Send + Sync, + { + self + } } pub fn downcast_ref<'a, V: StaticType + 'a>(i: &'a dyn DynAny<'a>) -> Option<&'a V> { if i.type_id() == core::any::TypeId::of::<::Static>() { diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs new file mode 100644 index 000000000..3e21d722a --- /dev/null +++ b/node-graph/gcore/src/context.rs @@ -0,0 +1,307 @@ +use crate::transform::Footprint; + +use core::{any::Any, borrow::Borrow, panic::Location}; +use std::sync::Arc; + +pub trait Ctx: Clone + Send {} + +pub trait ExtractFootprint { + #[track_caller] + fn try_footprint(&self) -> Option<&Footprint>; + #[track_caller] + fn footprint(&self) -> &Footprint { + self.try_footprint().unwrap_or_else(|| { + log::error!("Context did not have a footprint, called from: {}", Location::caller()); + &const { Footprint::empty() } + }) + } +} + +pub trait ExtractTime { + fn try_time(&self) -> Option; +} + +pub trait ExtractIndex { + fn try_index(&self) -> Option; +} + +// Consider returning a slice or something like that +pub trait ExtractVarArgs { + // Call this lifetime 'b so it is less likely to coflict when auto generating the function signature for implementation + fn vararg(&self, index: usize) -> Result, VarArgsResult>; + fn varargs_len(&self) -> Result; +} +// Consider returning a slice or something like that +pub trait CloneVarArgs: ExtractVarArgs { + // fn box_clone(&self) -> Vec; + fn arc_clone(&self) -> Option>; +} + +pub trait ExtractAll: ExtractFootprint + ExtractIndex + ExtractTime + ExtractVarArgs {} + +impl ExtractAll for T {} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum VarArgsResult { + IndexOutOfBounds, + NoVarArgs, +} +impl Ctx for Option {} +impl Ctx for &T {} +impl Ctx for () {} +impl Ctx for Footprint {} +impl ExtractFootprint for () { + fn try_footprint(&self) -> Option<&Footprint> { + log::error!("tried to extract footprint form (), {}", Location::caller()); + None + } +} + +impl ExtractFootprint for &T { + fn try_footprint(&self) -> Option<&Footprint> { + (*self).try_footprint() + } +} + +impl ExtractFootprint for Option { + fn try_footprint(&self) -> Option<&Footprint> { + self.as_ref().and_then(|x| x.try_footprint()) + } + #[track_caller] + fn footprint(&self) -> &Footprint { + self.try_footprint().unwrap_or_else(|| { + log::warn!("trying to extract footprint from context None {} ", Location::caller()); + &const { Footprint::empty() } + }) + } +} +impl ExtractTime for Option { + fn try_time(&self) -> Option { + self.as_ref().and_then(|x| x.try_time()) + } +} +impl ExtractIndex for Option { + fn try_index(&self) -> Option { + self.as_ref().and_then(|x| x.try_index()) + } +} +impl ExtractVarArgs for Option { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + let Some(ref inner) = self else { return Err(VarArgsResult::NoVarArgs) }; + inner.vararg(index) + } + + fn varargs_len(&self) -> Result { + let Some(ref inner) = self else { return Err(VarArgsResult::NoVarArgs) }; + inner.varargs_len() + } +} +impl ExtractFootprint for Arc { + fn try_footprint(&self) -> Option<&Footprint> { + (**self).try_footprint() + } +} +impl ExtractTime for Arc { + fn try_time(&self) -> Option { + (**self).try_time() + } +} +impl ExtractIndex for Arc { + fn try_index(&self) -> Option { + (**self).try_index() + } +} +impl ExtractVarArgs for Arc { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + (**self).vararg(index) + } + + fn varargs_len(&self) -> Result { + (**self).varargs_len() + } +} +impl CloneVarArgs for Option { + fn arc_clone(&self) -> Option> { + self.as_ref().and_then(CloneVarArgs::arc_clone) + } +} + +impl ExtractVarArgs for &T { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + (*self).vararg(index) + } + + fn varargs_len(&self) -> Result { + (*self).varargs_len() + } +} +impl CloneVarArgs for Arc { + fn arc_clone(&self) -> Option> { + (**self).arc_clone() + } +} + +impl Ctx for ContextImpl<'_> {} +impl Ctx for Arc {} + +impl ExtractFootprint for ContextImpl<'_> { + fn try_footprint(&self) -> Option<&Footprint> { + self.footprint + } +} +impl ExtractTime for ContextImpl<'_> { + fn try_time(&self) -> Option { + self.time + } +} +impl ExtractIndex for ContextImpl<'_> { + fn try_index(&self) -> Option { + self.index + } +} +impl ExtractVarArgs for ContextImpl<'_> { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; + inner.get(index).ok_or(VarArgsResult::IndexOutOfBounds).copied() + } + + fn varargs_len(&self) -> Result { + let Some(inner) = self.varargs else { return Err(VarArgsResult::NoVarArgs) }; + Ok(inner.len()) + } +} + +impl ExtractFootprint for OwnedContextImpl { + fn try_footprint(&self) -> Option<&Footprint> { + self.footprint.as_ref() + } +} +impl ExtractTime for OwnedContextImpl { + fn try_time(&self) -> Option { + self.time + } +} +impl ExtractIndex for OwnedContextImpl { + fn try_index(&self) -> Option { + self.index + } +} +impl ExtractVarArgs for OwnedContextImpl { + fn vararg(&self, index: usize) -> Result, VarArgsResult> { + let Some(ref inner) = self.varargs else { + let Some(ref parent) = self.parent else { + return Err(VarArgsResult::NoVarArgs); + }; + return parent.vararg(index); + }; + inner.get(index).map(|x| x.as_ref()).ok_or(VarArgsResult::IndexOutOfBounds) + } + + fn varargs_len(&self) -> Result { + let Some(ref inner) = self.varargs else { + let Some(ref parent) = self.parent else { + return Err(VarArgsResult::NoVarArgs); + }; + return parent.varargs_len(); + }; + Ok(inner.len()) + } +} + +impl CloneVarArgs for Arc { + fn arc_clone(&self) -> Option> { + Some(self.clone()) + } +} + +pub type Context<'a> = Option>; +type DynRef<'a> = &'a (dyn Any + Send + Sync); +type DynBox = Box; + +#[derive(dyn_any::DynAny)] +pub struct OwnedContextImpl { + footprint: Option, + varargs: Option>, + parent: Option>, + // This could be converted into a single enum to save extra bytes + index: Option, + time: Option, +} + +impl Default for OwnedContextImpl { + #[track_caller] + fn default() -> Self { + Self::empty() + } +} + +impl core::hash::Hash for OwnedContextImpl { + fn hash(&self, state: &mut H) { + self.footprint.hash(state); + self.index.hash(state); + self.time.map(|x| x.to_bits()).hash(state); + self.parent.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state); + self.varargs.as_ref().map(|x| Arc::as_ptr(x).addr()).hash(state); + } +} + +impl OwnedContextImpl { + #[track_caller] + pub fn from(value: T) -> Self { + let footprint = value.try_footprint().copied(); + let index = value.try_index(); + let time = value.try_time(); + let parent = value.arc_clone(); + OwnedContextImpl { + footprint, + varargs: None, + parent, + index, + time, + } + } + pub const fn empty() -> Self { + OwnedContextImpl { + footprint: None, + varargs: None, + parent: None, + index: None, + time: None, + } + } +} + +impl OwnedContextImpl { + pub fn set_footprint(&mut self, footprint: Footprint) { + self.footprint = Some(footprint); + } + pub fn with_footprint(mut self, footprint: Footprint) -> Self { + self.footprint = Some(footprint); + self + } + pub fn into_context(self) -> Option> { + Some(Arc::new(self)) + } +} + +#[derive(Default, Clone, Copy, dyn_any::DynAny)] +pub struct ContextImpl<'a> { + pub(crate) footprint: Option<&'a crate::transform::Footprint>, + varargs: Option<&'a [DynRef<'a>]>, + // This could be converted into a single enum to save extra bytes + index: Option, + time: Option, +} + +impl<'a> ContextImpl<'a> { + pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f> + where + 'a: 'f, + { + ContextImpl { + footprint: Some(new_footprint), + varargs: varargs.map(|x| x.borrow()), + ..*self + } + } +} diff --git a/node-graph/gcore/src/generic.rs b/node-graph/gcore/src/generic.rs index 49f3123f9..bd82d3f08 100644 --- a/node-graph/gcore/src/generic.rs +++ b/node-graph/gcore/src/generic.rs @@ -1,6 +1,7 @@ use core::marker::PhantomData; use crate::Node; +#[derive(Clone)] pub struct FnNode O, I, O>(T, PhantomData<(I, O)>); impl<'i, T: Fn(I) -> O + 'i, O: 'i, I: 'i> Node<'i, I> for FnNode { diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 331e30028..4ffd54548 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -2,10 +2,10 @@ use crate::application_io::{TextureFrame, TextureFrameTable}; use crate::instances::Instances; use crate::raster::image::{ImageFrame, ImageFrameTable}; use crate::raster::BlendMode; -use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut}; +use crate::transform::{Transform, TransformMut}; use crate::uuid::NodeId; use crate::vector::{VectorData, VectorDataTable}; -use crate::Color; +use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use dyn_any::DynAny; @@ -280,28 +280,8 @@ impl ArtboardGroup { } #[node_macro::node(category(""))] -async fn layer( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - )] - stack: impl Node, - #[implementations( - () -> GraphicElement, - Footprint -> GraphicElement, - )] - element: impl Node, - node_path: Vec, -) -> GraphicGroupTable { - let mut element = element.eval(footprint).await; - let stack = stack.eval(footprint).await; - let stack = stack.one_item(); - let mut stack = stack.clone(); +async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec) -> GraphicGroupTable { + let mut stack = stack.one_item().clone(); if stack.transform.matrix2.determinant() != 0. { *element.transform_mut() = stack.transform.inverse() * element.transform(); @@ -318,72 +298,36 @@ async fn layer( } #[node_macro::node(category("Debug"))] -async fn to_element + 'n>( +async fn to_element + 'n>( + _: impl Ctx, #[implementations( - (), - (), - (), - (), - Footprint, + GraphicGroupTable, + VectorDataTable, + ImageFrameTable, + TextureFrameTable, )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - () -> VectorDataTable, - () -> ImageFrameTable, - () -> TextureFrameTable, - Footprint -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - Footprint -> TextureFrameTable, - )] - data: impl Node, + data: Data, ) -> GraphicElement { - data.eval(footprint).await.into() + data.into() } #[node_macro::node(category("General"))] -async fn to_group + 'n>( +async fn to_group + 'n>( + _: impl Ctx, #[implementations( - (), - (), - (), - (), - Footprint, + GraphicGroupTable, + VectorDataTable, + ImageFrameTable, + TextureFrameTable, )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - () -> VectorDataTable, - () -> ImageFrameTable, - () -> TextureFrameTable, - Footprint -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - Footprint -> TextureFrameTable, - )] - element: impl Node, + element: Data, ) -> GraphicGroupTable { - element.eval(footprint).await.into() + element.into() } #[node_macro::node(category("General"))] -async fn flatten_group( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - )] - group: impl Node, - fully_flatten: bool, -) -> GraphicGroupTable { - let nested_group = group.eval(footprint).await; - let nested_group = nested_group.one_item(); - let nested_group = nested_group.clone(); +async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable { + let nested_group = group.one_item().clone(); let mut flat_group = GraphicGroup::default(); @@ -420,34 +364,28 @@ async fn flatten_group( } #[node_macro::node(category(""))] -async fn to_artboard + 'n>( +async fn to_artboard + 'n>( + ctx: impl ExtractAll + CloneVarArgs + Ctx, #[implementations( - (), - (), - (), - (), - Footprint, + Context -> GraphicGroupTable, + Context -> VectorDataTable, + Context -> ImageFrameTable, + Context -> TextureFrameTable, )] - mut footprint: F, - #[implementations( - () -> GraphicGroupTable, - () -> VectorDataTable, - () -> ImageFrameTable, - () -> TextureFrame, - Footprint -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - Footprint -> TextureFrame, - )] - contents: impl Node, + contents: impl Node, Output = Data>, label: String, location: IVec2, dimensions: IVec2, background: Color, clip: bool, ) -> Artboard { - footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2())); - let graphic_group = contents.eval(footprint).await; + let footprint = ctx.try_footprint().copied(); + let mut new_ctx = OwnedContextImpl::from(ctx); + if let Some(mut footprint) = footprint { + footprint.translate(location.as_dvec2()); + new_ctx = new_ctx.with_footprint(footprint); + } + let graphic_group = contents.eval(new_ctx.into_context()).await; Artboard { graphic_group: graphic_group.into(), @@ -460,28 +398,16 @@ async fn to_artboard( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> ArtboardGroup, - Footprint -> ArtboardGroup, - )] - artboards: impl Node, - #[implementations( - () -> Artboard, - Footprint -> Artboard, - )] - artboard: impl Node, - node_path: Vec, -) -> ArtboardGroup { - let artboard = artboard.eval(footprint).await; - let mut artboards = artboards.eval(footprint).await; - +async fn append_artboard(ctx: impl Ctx, mut artboards: ArtboardGroup, artboard: Artboard, node_path: Vec) -> ArtboardGroup { + // let mut artboards = artboards.eval(ctx.clone()).await; + // let artboard = artboard.eval(ctx).await; + // let foot = ctx.footprint(); + // log::debug!("{:?}", foot); // Get the penultimate element of the node path, or None if the path is too short + + // TODO: Delete this line + let _ctx = ctx; + let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); artboards.append_artboard(artboard, encapsulating_node_id); diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 5883fbb9c..e419bc4cf 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -14,6 +14,7 @@ pub use crate as graphene_core; pub use ctor; pub mod consts; +pub mod context; pub mod generic; pub mod instances; pub mod logic; @@ -48,6 +49,7 @@ pub mod application_io; #[cfg(feature = "reflections")] pub mod registry; +pub use context::*; use core::any::TypeId; pub use memo::MemoHash; pub use raster::Color; @@ -56,7 +58,7 @@ pub use types::Cow; // pub trait Node: for<'n> NodeIO<'n> { /// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct. /// See `node-graph/README.md` for information on how to define a new node. -pub trait Node<'i, Input: 'i>: 'i { +pub trait Node<'i, Input> { type Output: 'i; /// Evaluates the node with the single specified input. fn eval(&'i self, input: Input) -> Self::Output; @@ -79,10 +81,10 @@ mod types; #[cfg(feature = "alloc")] pub use types::*; -pub trait NodeIO<'i, Input: 'i>: 'i + Node<'i, Input> +pub trait NodeIO<'i, Input>: Node<'i, Input> where Self::Output: 'i + StaticTypeSized, - Input: 'i + StaticTypeSized, + Input: StaticTypeSized, { fn input_type(&self) -> TypeId { TypeId::of::() @@ -112,8 +114,7 @@ where { NodeIOTypes { call_argument: concrete!(::Static), - // TODO return actual future type - return_value: concrete!(<::Output as StaticTypeSized>::Static), + return_value: future!(<::Output as StaticTypeSized>::Static), inputs, } } @@ -122,7 +123,7 @@ where impl<'i, N: Node<'i, I>, I> NodeIO<'i, I> for N where N::Output: 'i + StaticTypeSized, - I: 'i + StaticTypeSized, + I: StaticTypeSized, { } @@ -152,13 +153,13 @@ use dyn_any::StaticTypeSized; use core::pin::Pin; #[cfg(feature = "alloc")] -impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin + 'i>> { +impl<'i, I, O: 'i> Node<'i, I> for Pin + 'i>> { type Output = O; fn eval(&'i self, input: I) -> O { (**self).eval(input) } } -impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> { +impl<'i, I, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + 'i)> { type Output = O; fn eval(&'i self, input: I) -> O { (**self).eval(input) diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index 591110985..2abdc723e 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -1,57 +1,40 @@ -use crate::transform::Footprint; use crate::vector::VectorDataTable; - +use crate::Context; +use crate::Ctx; use glam::{DAffine2, DVec2}; #[node_macro::node(category("Debug"))] -async fn log_to_console( - #[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F, - #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2, - )] - value: impl Node, -) -> T { +fn log_to_console(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> T { #[cfg(not(target_arch = "spirv"))] // KEEP THIS `debug!()` - It acts as the output for the debug node itself - let value = value.eval(footprint).await; debug!("{:#?}", value); value } -#[node_macro::node(category("Debug"))] -async fn to_string( - #[implementations((), (), (), (), (), (), Footprint)] footprint: F, - #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, - )] - value: impl Node, -) -> String { - let value = value.eval(footprint).await; +#[node_macro::node(category("Debug"), skip_impl)] +fn to_string(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String { format!("{:?}", value) } #[node_macro::node(category("Debug"))] -async fn switch( - #[implementations((), (), (), (), (), (), (), (), Footprint)] footprint: F, +async fn switch( + #[implementations(Context)] ctx: C, condition: bool, #[expose] #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2 + Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2, )] - if_true: impl Node, + if_true: impl Node, #[expose] #[implementations( - () -> String, () -> bool, () -> f64, () -> u32, () -> u64, () -> DVec2, () -> VectorDataTable, () -> DAffine2, - Footprint -> String, Footprint -> bool, Footprint -> f64, Footprint -> u32, Footprint -> u64, Footprint -> DVec2, Footprint -> VectorDataTable, Footprint -> DAffine2 + Context -> String, Context -> bool, Context -> f64, Context -> u32, Context -> u64, Context -> DVec2, Context -> VectorDataTable, Context -> DAffine2, )] - if_false: impl Node, + if_false: impl Node, ) -> T { if condition { - if_true.eval(footprint).await + // We can't remove these calls because we only want to evaluate the branch that we actually need + if_true.eval(ctx).await } else { - if_false.eval(footprint).await + if_false.eval(ctx).await } } diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index e2d26566a..a44f379ee 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -2,6 +2,7 @@ use crate::raster::image::ImageFrameTable; use crate::raster::BlendMode; use crate::registry::types::Percentage; use crate::vector::style::GradientStops; +use crate::Ctx; use crate::{Color, Node}; use math_parser::ast; @@ -39,7 +40,7 @@ impl ValueProvider for MathNodeContext { /// Calculates a mathematical expression with input values "A" and "B" #[node_macro::node(category("General"), properties("math_properties"))] fn math( - _: (), + _: impl Ctx, /// The value of "A" when calculating the expression #[implementations(f64, f32)] operand_a: U, @@ -84,7 +85,7 @@ fn math( /// The addition operation (+) calculates the sum of two numbers. #[node_macro::node(category("Math: Arithmetic"))] fn add, T>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U, #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T, ) -> >::Output { @@ -94,7 +95,7 @@ fn add, T>( /// The subtraction operation (-) calculates the difference between two numbers. #[node_macro::node(category("Math: Arithmetic"))] fn subtract, T>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U, #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T, ) -> >::Output { @@ -104,7 +105,7 @@ fn subtract, T>( /// The multiplication operation (×) calculates the product of two numbers. #[node_macro::node(category("Math: Arithmetic"))] fn multiply, T>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U, #[default(1.)] #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] @@ -116,7 +117,7 @@ fn multiply, T>( /// The division operation (÷) calculates the quotient of two numbers. #[node_macro::node(category("Math: Arithmetic"))] fn divide, T>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, #[default(1.)] #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] @@ -128,7 +129,7 @@ fn divide, T>( /// The modulo operation (%) calculates the remainder from the division of two numbers. The sign of the result shares the sign of the numerator unless "Always Positive" is enabled. #[node_macro::node(category("Math: Arithmetic"))] fn modulo>>, T: Copy>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, #[default(2.)] #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] @@ -145,7 +146,7 @@ fn modulo>>, T: Copy /// The exponent operation (^) calculates the result of raising a number to a power. #[node_macro::node(category("Math: Arithmetic"))] fn exponent, T>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U, #[default(2.)] #[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)] @@ -157,7 +158,7 @@ fn exponent, T>( /// The square root operation (√) calculates the nth root of a number, equivalent to raising the number to the power of 1/n. #[node_macro::node(category("Math: Arithmetic"))] fn root( - _: (), + _: impl Ctx, #[default(2.)] #[implementations(f64, f32)] radicand: U, @@ -177,7 +178,7 @@ fn root( /// The logarithmic function (log) calculates the logarithm of a number with a specified base. If the natural logarithm function (ln) is desired, set the base to "e". #[node_macro::node(category("Math: Arithmetic"))] fn logarithm( - _: (), + _: impl Ctx, #[implementations(f64, f32)] value: U, #[default(2.)] #[implementations(f64, f32)] @@ -196,7 +197,7 @@ fn logarithm( /// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length. #[node_macro::node(category("Math: Trig"))] -fn sine(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U { +fn sine(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { if radians { theta.sin() } else { @@ -206,7 +207,7 @@ fn sine(_: (), #[implementations(f64, f32)] theta: /// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length. #[node_macro::node(category("Math: Trig"))] -fn cosine(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U { +fn cosine(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { if radians { theta.cos() } else { @@ -216,7 +217,7 @@ fn cosine(_: (), #[implementations(f64, f32)] theta /// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length. #[node_macro::node(category("Math: Trig"))] -fn tangent(_: (), #[implementations(f64, f32)] theta: U, radians: bool) -> U { +fn tangent(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { if radians { theta.tan() } else { @@ -226,7 +227,7 @@ fn tangent(_: (), #[implementations(f64, f32)] thet /// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value. #[node_macro::node(category("Math: Trig"))] -fn sine_inverse(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U { +fn sine_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { if radians { value.asin() } else { @@ -236,7 +237,7 @@ fn sine_inverse(_: (), #[implementations(f64, f32)] /// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value. #[node_macro::node(category("Math: Trig"))] -fn cosine_inverse(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U { +fn cosine_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { if radians { value.acos() } else { @@ -246,7 +247,7 @@ fn cosine_inverse(_: (), #[implementations(f64, f32 /// The inverse tangent trigonometric function (atan) calculates the angle whose tangent is the specified value. #[node_macro::node(category("Math: Trig"))] -fn tangent_inverse(_: (), #[implementations(f64, f32)] value: U, radians: bool) -> U { +fn tangent_inverse(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { if radians { value.atan() } else { @@ -257,7 +258,7 @@ fn tangent_inverse(_: (), #[implementations(f64, f3 /// The inverse tangent trigonometric function (atan2) calculates the angle whose tangent is the ratio of the two specified values. #[node_macro::node(name("Tangent Inverse 2-Argument"), category("Math: Trig"))] fn tangent_inverse_2_argument( - _: (), + _: impl Ctx, #[implementations(f64, f32)] y: U, #[expose] #[implementations(f64, f32)] @@ -274,7 +275,7 @@ fn tangent_inverse_2_argument( /// The random function (rand) converts a seed into a random number within the specified range, inclusive of the minimum and exclusive of the maximum. The minimum and maximum values are automatically swapped if they are reversed. #[node_macro::node(category("Math: Numeric"))] fn random( - _: (), + _: impl Ctx, _primary: (), seed: u64, #[implementations(f64, f32)] @@ -293,45 +294,45 @@ fn random( /// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. #[node_macro::node(name("To u32"), category("Math: Numeric"))] -fn to_u32(_: (), #[implementations(f64, f32)] value: U) -> u32 { +fn to_u32(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u32 { let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap()); value.to_u32().unwrap() } /// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. This will be removed in the future when automatic type conversion is implemented. #[node_macro::node(name("To u64"), category("Math: Numeric"))] -fn to_u64(_: (), #[implementations(f64, f32)] value: U) -> u64 { +fn to_u64(_: impl Ctx, #[implementations(f64, f32)] value: U) -> u64 { let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap()); value.to_u64().unwrap() } /// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero. #[node_macro::node(category("Math: Numeric"))] -fn round(_: (), #[implementations(f64, f32)] value: U) -> U { +fn round(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { value.round() } /// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole. #[node_macro::node(category("Math: Numeric"))] -fn floor(_: (), #[implementations(f64, f32)] value: U) -> U { +fn floor(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { value.floor() } /// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole. #[node_macro::node(category("Math: Numeric"))] -fn ceiling(_: (), #[implementations(f64, f32)] value: U) -> U { +fn ceiling(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { value.ceil() } /// The absolute value function (abs) removes the negative sign from an input value, if present. #[node_macro::node(category("Math: Numeric"))] -fn absolute_value(_: (), #[implementations(f64, f32)] value: U) -> U { +fn absolute_value(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { value.abs() } /// The minimum function (min) picks the smaller of two numbers. #[node_macro::node(category("Math: Numeric"))] -fn min(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { +fn min(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { match value < other_value { true => value, false => other_value, @@ -340,7 +341,7 @@ fn min(_: (), #[implementations(f64, &f64, f32, &f32, /// The maximum function (max) picks the larger of two numbers. #[node_macro::node(category("Math: Numeric"))] -fn max(_: (), #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { +fn max(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { match value > other_value { true => value, false => other_value, @@ -350,7 +351,7 @@ fn max(_: (), #[implementations(f64, &f64, f32, &f32, /// The clamp function (clamp) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed. #[node_macro::node(category("Math: Numeric"))] fn clamp( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T, @@ -368,7 +369,7 @@ fn clamp( /// The equality operation (==) compares two values and returns true if they are equal, or false if they are not. #[node_macro::node(category("Math: Logic"))] fn equals, T>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] #[min(100.)] @@ -381,7 +382,7 @@ fn equals, T>( /// The inequality operation (!=) compares two values and returns true if they are not equal, or false if they are. #[node_macro::node(category("Math: Logic"))] fn not_equals, T>( - _: (), + _: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] #[min(100.)] @@ -393,98 +394,98 @@ fn not_equals, T>( /// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false. #[node_macro::node(category("Math: Logic"))] -fn logical_or(_: (), value: bool, other_value: bool) -> bool { +fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool { value || other_value } /// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false. #[node_macro::node(category("Math: Logic"))] -fn logical_and(_: (), value: bool, other_value: bool) -> bool { +fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool { value && other_value } /// The logical not operation (!) reverses true and false value of the input. #[node_macro::node(category("Math: Logic"))] -fn logical_not(_: (), input: bool) -> bool { +fn logical_not(_: impl Ctx, input: bool) -> bool { !input } /// Constructs a bool value which may be set to true or false. #[node_macro::node(category("Value"))] -fn bool_value(_: (), _primary: (), #[name("Bool")] bool_value: bool) -> bool { +fn bool_value(_: impl Ctx, _primary: (), #[name("Bool")] bool_value: bool) -> bool { bool_value } /// Constructs a number value which may be set to any real number. #[node_macro::node(category("Value"))] -fn number_value(_: (), _primary: (), number: f64) -> f64 { +fn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 { number } /// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider. #[node_macro::node(category("Value"))] -fn percentage_value(_: (), _primary: (), percentage: Percentage) -> f64 { +fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 { percentage } /// Constructs a two-dimensional vector value which may be set to any XY coordinate. -#[node_macro::node(category("Value"))] -fn vector2_value(_: (), _primary: (), x: f64, y: f64) -> DVec2 { +#[node_macro::node(name("Vector2 Value"), category("Value"))] +fn vector2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 { DVec2::new(x, y) } /// Constructs a color value which may be set to any color, or no color. #[node_macro::node(category("Value"))] -fn color_value(_: (), _primary: (), #[default(Color::BLACK)] color: Option) -> Option { +fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option) -> Option { color } /// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. #[node_macro::node(category("Value"))] -fn gradient_value(_: (), _primary: (), gradient: GradientStops) -> GradientStops { +fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops { gradient } /// Constructs a blend mode choice value which may be set to any of the available blend modes in order to tell another node which blending operation to use. #[node_macro::node(category("Value"))] -fn blend_mode_value(_: (), _primary: (), blend_mode: BlendMode) -> BlendMode { +fn blend_mode_value(_: impl Ctx, _primary: (), blend_mode: BlendMode) -> BlendMode { blend_mode } /// Meant for debugging purposes, not general use. Returns the size of the input type in bytes. #[cfg(feature = "std")] #[node_macro::node(category("Debug"))] -fn size_of(_: (), ty: crate::Type) -> Option { +fn size_of(_: impl Ctx, ty: crate::Type) -> Option { ty.size() } /// Meant for debugging purposes, not general use. Wraps the input value in the Some variant of an Option. #[node_macro::node(category("Debug"))] -fn some(_: (), #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option { +fn some(_: impl Ctx, #[implementations(f64, f32, u32, u64, String, Color)] input: T) -> Option { Some(input) } /// Meant for debugging purposes, not general use. Unwraps the input value from an Option, returning the default value if the input is None. #[node_macro::node(category("Debug"))] -fn unwrap(_: (), #[implementations(Option, Option, Option, Option, Option, Option)] input: Option) -> T { +fn unwrap(_: impl Ctx, #[implementations(Option, Option, Option, Option, Option, Option)] input: Option) -> T { input.unwrap_or_default() } /// Meant for debugging purposes, not general use. Clones the input value. #[node_macro::node(category("Debug"))] -fn clone<'i, T: Clone + 'i>(_: (), #[implementations(&ImageFrameTable)] value: &'i T) -> T { +fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&ImageFrameTable)] value: &'i T) -> T { value.clone() } #[node_macro::node(category("Math: Vector"))] -fn dot_product(vector_a: DVec2, vector_b: DVec2) -> f64 { +fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 { vector_a.dot(vector_b) } // TODO: Rename to "Passthrough" /// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. #[node_macro::node(skip_impl)] -fn identity<'i, T: 'i>(value: T) -> T { +fn identity<'i, T: 'i + Send>(value: T) -> T { value } @@ -537,13 +538,13 @@ where #[cfg(test)] mod test { use super::*; - use crate::{generic::*, structural::*, value::*}; + use crate::generic::*; #[test] pub fn dot_product_function() { let vector_a = glam::DVec2::new(1., 2.); let vector_b = glam::DVec2::new(3., 4.); - assert_eq!(dot_product(vector_a, vector_b), 11.); + assert_eq!(dot_product((), vector_a, vector_b), 11.); } #[test] @@ -572,8 +573,7 @@ mod test { #[test] pub fn identity_node() { - let value = ValueNode(4u32).then(IdentityNode::new()); - assert_eq!(value.eval(()), &4); + assert_eq!(identity(&4), &4); } #[test] diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index 2abc76cee..3e35afbd0 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -1,8 +1,8 @@ pub use self::color::{Color, Luma, SRGBA8}; use crate::raster::image::ImageFrameTable; use crate::registry::types::Percentage; -use crate::transform::Footprint; use crate::vector::VectorDataTable; +use crate::Ctx; use crate::GraphicGroupTable; use bytemuck::{Pod, Zeroable}; @@ -182,6 +182,9 @@ pub trait Alpha { } fn multiplied_alpha(&self, alpha: Self::AlphaChannel) -> Self; } +pub trait AlphaMut: Alpha { + fn set_alpha(&mut self, value: Self::AlphaChannel); +} pub trait Depth { type DepthChannel: Channel; @@ -228,6 +231,12 @@ pub trait Bitmap { type Pixel: Pixel; fn width(&self) -> u32; fn height(&self) -> u32; + fn dimensions(&self) -> (u32, u32) { + (self.width(), self.height()) + } + fn dim(&self) -> (u32, u32) { + self.dimensions() + } fn get_pixel(&self, x: u32, y: u32) -> Option; } @@ -316,51 +325,31 @@ impl SetBlendMode for ImageFrameTable { } #[node_macro::node(category("Style"))] -async fn blend_mode( +fn blend_mode( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + GraphicGroupTable, + VectorDataTable, + ImageFrameTable, )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - () -> VectorDataTable, - () -> ImageFrameTable, - Footprint -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - )] - value: impl Node, + mut value: T, blend_mode: BlendMode, ) -> T { - let mut value = value.eval(footprint).await; value.set_blend_mode(blend_mode); value } #[node_macro::node(category("Style"))] -async fn opacity( +fn opacity( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + GraphicGroupTable, + VectorDataTable, + ImageFrameTable, )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - () -> VectorDataTable, - () -> ImageFrameTable, - Footprint -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - )] - value: impl Node, + mut value: T, #[default(100.)] factor: Percentage, ) -> T { - let mut value = value.eval(footprint).await; let opacity_multiplier = factor / 100.; value.multiply_alpha(opacity_multiplier); value diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 6ee2f36c7..9e875a8f5 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -6,9 +6,9 @@ use crate::raster::curve::{Curve, CurveManipulatorGroup, ValueMapperNode}; use crate::raster::image::{ImageFrame, ImageFrameTable}; use crate::raster::{Channel, Color, Pixel}; use crate::registry::types::{Angle, Percentage, SignedPercentage}; -use crate::transform::Footprint; use crate::vector::style::GradientStops; use crate::vector::VectorDataTable; +use crate::{Ctx, Node}; use crate::{GraphicElement, GraphicGroupTable}; use dyn_any::DynAny; @@ -284,26 +284,16 @@ impl From for vello::peniko::Mix { } #[node_macro::node(category("Raster: Adjustment"))] -async fn luminance>( +fn luminance>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, luminance_calc: LuminanceCalculation, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let luminance = match luminance_calc { LuminanceCalculation::SRGB => color.luminance_srgb(), @@ -318,26 +308,16 @@ async fn luminance>( } #[node_macro::node(category("Raster"))] -async fn extract_channel>( +fn extract_channel>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, channel: RedGreenBlueAlpha, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let extracted_value = match channel { RedGreenBlueAlpha::Red => color.r(), @@ -351,25 +331,15 @@ async fn extract_channel>( } #[node_macro::node(category("Raster"))] -async fn make_opaque>( +fn make_opaque>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { if color.a() == 0. { return color.with_alpha(1.); @@ -385,31 +355,21 @@ async fn make_opaque>( // Algorithm from: // https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels #[node_macro::node(category("Raster: Adjustment"))] -async fn levels>( +fn levels>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, #[default(0.)] shadows: Percentage, #[default(50.)] midtones: Percentage, #[default(100.)] highlights: Percentage, #[default(0.)] output_minimums: Percentage, #[default(100.)] output_maximums: Percentage, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let color = color.to_gamma_srgb(); // Input Range (Range: 0-1) @@ -451,7 +411,7 @@ async fn levels>( color.to_linear_srgb() }); - input + image } // Aims for interoperable compatibility with: @@ -462,23 +422,14 @@ async fn levels>( // https://stackoverflow.com/a/55233732/775283 // Works the same for gamma and linear color #[node_macro::node(name("Black & White"), category("Raster: Adjustment"))] -async fn black_and_white>( +async fn black_and_white>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, #[default(Color::BLACK)] tint: Color, #[default(40.)] #[range((-200., 300.))] @@ -499,8 +450,7 @@ async fn black_and_white>( #[range((-200., 300.))] magentas: Percentage, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let color = color.to_gamma_srgb(); let reds = reds as f32 / 100.; @@ -537,35 +487,25 @@ async fn black_and_white>( color.to_linear_srgb() }); - input + image } // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27hue%20%27%20%3D%20Old,saturation%2C%20Photoshop%205.0 // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=0%20%3D%20Use%20other.-,Hue/Saturation,-Hue/Saturation%20settings #[node_macro::node(name("Hue/Saturation"), category("Raster: Adjustment"))] -async fn hue_saturation>( +async fn hue_saturation>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, hue_shift: Angle, saturation_shift: SignedPercentage, lightness_shift: SignedPercentage, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -588,25 +528,15 @@ async fn hue_saturation>( // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27%20%3D%20Color%20Lookup-,%27nvrt%27%20%3D%20Invert,-%27post%27%20%3D%20Posterize #[node_macro::node(category("Raster: Adjustment"))] -async fn invert>( +async fn invert>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -620,29 +550,19 @@ async fn invert>( // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=post%27%20%3D%20Posterize-,%27thrs%27%20%3D%20Threshold,-%27grdm%27%20%3D%20Gradient #[node_macro::node(category("Raster: Adjustment"))] -async fn threshold>( +async fn threshold>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, #[default(50.)] min_luminance: Percentage, #[default(100.)] max_luminance: Percentage, luminance_calc: LuminanceCalculation, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let min_luminance = Color::srgb_to_linear(min_luminance as f32 / 100.); let max_luminance = Color::srgb_to_linear(max_luminance as f32 / 100.); @@ -660,7 +580,7 @@ async fn threshold>( Color::BLACK } }); - input + image } trait Blend { @@ -723,44 +643,35 @@ impl Blend for GradientStops { } #[node_macro::node(category("Raster"))] -async fn blend + Send>( +async fn blend + Send>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - over: impl Node, + over: T, #[expose] #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, + Color, + ImageFrameTable, + GradientStops, )] - under: impl Node, + under: T, blend_mode: BlendMode, #[default(100.)] opacity: Percentage, ) -> T { - let over = over.eval(footprint).await; - let under = under.eval(footprint).await; - over.blend(&under, |a, b| blend_colors(a, b, blend_mode, opacity / 100.)) } -#[node_macro::node(category(""))] -fn blend_color_pair(input: (Color, Color), blend_mode: BlendMode, opacity: Percentage) -> Color { +#[node_macro::node(category(""), skip_impl)] +fn blend_color_pair(input: (Color, Color), blend_mode: &'n BlendModeNode, opacity: &'n OpacityNode) -> Color +where + BlendModeNode: Node<'n, (), Output = BlendMode> + 'n, + OpacityNode: Node<'n, (), Output = Percentage> + 'n, +{ + let blend_mode = blend_mode.eval(()); + let opacity = opacity.eval(()); blend_colors(input.0, input.1, blend_mode, opacity / 100.) } @@ -857,35 +768,24 @@ pub fn blend_colors(foreground: Color, background: Color, blend_mode: BlendMode, // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27grdm%27%20%3D%20Gradient%20Map // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Gradient%20settings%20(Photoshop%206.0) #[node_macro::node(category("Raster: Adjustment"))] -async fn gradient_map>( +async fn gradient_map>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, gradient: GradientStops, reverse: bool, ) -> T { - let mut input = image.eval(footprint).await; - - input.adjust(|color| { + image.adjust(|color| { let intensity = color.luminance_srgb(); let intensity = if reverse { 1. - intensity } else { intensity }; gradient.evalute(intensity as f64) }); - input + image } // Aims for interoperable compatibility with: @@ -896,27 +796,17 @@ async fn gradient_map>( // https://stackoverflow.com/questions/33966121/what-is-the-algorithm-for-vibrance-filters // The results of this implementation are very close to correct, but not quite perfect #[node_macro::node(category("Raster: Adjustment"))] -async fn vibrance>( +async fn vibrance>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, vibrance: SignedPercentage, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let vibrance = vibrance as f32 / 100.; // Slow the effect down by half when it's negative, since artifacts begin appearing past -50%. // So this scales the 0% to -50% range to 0% to -100%. @@ -963,7 +853,7 @@ async fn vibrance>( altered_color.map_rgb(|c| c * (1. - factor) + luminance * factor) } }); - input + image } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -1196,23 +1086,14 @@ impl DomainWarpType { // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr #[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))] -async fn channel_mixer>( +async fn channel_mixer>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, monochrome: bool, #[default(40.)] @@ -1270,8 +1151,7 @@ async fn channel_mixer>( // Display-only properties (not used within the node) _output_channel: RedGreenBlue, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let color = color.to_gamma_srgb(); let (r, g, b, a) = color.components(); @@ -1296,7 +1176,7 @@ async fn channel_mixer>( color.to_linear_srgb() }); - input + image } #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -1356,24 +1236,15 @@ impl core::fmt::Display for SelectiveColorChoice { // // Algorithm based on: // https://blog.pkh.me/p/22-understanding-selective-coloring-in-adobe-photoshop.html -#[node_macro::node(category("Raster: Adjustment"))] -async fn selective_color>( +#[node_macro::node(category("Raster: Adjustment"), properties("selective_color_properties"))] +async fn selective_color>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, mode: RelativeAbsolute, #[name("(Reds) Cyan")] r_c: f64, #[name("(Reds) Magenta")] r_m: f64, @@ -1413,8 +1284,7 @@ async fn selective_color>( #[name("(Blacks) Black")] k_k: f64, _colors: SelectiveColorChoice, ) -> T { - let mut input = image.eval(footprint).await; - input.adjust(|color| { + image.adjust(|color| { let color = color.to_gamma_srgb(); let (r, g, b, a) = color.components(); @@ -1488,7 +1358,7 @@ async fn selective_color>( color.to_linear_srgb() }); - input + image } pub(super) trait MultiplyAlpha { @@ -1534,28 +1404,18 @@ where // https://www.axiomx.com/posterize.htm // This algorithm produces fully accurate output in relation to the industry standard. #[node_macro::node(category("Raster: Adjustment"))] -async fn posterize>( +async fn posterize>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, #[default(4)] #[min(2.)] levels: u32, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let color = color.to_gamma_srgb(); @@ -1577,30 +1437,20 @@ async fn posterize>( // Algorithm based on: // https://geraldbakker.nl/psnumbers/exposure.html #[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))] -async fn exposure>( +async fn exposure>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - input: impl Node, + mut input: T, exposure: f64, offset: f64, #[default(1.)] #[range((0.01, 10.))] gamma_correction: f64, ) -> T { - let mut input = input.eval(footprint).await; input.adjust(|color| { let adjusted = color // Exposure @@ -1619,7 +1469,7 @@ const WINDOW_SIZE: usize = 1024; #[cfg(feature = "alloc")] #[node_macro::node(category(""))] -fn generate_curves(_: (), curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode { +fn generate_curves(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode { use bezier_rs::{Bezier, TValue}; let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle]; @@ -1660,31 +1510,21 @@ fn generate_curves(_: (), curve: Curve, #[implementa #[cfg(feature = "alloc")] #[node_macro::node(category("Raster: Adjustment"))] -async fn color_overlay>( +fn color_overlay>( + _: impl Ctx, #[implementations( - (), - (), - (), - Footprint, + Color, + ImageFrameTable, + GradientStops, )] - footprint: F, - #[implementations( - () -> Color, - () -> ImageFrameTable, - () -> GradientStops, - Footprint -> Color, - Footprint -> ImageFrameTable, - Footprint -> GradientStops, - )] - image: impl Node, + mut image: T, #[default(Color::BLACK)] color: Color, blend_mode: BlendMode, #[default(100.)] opacity: Percentage, ) -> T { let opacity = (opacity as f32 / 100.).clamp(0., 1.); - let mut input = image.eval(footprint).await; - input.adjust(|pixel| { + image.adjust(|pixel| { let image = pixel.map_rgb(|channel| channel * (1. - opacity)); // The apply blend mode function divides rgb by the alpha channel for the background. This undoes that. @@ -1693,7 +1533,7 @@ async fn color_overlay>( Color::from_rgbaf32_unchecked(image.r() + overlay.r(), image.g() + overlay.g(), image.b() + overlay.b(), pixel.a()) }); - input + image } // #[cfg(feature = "alloc")] @@ -1702,10 +1542,11 @@ async fn color_overlay>( // #[cfg(feature = "alloc")] // mod index_node { // use crate::raster::{Color, ImageFrame}; +// use crate::Ctx; // #[node_macro::node(category(""))] // pub fn index( -// _: (), +// _: impl Ctx, // #[implementations(Vec>, Vec)] // #[widget(ParsedWidgetOverride::Hidden)] // input: Vec, @@ -1752,7 +1593,7 @@ mod test { // 100% of the output should come from the multiplied value let opacity = 100_f64; - let result = super::color_overlay((), &FutureWrapperNode(ImageFrameTable::new(image.clone())), overlay_color, BlendMode::Multiply, opacity).await; + let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity); let result = result.one_item(); // The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0) diff --git a/node-graph/gcore/src/raster/color.rs b/node-graph/gcore/src/raster/color.rs index 61487db88..ac10430ae 100644 --- a/node-graph/gcore/src/raster/color.rs +++ b/node-graph/gcore/src/raster/color.rs @@ -1,5 +1,5 @@ use super::discrete_srgb::{float_to_srgb_u8, srgb_u8_to_float}; -use super::{Alpha, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB}; +use super::{Alpha, AlphaMut, AssociatedAlpha, Luminance, LuminanceMut, Pixel, RGBMut, Rec709Primaries, RGB, SRGB}; use dyn_any::DynAny; #[cfg(feature = "serde")] @@ -257,6 +257,11 @@ impl RGBMut for Color { self.blue = blue; } } +impl AlphaMut for Color { + fn set_alpha(&mut self, value: Self::AlphaChannel) { + self.alpha = value; + } +} impl Pixel for Color { #[cfg(not(target_arch = "spirv"))] diff --git a/node-graph/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index be702a5b7..5fc7c5da6 100644 --- a/node-graph/gcore/src/structural.rs +++ b/node-graph/gcore/src/structural.rs @@ -132,14 +132,15 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode { #[cfg(test)] mod test { use super::*; - use crate::{ops::IdentityNode, value::ValueNode}; + use crate::generic::FnNode; + use crate::value::ValueNode; #[test] fn compose() { let value = ValueNode::new(4u32); - let compose = value.then(IdentityNode::new()); + let compose = value.then(FnNode::new(|x| x)); assert_eq!(compose.eval(()), &4u32); - let type_erased = &compose as &dyn for<'i> Node<'i, (), Output = &'i u32>; + let type_erased = &compose as &dyn Node<'_, (), Output = &'_ u32>; assert_eq!(type_erased.eval(()), &4u32); } @@ -148,7 +149,7 @@ mod test { let value = ValueNode::new(5); assert_eq!(value.eval(()), &5); - let id = IdentityNode::new(); + let id = FnNode::new(|x| x); let compose = ComposeNode::new(&value, &id); diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index 3aaf1c245..31ea15387 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -1,9 +1,9 @@ -use crate::application_io::{TextureFrame, TextureFrameTable}; +use crate::application_io::TextureFrameTable; use crate::raster::bbox::AxisAlignedBbox; use crate::raster::image::{ImageFrame, ImageFrameTable}; use crate::raster::Pixel; use crate::vector::{VectorData, VectorDataTable}; -use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable}; +use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl}; use glam::{DAffine2, DVec2}; @@ -244,6 +244,12 @@ pub struct Footprint { impl Default for Footprint { fn default() -> Self { + Self::empty() + } +} + +impl Footprint { + pub const fn empty() -> Self { Self { transform: DAffine2::IDENTITY, resolution: glam::UVec2::new(1920, 1080), @@ -251,9 +257,6 @@ impl Default for Footprint { ignore_modifications: false, } } -} - -impl Footprint { pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox { let inverse = self.transform.inverse(); let start = inverse.transform_point2((0., 0.).into()); @@ -277,7 +280,7 @@ impl From<()> for Footprint { } #[node_macro::node(category("Debug"))] -fn cull(_footprint: Footprint, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable, ArtboardGroup)] data: T) -> T { +fn cull(_: impl Ctx, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable, ArtboardGroup)] data: T) -> T { data } @@ -301,26 +304,15 @@ impl ApplyTransform for () { } #[node_macro::node(category(""))] -async fn transform + 'n + ApplyTransform + Clone + Send + Sync, T: 'n + TransformMut>( +async fn transform( + ctx: impl Ctx + CloneVarArgs + ExtractAll, #[implementations( - (), - (), - (), - (), - Footprint, + Context -> VectorDataTable, + Context -> GraphicGroupTable, + Context -> ImageFrameTable, + Context -> TextureFrameTable, )] - mut input: I, - #[implementations( - () -> VectorDataTable, - () -> GraphicGroupTable, - () -> ImageFrameTable, - () -> TextureFrame, - Footprint -> VectorDataTable, - Footprint -> GraphicGroupTable, - Footprint -> ImageFrameTable, - Footprint -> TextureFrame, - )] - transform_target: impl Node, + transform_target: impl Node, Output = T>, translate: DVec2, rotate: f64, scale: DVec2, @@ -328,22 +320,27 @@ async fn transform + 'n + ApplyTransform + Clone + Send + Syn _pivot: DVec2, ) -> T { let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]); - let footprint = input.clone().into(); - if !footprint.ignore_modifications { - input.apply_transform(&modification); + let footprint = ctx.try_footprint().copied(); + + let mut ctx = OwnedContextImpl::from(ctx); + if let Some(mut footprint) = footprint { + if !footprint.ignore_modifications { + footprint.apply_transform(&modification); + } + ctx = ctx.with_footprint(footprint); } - let mut data = transform_target.eval(input).await; + let mut transform_target = transform_target.eval(ctx.into_context()).await; - let data_transform = data.transform_mut(); + let data_transform = transform_target.transform_mut(); *data_transform = modification * (*data_transform); - data + transform_target } #[node_macro::node(category(""))] fn replace_transform( - _: (), + _: impl Ctx, #[implementations(VectorDataTable, ImageFrameTable, GraphicGroupTable)] mut data: Data, #[implementations(DAffine2)] transform: TransformInput, ) -> Data { diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 1868a4f7a..86a034cf7 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -53,6 +53,9 @@ macro_rules! future { ($type:ty) => {{ $crate::Type::Future(Box::new(concrete!($type))) }}; + ($type:ty, $name:ty) => { + $crate::Type::Future(Box::new(concrete!($type, $name))) + }; } #[macro_export] @@ -67,6 +70,18 @@ macro_rules! fn_type { $crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(concrete!($type))) }; } +#[macro_export] +macro_rules! fn_type_fut { + ($type:ty) => { + $crate::Type::Fn(Box::new(concrete!(())), Box::new(future!($type))) + }; + ($in_type:ty, $type:ty, alias: $outname:ty) => { + $crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type, $outname))) + }; + ($in_type:ty, $type:ty) => { + $crate::Type::Fn(Box::new(concrete!($in_type)), Box::new(future!($type))) + }; +} #[derive(Clone, PartialEq, Eq, Hash, Default)] pub struct NodeIOTypes { @@ -134,6 +149,7 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: let name = String::deserialize(deserializer)?; let name = match name.as_str() { "f32" => "f64".to_string(), + "graphene_core::transform::Footprint" => "core::option::Option>".to_string(), "graphene_core::graphic_element::GraphicGroup" => "graphene_core::graphic_element::Instances".to_string(), "graphene_core::vector::vector_data::VectorData" => "graphene_core::graphic_element::Instances".to_string(), "graphene_core::raster::image::ImageFrame" => "graphene_core::graphic_element::Instances>".to_string(), @@ -189,7 +205,7 @@ pub enum Type { /// Runtime type information for a function. Given some input, gives some output. /// See the example and explanation in the `ComposeNode` implementation within the node registry for more info. Fn(Box, Box), - /// Not used at the moment. + /// Represents a future which promises to return the inner type. Future(Box), } @@ -280,7 +296,7 @@ impl Type { Self::Generic(_) => self, Self::Concrete(_) => self, Self::Fn(_, output) => output.nested_type(), - Self::Future(_) => self, + Self::Future(output) => output.nested_type(), } } } diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 48389f02f..e63ad453c 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -8,10 +8,10 @@ use core::{ #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct IntNode; -impl<'i, const N: u32> Node<'i, ()> for IntNode { +impl<'i, const N: u32, I> Node<'i, I> for IntNode { type Output = u32; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { N } } @@ -19,10 +19,10 @@ impl<'i, const N: u32> Node<'i, ()> for IntNode { #[derive(Default, Debug, Clone, Copy)] pub struct ValueNode(pub T); -impl<'i, T: 'i> Node<'i, ()> for ValueNode { +impl<'i, T: 'i, I> Node<'i, I> for ValueNode { type Output = &'i T; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { &self.0 } } @@ -77,10 +77,10 @@ impl RefCellMutNode { #[derive(Default)] pub struct OnceCellNode(pub Cell); -impl<'i, T: Default + 'i> Node<'i, ()> for OnceCellNode { +impl<'i, T: Default + 'i, I> Node<'i, I> for OnceCellNode { type Output = T; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { self.0.replace(T::default()) } } @@ -94,10 +94,10 @@ impl OnceCellNode { #[derive(Clone, Copy)] pub struct ClonedNode(pub T); -impl<'i, T: Clone + 'i> Node<'i, ()> for ClonedNode { +impl<'i, T: Clone + 'i, I> Node<'i, I> for ClonedNode { type Output = T; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { self.0.clone() } } @@ -140,10 +140,10 @@ impl DebugClonedNode { #[derive(Clone, Copy)] pub struct CopiedNode(pub T); -impl<'i, T: Copy + 'i> Node<'i, ()> for CopiedNode { +impl<'i, T: Copy + 'i, I> Node<'i, I> for CopiedNode { type Output = T; #[inline(always)] - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { self.0 } } @@ -157,9 +157,9 @@ impl CopiedNode { #[derive(Default)] pub struct DefaultNode(PhantomData); -impl<'i, T: Default + 'i> Node<'i, ()> for DefaultNode { +impl<'i, T: Default + 'i, I> Node<'i, I> for DefaultNode { type Output = T; - fn eval(&'i self, _input: ()) -> Self::Output { + fn eval(&'i self, _input: I) -> Self::Output { T::default() } } @@ -205,7 +205,7 @@ mod test { #[test] fn test_default_node() { let node = DefaultNode::::new(); - assert_eq!(node.eval(()), 0); + assert_eq!(node.eval(42), 0); } #[test] #[allow(clippy::unit_cmp)] diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index d7fc44d5d..bcca892b3 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,5 +1,5 @@ -use crate::transform::Footprint; use crate::vector::{HandleId, PointId, VectorData, VectorDataTable}; +use crate::Ctx; use bezier_rs::Subpath; use glam::DVec2; @@ -35,12 +35,12 @@ impl CornerRadius for [f64; 4] { } #[node_macro::node(category("Vector: Shape"))] -fn circle(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable { +fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable { VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) } #[node_macro::node(category("Vector: Shape"))] -fn ellipse(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { +fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { let radius = DVec2::new(radius_x, radius_y); let corner1 = -radius; let corner2 = radius; @@ -58,8 +58,8 @@ fn ellipse(#[implementations((), Footprint)] _footprint: F, _prima } #[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))] -fn rectangle( - #[implementations((), Footprint)] _footprint: F, +fn rectangle( + _: impl Ctx, _primary: (), #[default(100)] width: f64, #[default(100)] height: f64, @@ -71,8 +71,8 @@ fn rectangle( } #[node_macro::node(category("Vector: Shape"))] -fn regular_polygon( - #[implementations((), Footprint)] _footprint: F, +fn regular_polygon( + _: impl Ctx, _primary: (), #[default(6)] #[min(3.)] @@ -85,8 +85,8 @@ fn regular_polygon( } #[node_macro::node(category("Vector: Shape"))] -fn star( - #[implementations((), Footprint)] _footprint: F, +fn star( + _: impl Ctx, _primary: (), #[default(5)] #[min(2.)] @@ -102,15 +102,14 @@ fn star( } #[node_macro::node(category("Vector: Shape"))] -fn line(#[implementations((), Footprint)] _footprint: F, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable { +fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable { VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end))) } // TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node #[node_macro::node(category(""))] -fn path(#[implementations((), Footprint)] _footprint: F, path_data: Vec>, colinear_manipulators: Vec) -> VectorDataTable { +fn path(_: impl Ctx, path_data: Vec>, colinear_manipulators: Vec) -> VectorDataTable { let mut vector_data = VectorData::from_subpaths(path_data, false); - vector_data.colinear_manipulators = colinear_manipulators .iter() .filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data)) diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index a73add8c6..221e41ad3 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -1,6 +1,6 @@ use super::*; -use crate::transform::Footprint; use crate::uuid::generate_uuid; +use crate::Ctx; use bezier_rs::BezierHandles; use dyn_any::DynAny; @@ -424,20 +424,7 @@ impl core::hash::Hash for VectorModification { /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] -async fn path_modify( - #[implementations( - (), - Footprint, - )] - input: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, - modification: Box, -) -> VectorDataTable { - let mut vector_data = vector_data.eval(input).await; +async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box) -> VectorDataTable { let vector_data = vector_data.one_item_mut(); modification.apply(vector_data); diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 389ef9884..1201e8818 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -6,7 +6,7 @@ use crate::renderer::GraphicElementRendered; use crate::transform::{Footprint, Transform, TransformMut}; use crate::vector::style::LineJoin; use crate::vector::PointDomain; -use crate::{Color, GraphicElement, GraphicGroup, GraphicGroupTable}; +use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl}; use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue}; use glam::{DAffine2, DVec2}; @@ -43,21 +43,11 @@ impl VectorIterMut for VectorDataTable { } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn assign_colors( - #[implementations( - (), - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - () -> VectorDataTable, - Footprint -> GraphicGroupTable, - Footprint -> VectorDataTable, - )] +async fn assign_colors( + _: impl Ctx, + #[implementations(GraphicGroupTable, VectorDataTable)] #[widget(ParsedWidgetOverride::Hidden)] - vector_group: impl Node, + mut vector_group: T, #[default(true)] fill: bool, stroke: bool, #[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] gradient: GradientStops, @@ -66,8 +56,6 @@ async fn assign_colors( #[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue, #[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] repeat_every: u32, ) -> T { - let mut vector_group = vector_group.eval(footprint).await; - let length = vector_group.vector_iter_mut().count(); let gradient = if reverse { gradient.reversed() } else { gradient }; @@ -99,54 +87,20 @@ async fn assign_colors( } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] -async fn fill + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( +async fn fill + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( + _: impl Ctx, #[implementations( - (), - (), - (), - (), - (), - (), - (), - (), - Footprint, - Footprint, - Footprint, - Footprint, - Footprint, - Footprint, - Footprint, - Footprint, + VectorDataTable, + VectorDataTable, + VectorDataTable, + VectorDataTable, + GraphicGroupTable, + GraphicGroupTable, + GraphicGroupTable, + GraphicGroupTable )] - footprint: F, + mut vector_data: TargetTy, #[implementations( - () -> VectorDataTable, - () -> VectorDataTable, - () -> VectorDataTable, - () -> VectorDataTable, - () -> GraphicGroupTable, - () -> GraphicGroupTable, - () -> GraphicGroupTable, - () -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> VectorDataTable, - Footprint -> VectorDataTable, - Footprint -> VectorDataTable, - Footprint -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - )] - vector_data: impl Node, - #[implementations( - Fill, - Option, - Color, - Gradient, - Fill, - Option, - Color, - Gradient, Fill, Option, Color, @@ -161,44 +115,19 @@ async fn fill + 'n + Send, TargetTy: VectorIter _backup_color: Option, _backup_gradient: Gradient, ) -> TargetTy { - let mut target = vector_data.eval(footprint).await; let fill: Fill = fill.into(); - for (target, _transform) in target.vector_iter_mut() { + for (target, _transform) in vector_data.vector_iter_mut() { target.style.set_fill(fill.clone()); } - target + vector_data } #[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))] -async fn stroke> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( +async fn stroke> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( + _: impl Ctx, + #[implementations(VectorDataTable, VectorDataTable, GraphicGroupTable, GraphicGroupTable)] mut vector_data: TargetTy, #[implementations( - (), - (), - (), - (), - Footprint, - Footprint, - Footprint, - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - () -> VectorDataTable, - () -> GraphicGroupTable, - () -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> VectorDataTable, - Footprint -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - )] - vector_data: impl Node, - #[implementations( - Option, - Color, - Option, - Color, Option, Color, Option, @@ -213,7 +142,6 @@ async fn stroke> + 'n + Send, TargetTy line_join: LineJoin, #[default(4.)] miter_limit: f64, ) -> TargetTy { - let mut target = vector_data.eval(footprint).await; let stroke = Stroke { color: color.into(), weight, @@ -224,37 +152,24 @@ async fn stroke> + 'n + Send, TargetTy line_join_miter_limit: miter_limit, transform: DAffine2::IDENTITY, }; - for (target, transform) in target.vector_iter_mut() { + for (target, transform) in vector_data.vector_iter_mut() { target.style.set_stroke(Stroke { transform, ..stroke.clone() }); } - target + vector_data } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn repeat( - #[implementations( - (), - (), - Footprint, - Footprint, - )] - footprint: F, +async fn repeat( + _: impl Ctx, // TODO: Implement other GraphicElementRendered types. - #[implementations( - () -> VectorDataTable, - () -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> GraphicGroupTable, - )] - instance: impl Node, + #[implementations(VectorDataTable, GraphicGroupTable)] instance: I, #[default(100., 100.)] // TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed. direction: DVec2, angle: Angle, #[default(4)] instances: IntegerCount, ) -> GraphicGroupTable { - let instance = instance.eval(footprint).await; let first_vector_transform = instance.transform(); let angle = angle.to_radians(); @@ -285,27 +200,14 @@ async fn repeat( - #[implementations( - (), - (), - Footprint, - Footprint, - )] - footprint: F, +async fn circular_repeat( + _: impl Ctx, // TODO: Implement other GraphicElementRendered types. - #[implementations( - () -> VectorDataTable, - () -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> GraphicGroupTable, - )] - instance: impl Node, + #[implementations(VectorDataTable, GraphicGroupTable)] instance: I, angle_offset: Angle, #[default(5)] radius: f64, #[default(5)] instances: IntegerCount, ) -> GraphicGroupTable { - let instance = instance.eval(footprint).await; let first_vector_transform = instance.transform(); let instances = instances.max(1); @@ -334,27 +236,12 @@ async fn circular_repeat( - #[implementations( - (), - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - points: impl Node, +async fn copy_to_points( + _: impl Ctx, + points: VectorDataTable, #[expose] - #[implementations( - () -> VectorDataTable, - () -> GraphicGroupTable, - Footprint -> VectorDataTable, - Footprint -> GraphicGroupTable, - )] - instance: impl Node, + #[implementations(VectorDataTable, GraphicGroupTable)] + instance: I, #[default(1)] random_scale_min: f64, #[default(1)] random_scale_max: f64, random_scale_bias: f64, @@ -362,11 +249,8 @@ async fn copy_to_points GraphicGroupTable { - let points = points.eval(footprint).await; let points = points.one_item(); - let instance = instance.eval(footprint).await; - let instance_transform = instance.transform(); let random_scale_difference = random_scale_max - random_scale_min; @@ -421,19 +305,7 @@ async fn copy_to_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, -) -> VectorDataTable { - let vector_data = vector_data.eval(footprint).await; +async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { let vector_data = vector_data.one_item(); let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap(); @@ -445,30 +317,16 @@ async fn bounding_box( } #[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))] -async fn offset_path( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, - distance: f64, - line_join: LineJoin, - #[default(4.)] miter_limit: f64, -) -> VectorDataTable { - let vector_data = vector_data.eval(footprint).await; +async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable { let vector_data = vector_data.one_item(); + let subpaths = vector_data.stroke_bezier_paths(); let mut result = VectorData::empty(); result.style = vector_data.style.clone(); result.style.set_stroke_transform(DAffine2::IDENTITY); // Perform operation on all subpaths in this shape. - for mut subpath in vector_data.stroke_bezier_paths() { + for mut subpath in subpaths { subpath.apply_transform(vector_data.transform); // Taking the existing stroke data and passing it to Bezier-rs to generate new paths. @@ -489,19 +347,7 @@ async fn offset_path( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn solidify_stroke( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, -) -> VectorDataTable { - let vector_data = vector_data.eval(footprint).await; +async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable { let vector_data = vector_data.one_item(); let transform = &vector_data.transform; @@ -551,20 +397,8 @@ async fn solidify_stroke( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn flatten_vector_elements( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - )] - graphic_group_input: impl Node, -) -> VectorDataTable { - let graphic_group = graphic_group_input.eval(footprint).await; - let graphic_group = graphic_group.one_item(); +async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable { + let graphic_group_input = graphic_group_input.one_item(); // A node based solution to support passing through vector data could be a network node with a cache node connected to // a flatten vector elements connected to an if else node, another connection from the cache directly @@ -587,7 +421,7 @@ async fn flatten_vector_elements( } let mut result = VectorData::empty(); - concat_group(graphic_group, DAffine2::IDENTITY, &mut result); + concat_group(graphic_group_input, DAffine2::IDENTITY, &mut result); // TODO: This leads to incorrect stroke widths when flattening groups with different transforms. result.style.set_stroke_transform(DAffine2::IDENTITY); @@ -614,34 +448,10 @@ impl ConcatElement for GraphicGroupTable { } #[node_macro::node(category(""), path(graphene_core::vector))] -async fn sample_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, - spacing: f64, - start_offset: f64, - stop_offset: f64, - adaptive_spacing: bool, - #[implementations( - () -> Vec, - Footprint -> Vec, - )] - subpath_segment_lengths: impl Node>, -) -> VectorDataTable { +async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec) -> VectorDataTable { // Limit the smallest spacing to something sensible to avoid freezing the application. let spacing = spacing.max(0.01); - - // Evaluate vector data and subpath segment lengths asynchronously. - let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.one_item(); - let subpath_segment_lengths = subpath_segment_lengths.eval(footprint).await; // Create an iterator over the bezier segments with enumeration and peeking capability. let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable(); @@ -786,23 +596,14 @@ async fn sample_points( } #[node_macro::node(category(""), path(graphene_core::vector))] -async fn poisson_disk_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, +async fn poisson_disk_points( + _: impl Ctx, + vector_data: VectorDataTable, #[default(10.)] #[min(0.01)] separation_disk_diameter: f64, seed: SeedValue, ) -> VectorDataTable { - let vector_data = vector_data.eval(footprint).await; let vector_data = vector_data.one_item(); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); @@ -846,19 +647,7 @@ async fn poisson_disk_points( } #[node_macro::node(category(""), path(graphene_core::vector))] -async fn subpath_segment_lengths( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, -) -> Vec { - let vector_data = vector_data.eval(footprint).await; +async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec { let vector_data = vector_data.one_item(); vector_data @@ -868,20 +657,7 @@ async fn subpath_segment_lengths( } #[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))] -async fn spline( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, -) -> VectorDataTable { - // Evaluate the vector data within the given footprint. - let mut vector_data = vector_data.eval(footprint).await; +async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable { let vector_data = vector_data.one_item_mut(); // Exit early if there are no points to generate splines from. @@ -923,21 +699,7 @@ async fn spline( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn jitter_points( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - vector_data: impl Node, - #[default(5.)] amount: f64, - seed: SeedValue, -) -> VectorDataTable { - let vector_data = vector_data.eval(footprint).await; +async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable { let mut vector_data = vector_data.one_item().clone(); let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); @@ -986,31 +748,16 @@ async fn jitter_points( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn morph( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - source: impl Node, - #[expose] - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - target: impl Node, +async fn morph( + _: impl Ctx, + source: VectorDataTable, + #[expose] target: VectorDataTable, #[range((0., 1.))] #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount, ) -> VectorDataTable { - let source = source.eval(footprint).await; let source = source.one_item(); - let target = target.eval(footprint).await; let target = target.one_item(); let mut result = VectorData::empty(); @@ -1202,30 +949,16 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData { } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn bevel( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorDataTable, - Footprint -> VectorDataTable, - )] - source: impl Node, - #[default(10.)] distance: Length, -) -> VectorDataTable { - let source = source.eval(footprint).await; +fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable { let source = source.one_item(); - let result = bevel_algorithm(source.clone(), distance); - - VectorDataTable::new(result) + VectorDataTable::new(bevel_algorithm(source.clone(), distance)) } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn area(_: (), vector_data: impl Node) -> f64 { - let vector_data = vector_data.eval(Footprint::default()).await; +async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node, Output = VectorDataTable>) -> f64 { + let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); + let vector_data = vector_data.eval(new_ctx).await; let vector_data = vector_data.one_item(); let mut area = 0.; @@ -1237,8 +970,9 @@ async fn area(_: (), vector_data: impl Node } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn centroid(_: (), vector_data: impl Node, centroid_type: CentroidType) -> DVec2 { - let vector_data = vector_data.eval(Footprint::default()).await; +async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 { + let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context(); + let vector_data = vector_data.eval(new_ctx).await; let vector_data = vector_data.one_item(); if centroid_type == CentroidType::Area { @@ -1303,16 +1037,16 @@ mod test { } } - fn vector_node(data: Subpath) -> FutureWrapperNode { - FutureWrapperNode(VectorDataTable::new(VectorData::from_subpath(data))) + fn vector_node(data: Subpath) -> VectorDataTable { + VectorDataTable::new(VectorData::from_subpath(data)) } #[tokio::test] async fn repeat() { let direction = DVec2::X * 1.5; let instances = 3; - let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; - let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await; + let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; + let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = vector_data.one_item(); assert_eq!(vector_data.region_bezier_paths().count(), 3); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { @@ -1323,8 +1057,8 @@ mod test { async fn repeat_transform_position() { let direction = DVec2::new(12., 10.); let instances = 8; - let repeated = super::repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; - let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await; + let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; + let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = vector_data.one_item(); assert_eq!(vector_data.region_bezier_paths().count(), 8); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { @@ -1333,8 +1067,8 @@ mod test { } #[tokio::test] async fn circle_repeat() { - let repeated = super::circular_repeat(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; - let vector_data = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(repeated)).await; + let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; + let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await; let vector_data = vector_data.one_item(); assert_eq!(vector_data.region_bezier_paths().count(), 8); for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { @@ -1346,10 +1080,7 @@ mod test { } #[tokio::test] async fn bounding_box() { - let bounding_box = BoundingBoxNode { - vector_data: vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), - }; - let bounding_box = bounding_box.eval(Footprint::default()).await; + let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await; let bounding_box = bounding_box.one_item(); assert_eq!(bounding_box.region_bezier_paths().count(), 1); let subpath = bounding_box.region_bezier_paths().next().unwrap().1; @@ -1375,8 +1106,8 @@ mod test { let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.); let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE); let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec(); - let copy_to_points = super::copy_to_points(Footprint::default(), &vector_node(points), &vector_node(instance), 1., 1., 0., 0, 0., 0).await; - let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), &FutureWrapperNode(copy_to_points)).await; + let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await; + let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), copy_to_points).await; let flattened_copy_to_points = flattened_copy_to_points.one_item(); assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len()); for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() { @@ -1390,7 +1121,7 @@ mod test { #[tokio::test] async fn sample_points() { let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); - let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 30., 0., 0., false, &FutureWrapperNode(vec![100.])).await; + let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await; let sample_points = sample_points.one_item(); assert_eq!(sample_points.point_domain.positions().len(), 4); for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) { @@ -1400,7 +1131,7 @@ mod test { #[tokio::test] async fn adaptive_spacing() { let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); - let sample_points = super::sample_points(Footprint::default(), &vector_node(path), 18., 45., 10., true, &FutureWrapperNode(vec![100.])).await; + let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await; let sample_points = sample_points.one_item(); assert_eq!(sample_points.point_domain.positions().len(), 4); for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) { @@ -1411,7 +1142,7 @@ mod test { async fn poisson() { let sample_points = super::poisson_disk_points( Footprint::default(), - &vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)), + vector_node(Subpath::new_ellipse(DVec2::NEG_ONE * 50., DVec2::ONE * 50.)), 10. * std::f64::consts::SQRT_2, 0, ) @@ -1429,12 +1160,12 @@ mod test { #[tokio::test] async fn lengths() { let subpath = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); - let lengths = subpath_segment_lengths(Footprint::default(), &vector_node(subpath)).await; + let lengths = subpath_segment_lengths(Footprint::default(), vector_node(subpath)).await; assert_eq!(lengths, vec![100.]); } #[tokio::test] async fn spline() { - let spline = super::spline(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await; + let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await; let spline = spline.one_item(); assert_eq!(spline.stroke_bezier_paths().count(), 1); assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]); @@ -1443,7 +1174,7 @@ mod test { async fn morph() { let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO); - let sample_points = super::morph(Footprint::default(), &vector_node(source), &vector_node(target), 0.5, 0).await; + let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await; let sample_points = sample_points.one_item(); assert_eq!( &sample_points.point_domain.positions()[..4], @@ -1452,7 +1183,7 @@ mod test { } #[track_caller] - fn contains_segment(vector: &VectorData, target: bezier_rs::Bezier) { + fn contains_segment(vector: VectorData, target: bezier_rs::Bezier) { let segments = vector.segment_bezier_iter().map(|x| x.1); let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count(); assert_eq!(count, 1, "Incorrect number of {target:#?} in {:#?}", vector.segment_bezier_iter().collect::>()); @@ -1461,42 +1192,42 @@ mod test { #[tokio::test] async fn bevel_rect() { let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.); - let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await; + let beveled = super::bevel(Footprint::default(), vector_node(source), 5.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 8); assert_eq!(beveled.segment_domain.ids().len(), 8); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(95., 0.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(95., 100.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 5.), DVec2::new(0., 95.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(95., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(95., 100.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 5.), DVec2::new(0., 95.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 5.), DVec2::new(100., 95.))); // Joins - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(0., 5.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(95., 0.), DVec2::new(100., 5.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 95.), DVec2::new(95., 100.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 0.), DVec2::new(0., 5.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(95., 0.), DVec2::new(100., 5.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 95.), DVec2::new(95., 100.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(5., 100.), DVec2::new(0., 95.))); } #[tokio::test] async fn bevel_open_curve() { let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.); let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false); - let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await; + let beveled = super::bevel((), vector_node(source), 5.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 4); assert_eq!(beveled.segment_domain.ids().len(), 3); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-100., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-100., 0.))); let trimmed = curve.trim(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.)); - contains_segment(&beveled, trimmed); + contains_segment(beveled.clone(), trimmed); // Join - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start)); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start)); } #[tokio::test] @@ -1506,7 +1237,7 @@ mod test { let mut vector_data = VectorData::from_subpath(source); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.)); vector_data.transform = transform; - let beveled = super::bevel(Footprint::default(), &FutureWrapperNode(VectorDataTable::new(vector_data)), 5.).await; + let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 4); @@ -1514,31 +1245,31 @@ mod test { assert_eq!(beveled.transform, transform); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.))); let trimmed = curve.trim(bezier_rs::TValue::Euclidean(0.5 / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.)); - contains_segment(&beveled, trimmed); + contains_segment(beveled.clone(), trimmed); // Join - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start)); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start)); } #[tokio::test] async fn bevel_too_high() { let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false); - let beveled = super::bevel(Footprint::default(), &vector_node(source), 999.).await; + let beveled = super::bevel(Footprint::default(), vector_node(source), 999.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.segment_domain.ids().len(), 5); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 0.), DVec2::new(50., 0.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(100., 50.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(0., 0.), DVec2::new(50., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(100., 50.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.))); // Joins - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(50., 0.), DVec2::new(100., 50.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(50., 0.), DVec2::new(100., 50.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(100., 50.), DVec2::new(50., 100.))); } #[tokio::test] @@ -1546,18 +1277,18 @@ mod test { let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.); let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO); let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false); - let beveled = super::bevel(Footprint::default(), &vector_node(source), 5.).await; + let beveled = super::bevel(Footprint::default(), vector_node(source), 5.); let beveled = beveled.one_item(); assert_eq!(beveled.point_domain.positions().len(), 6); assert_eq!(beveled.segment_domain.ids().len(), 5); // Segments - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-100., 0.), DVec2::new(-5., 0.))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(0., 0.))); - contains_segment(&beveled, point); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-100., 0.), DVec2::new(-5., 0.))); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(0., 0.))); + contains_segment(beveled.clone(), point); let [start, end] = curve.split(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001)))); - contains_segment(&beveled, bezier_rs::Bezier::from_linear_dvec2(start.start, start.end)); - contains_segment(&beveled, end); + contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(start.start, start.end)); + contains_segment(beveled.clone(), end); } } diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index 107ef5e41..b876af99b 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -54,6 +54,7 @@ winit = { workspace = true } [dev-dependencies] # Workspace dependencies graph-craft = { workspace = true, features = ["loading"] } +pretty_assertions = { workspace = true } # Required dependencies criterion = { version = "0.5", features = ["html_reports"]} diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 8e8b2bfd1..65d1af8c5 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -304,7 +304,7 @@ impl DocumentNode { match first { NodeInput::Value { tagged_value, .. } => { assert_eq!(self.inputs.len(), 0, "A value node cannot have any inputs. Current inputs: {:?}", self.inputs); - (ProtoNodeInput::None, ConstructionArgs::Value(tagged_value)) + (ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context<'static>)), ConstructionArgs::Value(tagged_value)) } NodeInput::Node { node_id, output_index, lambda } => { assert_eq!(output_index, 0, "Outputs should be flattened before converting to proto node"); @@ -1567,7 +1567,7 @@ mod test { NodeId(14), ProtoNode { identifier: "graphene_core::value::ClonedNode".into(), - input: ProtoNodeInput::None, + input: ProtoNodeInput::ManualComposition(concrete!(graphene_core::Context)), construction_args: ConstructionArgs::Value(TaggedValue::U32(2).into()), original_location: OriginalLocation { path: Some(vec![NodeId(1), NodeId(4)]), @@ -1589,7 +1589,7 @@ mod test { println!("{:#?}", resolved_network[0]); println!("{construction_network:#?}"); - assert_eq!(resolved_network[0], construction_network); + pretty_assertions::assert_eq!(resolved_network[0], construction_network); } fn flat_network() -> NodeNetwork { diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 81034f915..d2b83c83d 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -102,8 +102,8 @@ macro_rules! tagged_value { }) } Type::Fn(_, output) => TaggedValue::from_type(output), - Type::Future(_) => { - None + Type::Future(output) => { + TaggedValue::from_type(output) } } } @@ -270,7 +270,7 @@ impl TaggedValue { Some(ty) } Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output), - Type::Future(_) => None, + Type::Future(fut) => TaggedValue::from_primitive_string(string, fut), } } diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 79d780e8f..cb71106c8 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -162,7 +162,7 @@ impl Default for ProtoNode { #[derive(Debug, PartialEq, Eq, Clone, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ProtoNodeInput { - /// [`ProtoNode`]s do not require any input, e.g. the value node just takes in [`ConstructionArgs`]. + /// This input will be converted to `()` as the call argument. None, /// A ManualComposition input represents an input that opts out of being resolved through the `ComposeNode`, which first runs the previous (upstream) node, then passes that evaluated /// result to this node. Instead, ManualComposition lets this node actually consume the provided input instead of passing it to its predecessor. @@ -203,6 +203,7 @@ impl ProtoNode { if self.skip_deduplication { self.original_location.path.hash(&mut hasher); } + std::mem::discriminant(&self.input).hash(&mut hasher); match self.input { ProtoNodeInput::None => (), @@ -212,6 +213,7 @@ impl ProtoNode { ProtoNodeInput::Node(id) => (id, false).hash(&mut hasher), ProtoNodeInput::NodeLambda(id) => (id, true).hash(&mut hasher), }; + Some(NodeId(hasher.finish())) } @@ -224,7 +226,7 @@ impl ProtoNode { Self { identifier: ProtoNodeIdentifier::new("graphene_core::value::ClonedNode"), construction_args: value, - input: ProtoNodeInput::None, + input: ProtoNodeInput::ManualComposition(concrete!(Context)), original_location: OriginalLocation { path: Some(path), inputs_exposed: vec![false; inputs_exposed], @@ -552,10 +554,11 @@ impl core::fmt::Debug for GraphErrorType { GraphErrorType::NoImplementations => write!(f, "No implementations found"), GraphErrorType::NoConstructor => write!(f, "No construct found for node"), GraphErrorType::InvalidImplementations { inputs, error_inputs } => { - let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}", index + 1); - let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::>().join("\n"); + let format_error = |(index, (_found, expected)): &(usize, (Type, Type))| format!("• Input {}: {expected}, found: {_found}", index + 1); + let format_error_list = |errors: &Vec<(usize, (Type, Type))>| errors.iter().map(format_error).collect::>().join("\n").replace("Option>", "Context"); let mut errors = error_inputs.iter().map(format_error_list).collect::>(); errors.sort(); + let inputs = inputs.replace("Option>", "Context"); write!( f, "This node isn't compatible with the com-\n\ @@ -651,9 +654,9 @@ impl TypingContext { let inputs = match node.construction_args { // If the node has a value input we can infer the return type from it ConstructionArgs::Value(ref v) => { - assert!(matches!(node.input, ProtoNodeInput::None)); + assert!(matches!(node.input, ProtoNodeInput::None) || matches!(node.input, ProtoNodeInput::ManualComposition(ref x) if x == &concrete!(Context))); // TODO: This should return a reference to the value - let types = NodeIOTypes::new(concrete!(()), v.ty(), vec![v.ty()]); + let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]); self.inferred.insert(node_id, types.clone()); return Ok(types); } @@ -696,6 +699,8 @@ impl TypingContext { match (from, to) { // Direct comparison of two concrete types. (Type::Concrete(type1), Type::Concrete(type2)) => type1 == type2, + // Check inner type for futures + (Type::Future(type1), Type::Future(type2)) => type1 == type2, // Loose comparison of function types, where loose means that functions are considered on a "greater than or equal to" basis of its function type's generality. // That means we compare their types with a contravariant relationship, which means that a more general type signature may be substituted for a more specific type signature. // For example, we allow `T -> V` to be substituted with `T' -> V` or `() -> V` where T' and () are more specific than T. diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index 43f5b4c22..7017b73c9 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -1,21 +1,23 @@ -use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, ExtendImageToBoundsNode}; +use crate::raster::{blend_image_closure, BlendImageTupleNode, ExtendImageToBoundsNode}; +use graph_craft::generic::FnNode; +use graph_craft::proto::FutureWrapperNode; use graphene_core::raster::adjustments::blend_colors; use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::BlendMode; -use graphene_core::raster::{Alpha, BlendColorPairNode, Color, Image, Pixel, Sample}; -use graphene_core::transform::{Footprint, Transform, TransformMut}; +use graphene_core::raster::{Alpha, Color, Image, Pixel, Sample}; +use graphene_core::transform::{Transform, TransformMut}; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; use graphene_core::vector::VectorDataTable; -use graphene_core::Node; +use graphene_core::{Ctx, Node}; use glam::{DAffine2, DVec2}; #[node_macro::node(category("Debug"))] -fn vector_points(_: (), vector_data: VectorDataTable) -> Vec { +fn vector_points(_: impl Ctx, vector_data: VectorDataTable) -> Vec { let vector_data = vector_data.one_item(); vector_data.point_domain.positions().to_vec() @@ -131,14 +133,21 @@ where target } -pub fn create_brush_texture(brush_style: &BrushStyle) -> Image { - let stamp = BrushStampGeneratorNode::new(CopiedNode::new(brush_style.color), CopiedNode::new(brush_style.hardness), CopiedNode::new(brush_style.flow)); - let stamp = stamp.eval(brush_style.diameter); +pub async fn create_brush_texture(brush_style: &BrushStyle) -> Image { + let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.)); - let blank_texture = EmptyImageNode::new(CopiedNode::new(transform), CopiedNode::new(Color::TRANSPARENT)).eval(()); - let normal_blend = BlendColorPairNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)); - let blend_executor = BlendImageTupleNode::new(ValueNode::new(normal_blend)); - blend_executor.eval((blank_texture, stamp)).image + use crate::raster::empty_image; + let blank_texture = empty_image((), transform, Color::TRANSPARENT); + // let normal_blend = BlendColorPairNode::new( + // FutureWrapperNode::new(ValueNode::new(CopiedNode::new(BlendMode::Normal))), + // FutureWrapperNode::new(ValueNode::new(CopiedNode::new(100.))), + // ); + // normal_blend.eval((Color::default(), Color::default())); + // use crate::raster::blend_image_tuple; + // blend_image_tuple((blank_texture, stamp), &normal_blend).await.image; + crate::raster::blend_image_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.)).image + // let blend_executoc = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(normal_blend))); + // blend_executor.eval((blank_texture, stamp)).image } macro_rules! inline_blend_funcs { @@ -202,7 +211,7 @@ pub fn blend_with_mode(background: ImageFrame, foreground: ImageFrame, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { +async fn brush(_: impl Ctx, image: ImageFrameTable, bounds: ImageFrameTable, strokes: Vec, cache: BrushCache) -> ImageFrameTable { let image = image.one_item().clone(); let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); @@ -225,11 +234,13 @@ fn brush(_: Footprint, image: ImageFrameTable, bounds: ImageFrameTable, bounds: ImageFrameTable, bounds: ImageFrameTable = stroke.compute_blit_points().into_iter().collect(); match stroke.style.blend_mode { BlendMode::Erase => { - let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Erase), CopiedNode::new(100.)); - let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params)); - erase_restore_mask = blit_node.eval(erase_restore_mask); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Erase, 1.)); + let blit_node = BlitNode::new( + FutureWrapperNode::new(ClonedNode::new(brush_texture)), + FutureWrapperNode::new(ClonedNode::new(positions)), + FutureWrapperNode::new(ClonedNode::new(blend_params)), + ); + erase_restore_mask = blit_node.eval(erase_restore_mask).await; } // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. BlendMode::Restore => { - let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::Restore), CopiedNode::new(100.)); - let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(blend_params)); - erase_restore_mask = blit_node.eval(erase_restore_mask); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::Restore, 1.)); + let blit_node = BlitNode::new( + FutureWrapperNode::new(ClonedNode::new(brush_texture)), + FutureWrapperNode::new(ClonedNode::new(positions)), + FutureWrapperNode::new(ClonedNode::new(blend_params)), + ); + erase_restore_mask = blit_node.eval(erase_restore_mask).await; } _ => unreachable!(), } } - let blend_params = BlendColorPairNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.)); - let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params)); - actual_image = blend_executor.eval((actual_image, erase_restore_mask)); + let blend_params = FnNode::new(|(a, b)| blend_colors(a, b, BlendMode::MultiplyAlpha, 1.)); + let blend_executor = BlendImageTupleNode::new(FutureWrapperNode::new(ValueNode::new(blend_params))); + actual_image = blend_executor.eval((actual_image, erase_restore_mask)).await; } ImageFrameTable::new(actual_image) @@ -315,15 +343,13 @@ mod test { use super::*; use graphene_core::transform::Transform; - use graphene_core::value::ClonedNode; use glam::DAffine2; #[test] fn test_brush_texture() { - let brush_texture_node = BrushStampGeneratorNode::new(ClonedNode::new(Color::BLACK), ClonedNode::new(100.), ClonedNode::new(100.)); let size = 20.; - let image = brush_texture_node.eval(size); + let image = brush_stamp_generator(size, Color::BLACK, 100., 100.); assert_eq!(image.transform(), DAffine2::from_scale_angle_translation(DVec2::splat(size.ceil()), 0., -DVec2::splat(size / 2.))); // center pixel should be BLACK assert_eq!(image.sample(DVec2::splat(0.), DVec2::ONE), Some(Color::BLACK)); diff --git a/node-graph/gstd/src/dehaze.rs b/node-graph/gstd/src/dehaze.rs index 647983104..20d942d2f 100644 --- a/node-graph/gstd/src/dehaze.rs +++ b/node-graph/gstd/src/dehaze.rs @@ -1,28 +1,14 @@ use graph_craft::proto::types::Percentage; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::Image; -use graphene_core::transform::Footprint; -use graphene_core::Color; +use graphene_core::{Color, Ctx}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; use ndarray::{Array2, ArrayBase, Dim, OwnedRepr}; use std::cmp::{max, min}; -#[node_macro::node(category("Raster: Filter"))] -async fn dehaze( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> ImageFrameTable, - Footprint -> ImageFrameTable, - )] - image_frame: impl Node>, - strength: Percentage, -) -> ImageFrameTable { - let image_frame = image_frame.eval(footprint).await; +#[node_macro::node(category("Raster"))] +async fn dehaze(_: impl Ctx, image_frame: ImageFrameTable, strength: Percentage) -> ImageFrameTable { let image_frame = image_frame.one_item(); // Prepare the image data for processing diff --git a/node-graph/gstd/src/gpu_nodes.rs b/node-graph/gstd/src/gpu_nodes.rs index c30ddf015..4e2233f33 100644 --- a/node-graph/gstd/src/gpu_nodes.rs +++ b/node-graph/gstd/src/gpu_nodes.rs @@ -19,7 +19,7 @@ use crate::wasm_application_io::WasmApplicationIo; // TODO: Move to graph-craft #[node_macro::node(category("Debug: GPU"))] -async fn compile_gpu<'a: 'n>(_: (), node: &'a DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result { +async fn compile_gpu<'a: 'n>(_: impl Ctx, node: &'a DocumentNode, typing_context: TypingContext, io: ShaderIO) -> Result { let mut typing_context = typing_context; let compiler = graph_craft::graphene_compiler::Compiler {}; let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() }; @@ -279,7 +279,7 @@ where } #[node_macro::node(category("Debug: GPU"))] -async fn blend_gpu_image(_: (), foreground: ImageFrameTable, background: ImageFrameTable, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable { +async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable, background: ImageFrameTable, blend_mode: BlendMode, opacity: f64) -> ImageFrameTable { let foreground = foreground.one_item(); let background = background.one_item(); diff --git a/node-graph/gstd/src/http.rs b/node-graph/gstd/src/http.rs index 26c6fab0f..a90af0ba9 100644 --- a/node-graph/gstd/src/http.rs +++ b/node-graph/gstd/src/http.rs @@ -1,9 +1,11 @@ +use graphene_core::Ctx; + #[node_macro::node(category("Network"))] -async fn get_request(_: (), url: String) -> reqwest::Response { +async fn get_request(_: impl Ctx, url: String) -> reqwest::Response { reqwest::get(url).await.unwrap() } #[node_macro::node(category("Network"))] -async fn post_request(_: (), url: String, body: String) -> reqwest::Response { +async fn post_request(_: impl Ctx, url: String, body: String) -> reqwest::Response { reqwest::Client::new().post(url).body(body).send().await.unwrap() } diff --git a/node-graph/gstd/src/image_color_palette.rs b/node-graph/gstd/src/image_color_palette.rs index 03ac5245f..a554c9ee1 100644 --- a/node-graph/gstd/src/image_color_palette.rs +++ b/node-graph/gstd/src/image_color_palette.rs @@ -1,19 +1,10 @@ use graphene_core::raster::image::ImageFrameTable; -use graphene_core::transform::Footprint; -use graphene_core::Color; +use graphene_core::{Color, Ctx}; #[node_macro::node(category("Raster"))] -async fn image_color_palette( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> ImageFrameTable, - Footprint -> ImageFrameTable, - )] - image: impl Node>, +async fn image_color_palette( + _: impl Ctx, + image: ImageFrameTable, #[min(1.)] #[max(28.)] max_size: u32, @@ -25,7 +16,6 @@ async fn image_color_palette( let mut histogram: Vec = vec![0; (bins + 1.) as usize]; let mut colors: Vec> = vec![vec![]; (bins + 1.) as usize]; - let image = image.eval(footprint).await; let image = image.one_item(); for pixel in image.image.data.iter() { @@ -75,30 +65,24 @@ async fn image_color_palette( mod test { use super::*; - use graph_craft::generic::FnNode; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::Image; - use graphene_core::value::CopiedNode; - use graphene_core::Node; #[test] fn test_image_color_palette() { - let node = ImageColorPaletteNode { - max_size: CopiedNode(1u32), - image: FnNode::new(|_| { - Box::pin(async move { - ImageFrameTable::new(ImageFrame { - image: Image { - width: 100, - height: 100, - data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], - base64_string: None, - }, - ..Default::default() - }) - }) + let result = image_color_palette( + (), + ImageFrameTable::new(ImageFrame { + image: Image { + width: 100, + height: 100, + data: vec![Color::from_rgbaf32(0., 0., 0., 1.).unwrap(); 10000], + base64_string: None, + }, + ..Default::default() }), - }; - assert_eq!(futures::executor::block_on(node.eval(())), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]); + 1, + ); + assert_eq!(futures::executor::block_on(result), [Color::from_rgbaf32(0., 0., 0., 1.).unwrap()]); } } diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index ed4a9b744..39cbf95ef 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -2,10 +2,11 @@ use dyn_any::DynAny; use graphene_core::raster::bbox::Bbox; use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; use graphene_core::raster::{ - Alpha, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, Sample, + Alpha, AlphaMut, Bitmap, BitmapMut, CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, Image, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, RedGreenBlue, + Sample, }; -use graphene_core::transform::{Footprint, Transform}; -use graphene_core::{AlphaBlending, Color, Node}; +use graphene_core::transform::Transform; +use graphene_core::{AlphaBlending, Color, Ctx, ExtractFootprint, Node}; use fastnoise_lite; use glam::{DAffine2, DVec2, Vec2}; @@ -28,13 +29,14 @@ impl From for Error { } #[node_macro::node(category("Debug: Raster"))] -fn sample_image(footprint: Footprint, image_frame: ImageFrameTable) -> ImageFrameTable { +fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFrameTable) -> ImageFrameTable { let image_frame = image_frame.one_item(); // Resize the image using the image crate let image = &image_frame.image; let data = bytemuck::cast_vec(image.data.clone()); + let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); let image_bounds = Bbox::from_transform(image_frame.transform).to_axis_aligned_bbox(); let intersection = viewport_bounds.intersect(&image_bounds); @@ -107,15 +109,7 @@ where image } -#[derive(Debug, Clone, Copy)] -pub struct InsertChannelNode { - insertion: Insertion, - target_channel: TargetChannel, - _p: PhantomData

, - _s: PhantomData, -} - -#[node_macro::old_node_fn(InsertChannelNode<_P, _S>)] +#[node_macro::node] fn insert_channel< // _P is the color of the input image. _P: RGBMut, @@ -124,8 +118,9 @@ fn insert_channel< Input: BitmapMut, Insertion: Bitmap, >( - mut image: Input, - insertion: Insertion, + _: impl Ctx, + #[implementations(ImageFrameTable)] mut image: Input, + #[implementations(ImageFrameTable)] insertion: Insertion, target_channel: RedGreenBlue, ) -> Input where @@ -154,15 +149,60 @@ where image } +#[node_macro::node] +fn combine_channels< + // _P is the color of the input image. + _P: RGBMut + AlphaMut, + _S: Pixel + Luminance, + // Input image + Input: BitmapMut, + Red: Bitmap, + Green: Bitmap, + Blue: Bitmap, + Alpha: Bitmap, +>( + _: impl Ctx, + #[implementations(ImageFrameTable)] mut image: Input, + #[implementations(ImageFrameTable)] red: Red, + #[implementations(ImageFrameTable)] green: Green, + #[implementations(ImageFrameTable)] blue: Blue, + #[implementations(ImageFrameTable)] alpha: Alpha, +) -> Input +where + _P::ColorChannel: Linear, +{ + let dimensions = [red.dim(), green.dim(), blue.dim(), alpha.dim()]; + if dimensions.iter().all(|&(x, _)| x == 0) { + return image; + } -#[derive(Debug, Clone, Copy)] -pub struct MaskImageNode { - stencil: Stencil, - _p: PhantomData

, - _s: PhantomData, + if dimensions.iter().any(|&(x, y)| x != image.width() || y != image.height()) { + log::warn!("Stencil and image have different sizes. This is not supported."); + return image; + } + + for y in 0..image.height() { + for x in 0..image.width() { + let image_pixel = image.get_pixel_mut(x, y).unwrap(); + if let Some(r) = red.get_pixel(x, y) { + image_pixel.set_red(r.l().cast_linear_channel()); + } + if let Some(g) = green.get_pixel(x, y) { + image_pixel.set_green(g.l().cast_linear_channel()); + } + if let Some(b) = blue.get_pixel(x, y) { + image_pixel.set_blue(b.l().cast_linear_channel()); + } + if let Some(a) = alpha.get_pixel(x, y) { + image_pixel.set_alpha(a.l().cast_linear_channel()); + } + } + } + + image } -#[node_macro::old_node_fn(MaskImageNode<_P, _S>)] +#[node_macro::node()] fn mask_image< // _P is the color of the input image. It must have an alpha channel because that is going to // be modified by the mask @@ -175,8 +215,9 @@ fn mask_image< // Stencil Stencil: Transform + Sample, >( - mut image: Input, - stencil: Stencil, + _: impl Ctx, + #[implementations(ImageFrameTable)] mut image: Input, + #[implementations(ImageFrameTable)] stencil: Stencil, ) -> Input { let image_size = DVec2::new(image.width() as f64, image.height() as f64); let mask_size = stencil.transform().decompose_scale(); @@ -207,17 +248,17 @@ fn mask_image< image } -#[derive(Debug, Clone, Copy)] -pub struct BlendImageTupleNode { - map_fn: MapFn, - _p: PhantomData

, - _fg: PhantomData, -} +// #[derive(Debug, Clone, Copy)] +// pub struct BlendImageTupleNode { +// map_fn: MapFn, +// _p: PhantomData

, +// _fg: PhantomData, +// } -#[node_macro::old_node_fn(BlendImageTupleNode<_P, _Fg>)] -fn blend_image_tuple<_P: Alpha + Pixel + Debug, MapFn, _Fg: Sample + Transform>(images: (ImageFrame<_P>, _Fg), map_fn: &'input MapFn) -> ImageFrame<_P> +#[node_macro::node(skip_impl)] +async fn blend_image_tuple<_P: Alpha + Pixel + Debug + Send, MapFn, _Fg: Sample + Transform + Clone + Send + 'n>(images: (ImageFrame<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrame<_P> where - MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'input + Clone, + MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone, { let (background, foreground) = images; @@ -319,7 +360,7 @@ fn extend_image_to_bounds(image: ImageFrame, bounds: DAffine2) -> ImageFr } #[node_macro::node(category("Debug: Raster"))] -fn empty_image(_: (), transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame

{ +fn empty_image(_: impl Ctx, transform: DAffine2, #[implementations(Color)] color: P) -> ImageFrame

{ let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32; @@ -431,10 +472,10 @@ fn empty_image(_: (), transform: DAffine2, #[implementations(Color)] c // tiling: Tiling: bool, // } -#[node_macro::node(category("Raster: Generator"))] +#[node_macro::node(category("Raster"))] #[allow(clippy::too_many_arguments)] fn noise_pattern( - footprint: Footprint, + ctx: impl ExtractFootprint + Ctx, _primary: (), clip: bool, seed: u32, @@ -452,6 +493,7 @@ fn noise_pattern( cellular_return_type: CellularReturnType, cellular_jitter: f64, ) -> ImageFrameTable { + let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); let mut size = viewport_bounds.size(); @@ -585,8 +627,9 @@ fn noise_pattern( ImageFrameTable::new(result) } -#[node_macro::node(category("Raster: Generator"))] -fn mandelbrot(footprint: Footprint) -> ImageFrameTable { +#[node_macro::node(category("Raster"))] +fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable { + let footprint = ctx.footprint(); let viewport_bounds = footprint.viewport_bounds_in_local_space(); let image_bounds = Bbox::from_transform(DAffine2::IDENTITY).to_axis_aligned_bbox(); diff --git a/node-graph/gstd/src/text.rs b/node-graph/gstd/src/text.rs index 11a7b49bb..3139307cc 100644 --- a/node-graph/gstd/src/text.rs +++ b/node-graph/gstd/src/text.rs @@ -3,10 +3,11 @@ use crate::vector::{VectorData, VectorDataTable}; use graph_craft::wasm_application_io::WasmEditorApi; use graphene_core::text::TypesettingConfig; pub use graphene_core::text::{bounding_box, load_face, to_path, Font, FontCache}; +use graphene_core::Ctx; #[node_macro::node(category(""))] fn text<'i: 'n>( - _: (), + _: impl Ctx, editor: &'i WasmEditorApi, text: String, font_name: Font, diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index 852e498b6..aab0fffa7 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -1,11 +1,9 @@ -use crate::transform::Footprint; - use bezier_rs::{ManipulatorGroup, Subpath}; use graphene_core::vector::misc::BooleanOperation; use graphene_core::vector::style::Fill; pub use graphene_core::vector::*; use graphene_core::{transform::Transform, GraphicGroup}; -use graphene_core::{Color, GraphicElement, GraphicGroupTable}; +use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable}; pub use path_bool as path_bool_lib; use path_bool::{FillRule, PathBooleanOperation}; @@ -13,22 +11,7 @@ use glam::{DAffine2, DVec2}; use std::ops::Mul; #[node_macro::node(category(""))] -async fn boolean_operation( - #[implementations( - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> GraphicGroupTable, - Footprint -> GraphicGroupTable, - )] - group_of_paths: impl Node, - operation: BooleanOperation, -) -> VectorDataTable { - let group_of_paths = group_of_paths.eval(footprint).await; - let group_of_paths = group_of_paths.one_item(); - +async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, operation: BooleanOperation) -> VectorDataTable { fn vector_from_image(image_frame: T) -> VectorData { let corner1 = DVec2::ZERO; let corner2 = DVec2::new(1., 1.); @@ -193,6 +176,7 @@ async fn boolean_operation( } } + let group_of_paths = group_of_paths.one_item(); // The first index is the bottom of the stack let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(group_of_paths), operation); diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index a9d6c3263..967ce363c 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -12,8 +12,7 @@ use graphene_core::renderer::RenderMetadata; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::transform::Footprint; use graphene_core::vector::VectorDataTable; -use graphene_core::GraphicGroupTable; -use graphene_core::{Color, WasmNotSend}; +use graphene_core::{Color, Context, Ctx, ExtractFootprint, GraphicGroupTable, OwnedContextImpl, WasmNotSend}; #[cfg(target_arch = "wasm32")] use base64::Engine; @@ -27,7 +26,7 @@ use wasm_bindgen::JsCast; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; #[node_macro::node(category("Debug: GPU"))] -async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc { +async fn create_surface<'a: 'n>(_: impl Ctx, editor: &'a WasmEditorApi) -> Arc { Arc::new(editor.application_io.as_ref().unwrap().create_window()) } @@ -37,7 +36,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc, // surface_handle: Arc, // ) -> graphene_core::application_io::SurfaceHandleFrame { @@ -60,7 +59,7 @@ async fn create_surface<'a: 'n>(_: (), editor: &'a WasmEditorApi) -> Arc(_: (), _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> { +async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")] editor: &'a WasmEditorApi, #[name("URL")] url: String) -> Arc<[u8]> { let Some(api) = editor.application_io.as_ref() else { return Arc::from(include_bytes!("../../graph-craft/src/null.png").to_vec()); }; @@ -74,8 +73,8 @@ async fn load_resource<'a: 'n>(_: (), _primary: (), #[scope("editor-api")] edito data } -#[node_macro::node(category("Raster"))] -fn decode_image(_: (), data: Arc<[u8]>) -> ImageFrameTable { +#[node_macro::node(category("Network"))] +fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable { let Some(image) = image::load_from_memory(data.as_ref()).ok() else { return ImageFrameTable::default(); }; @@ -156,13 +155,13 @@ async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRen #[node_macro::node(category(""))] #[cfg(target_arch = "wasm32")] async fn rasterize( - _: (), + _: impl Ctx, #[implementations( - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - Footprint -> GraphicGroupTable, + VectorDataTable, + ImageFrameTable, + GraphicGroupTable, )] - data: impl Node, + mut data: T, footprint: Footprint, surface_handle: Arc>, ) -> ImageFrameTable { @@ -171,7 +170,6 @@ async fn rasterize( render_config: RenderConfig, - editor_api: &'a WasmEditorApi, + editor_api: impl Node, Output = &'a WasmEditorApi>, #[implementations( - Footprint -> VectorDataTable, - Footprint -> ImageFrameTable, - Footprint -> GraphicGroupTable, - Footprint -> graphene_core::Artboard, - Footprint -> graphene_core::ArtboardGroup, - Footprint -> Option, - Footprint -> Vec, - Footprint -> bool, - Footprint -> f32, - Footprint -> f64, - Footprint -> String, + Context -> VectorDataTable, + Context -> ImageFrameTable, + Context -> GraphicGroupTable, + Context -> graphene_core::Artboard, + Context -> graphene_core::ArtboardGroup, + Context -> Option, + Context -> Vec, + Context -> bool, + Context -> f32, + Context -> f64, + Context -> String, )] - data: impl Node, - _surface_handle: impl Node<(), Output = Option>, + data: impl Node, Output = T>, + _surface_handle: impl Node, Output = Option>, ) -> RenderOutput { let footprint = render_config.viewport; + let ctx = OwnedContextImpl::default().with_footprint(footprint).into_context(); + ctx.footprint(); let RenderConfig { hide_artboards, for_export, .. } = render_config; let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); - let data = data.eval(footprint).await; + let data = data.eval(ctx.clone()).await; + let editor_api = editor_api.eval(ctx.clone()).await; #[cfg(all(feature = "vello", target_arch = "wasm32"))] - let surface_handle = _surface_handle.eval(()).await; + let surface_handle = _surface_handle.eval(ctx.clone()).await; let use_vello = editor_api.editor_preferences.use_vello(); #[cfg(all(feature = "vello", target_arch = "wasm32"))] let use_vello = use_vello && surface_handle.is_some(); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index c37c0eb2c..85ab9d23b 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -2,74 +2,29 @@ use dyn_any::StaticType; use graph_craft::document::value::RenderOutput; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_core::fn_type; -use graphene_core::ops::IdentityNode; use graphene_core::raster::color::Color; -use graphene_core::raster::image::{ImageFrame, ImageFrameTable}; +use graphene_core::raster::image::ImageFrameTable; use graphene_core::raster::*; -use graphene_core::structural::Then; -use graphene_core::transform::Footprint; use graphene_core::value::{ClonedNode, ValueNode}; use graphene_core::vector::VectorDataTable; use graphene_core::{concrete, generic, Artboard, GraphicGroupTable}; +use graphene_core::{fn_type_fut, future}; use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::application_io::TextureFrame; -use graphene_std::raster::*; use graphene_std::wasm_application_io::*; +use graphene_std::Context; use graphene_std::{GraphicElement, GraphicGroup}; #[cfg(feature = "gpu")] use wgpu_executor::{ShaderInputFrame, WgpuExecutor}; use wgpu_executor::{WgpuSurface, WindowHandle}; -use glam::{DAffine2, DVec2, UVec2}; +use glam::{DVec2, UVec2}; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Arc; -macro_rules! construct_node { - ($args: ident, $path:ty, [$($arg:ty => $type:ty),*]) => { async move { - let mut args = $args.clone(); - args.reverse(); - let node = <$path>::new($( - { - let node = graphene_std::any::downcast_node::<$arg, $type>(args.pop().expect("Not enough arguments provided to construct node")); - let value = node.eval(()).await; - graphene_core::value::ClonedNode::new(value) - } - ),* - ); - node - }} -} - -macro_rules! register_node { - ($path:ty, input: $input:ty, params: [ $($type:ty),*]) => { - register_node!($path, input: $input, fn_params: [ $(() => $type),*]) - }; - ($path:ty, input: $input:ty, fn_params: [ $($arg:ty => $type:ty),*]) => { - ( - ProtoNodeIdentifier::new(stringify!($path)), - |args| { - Box::pin(async move { - let node = construct_node!(args, $path, [$($arg => $type),*]).await; - let node = graphene_std::any::FutureWrapperNode::new(node); - let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(node); - Box::new(any) as TypeErasedBox - }) - }, - { - let node = <$path>::new($( - graphene_std::any::PanicNode::<(), $type>::new() - ),*); - let params = vec![$(fn_type!((), $type)),*]; - let mut node_io = <$path as NodeIO<'_, $input>>::to_node_io(&node, params); - node_io.call_argument = concrete!(<$input as StaticType>::Static); - node_io - }, - ) - }; -} macro_rules! async_node { // TODO: we currently need to annotate the type here because the compiler would otherwise (correctly) // TODO: assign a Pin>> type to the node, which is not what we want for now. @@ -95,7 +50,7 @@ macro_rules! async_node { ),*); // TODO: Propagate the future type through the node graph // let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*]; - let params = vec![$(fn_type!($arg, $type)),*]; + let params = vec![$(fn_type_fut!($arg, $type)),*]; let mut node_io = NodeIO::<'_, $input>::to_async_node_io(&node, params); node_io.call_argument = concrete!(<$input as StaticType>::Static); node_io @@ -107,11 +62,11 @@ macro_rules! async_node { // TODO: turn into hashmap fn node_registry() -> HashMap> { let node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![ - ( - ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), - |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }), - NodeIOTypes::new(generic!(I), generic!(I), vec![]), - ), + // ( + // ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), + // |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }), + // NodeIOTypes::new(generic!(I), generic!(I), vec![]), + // ), // async_node!(graphene_core::ops::IntoNode>, input: ImageFrameTable, params: []), // async_node!(graphene_core::ops::IntoNode>, input: ImageFrameTable, params: []), async_node!(graphene_core::ops::IntoNode, input: ImageFrameTable, params: []), @@ -123,78 +78,31 @@ fn node_registry() -> HashMap, input: GraphicGroupTable, params: []), async_node!(graphene_core::ops::IntoNode, input: VectorDataTable, params: []), async_node!(graphene_core::ops::IntoNode, input: ImageFrameTable, params: []), - register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable, params: [ImageFrameTable]), - register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable, params: [ImageFrameTable, RedGreenBlue]), - // register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrameTable, params: [ImageFrameTable]), - // register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrameTable, params: [ImageFrameTable, RedGreenBlue]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageFrameTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => TextureFrame]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]), + #[cfg(feature = "gpu")] ( - ProtoNodeIdentifier::new("graphene_std::raster::CombineChannelsNode"), + ProtoNodeIdentifier::new(stringify!(wgpu_executor::CreateGpuSurfaceNode<_>)), |args| { Box::pin(async move { - use graphene_core::raster::*; - use graphene_core::value::*; - - let channel_r: ImageFrameTable = DowncastBothNode::new(args[0].clone()).eval(()).await; - let channel_g: ImageFrameTable = DowncastBothNode::new(args[1].clone()).eval(()).await; - let channel_b: ImageFrameTable = DowncastBothNode::new(args[2].clone()).eval(()).await; - let channel_a: ImageFrameTable = DowncastBothNode::new(args[3].clone()).eval(()).await; - - let insert_r = InsertChannelNode::new(ClonedNode::new(channel_r.clone()), CopiedNode::new(RedGreenBlue::Red)); - let insert_g = InsertChannelNode::new(ClonedNode::new(channel_g.clone()), CopiedNode::new(RedGreenBlue::Green)); - let insert_b = InsertChannelNode::new(ClonedNode::new(channel_b.clone()), CopiedNode::new(RedGreenBlue::Blue)); - let complete_node = insert_r.then(insert_g).then(insert_b); - let complete_node = complete_node.then(MaskImageNode::new(ClonedNode::new(channel_a.clone()))); - - let channel_r = channel_r.one_item(); - let channel_g = channel_g.one_item(); - let channel_b = channel_b.one_item(); - let channel_a = channel_a.one_item(); - - // TODO: Move to FN Node for better performance - let (mut transform, mut bounds) = (DAffine2::ZERO, glam::UVec2::ZERO); - for image in [channel_a, channel_r, channel_g, channel_b] { - if image.image.width() > bounds.x { - bounds = glam::UVec2::new(image.image.width(), image.image.height()); - transform = image.transform; - } - } - let empty_image = ImageFrame { - image: Image::new(bounds.x, bounds.y, Color::BLACK), - transform, - alpha_blending: Default::default(), - }; - let empty_image = ImageFrameTable::new(empty_image); - let final_image = ClonedNode::new(empty_image).then(complete_node); - let final_image = FutureWrapperNode::new(final_image); - - let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(final_image); - any.into_type_erased() + let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[0].clone()); + let node = >::new(editor_api); + let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(node); + Box::new(any) as TypeErasedBox }) }, - NodeIOTypes::new( - concrete!(()), - concrete!(ImageFrameTable), - vec![ - fn_type!(ImageFrameTable), - fn_type!(ImageFrameTable), - fn_type!(ImageFrameTable), - fn_type!(ImageFrameTable), - ], - ), + { + let node = >::new(graphene_std::any::PanicNode::>::new()); + let params = vec![fn_type_fut!(Context, &WasmEditorApi)]; + let mut node_io = as NodeIO<'_, Context>>::to_async_node_io(&node, params); + node_io.call_argument = concrete!(::Static); + node_io + }, ), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [ImageFrameTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), params: [TextureFrame]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => VectorDataTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroupTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicGroupTable]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: (), fn_params: [() => GraphicElement]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, fn_params: [Footprint => Artboard]), - #[cfg(feature = "gpu")] - register_node!(wgpu_executor::CreateGpuSurfaceNode<_>, input: (), params: [&WasmEditorApi]), #[cfg(feature = "gpu")] ( ProtoNodeIdentifier::new("graphene_std::executor::MapGpuSingleImageNode"), @@ -202,7 +110,6 @@ fn node_registry() -> HashMap = DowncastBothNode::new(args[0].clone()); let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone()); - // let document_node = ClonedNode::new(document_node.eval(())); let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(node); any.into_type_erased() @@ -279,27 +186,27 @@ fn node_registry() -> HashMap), concrete!(ImageFrameTable), vec![fn_type!(graphene_core::raster::curve::Curve)]), // ), // TODO: Use channel split and merge for this instead of using LuminanceMut for the whole color. - ( - ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), - |args| { - use graphene_core::raster::{curve::Curve, GenerateCurvesNode}; - let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone()); - Box::pin(async move { - let curve = ClonedNode::new(curve.eval(()).await); + // ( + // ProtoNodeIdentifier::new("graphene_core::raster::CurvesNode"), + // |args| { + // use graphene_core::raster::{curve::Curve, GenerateCurvesNode}; + // let curve: DowncastBothNode<(), Curve> = DowncastBothNode::new(args[0].clone()); + // Box::pin(async move { + // let curve = ValueNode::new(ClonedNode::new(curve.eval(()).await)); - let generate_curves_node = GenerateCurvesNode::new(curve, ClonedNode::new(0_f32)); - let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_curves_node.eval(()))); - let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); - let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); - any.into_type_erased() - }) - }, - NodeIOTypes::new( - concrete!(ImageFrameTable), - concrete!(ImageFrameTable), - vec![fn_type!(graphene_core::raster::curve::Curve)], - ), - ), + // let generate_curves_node = GenerateCurvesNode::new(FutureWrapperNode::new(curve), FutureWrapperNode::new(ClonedNode::new(0_f32))); + // let map_image_frame_node = graphene_std::raster::MapImageNode::new(FutureWrapperNode::new(ValueNode::new(generate_curves_node.eval(())))); + // let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); + // let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(map_image_frame_node); + // any.into_type_erased() + // }) + // }, + // NodeIOTypes::new( + // concrete!(ImageFrameTable), + // concrete!(ImageFrameTable), + // vec![fn_type!(graphene_core::raster::curve::Curve)], + // ), + // ), // ( // ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode"), // |args: Vec| { @@ -337,44 +244,31 @@ fn node_registry() -> HashMap, input: (), params: [Image]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [VectorDataTable]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ImageFrameTable]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Vec]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Arc]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [WindowHandle]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ImageFrameTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => GraphicGroupTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Arc]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [ShaderInputFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ShaderInputFrame]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WgpuSurface]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [wgpu_executor::WindowHandle]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [graphene_std::SurfaceFrame]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: (), params: [RenderOutput]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Image]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ImageFrameTable]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Vec]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Arc]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => WindowHandle]), - #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]), - #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => wgpu_executor::WgpuSurface]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => wgpu_executor::WindowHandle]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => graphene_std::SurfaceFrame]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WgpuSurface]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => wgpu_executor::WindowHandle]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: UVec2, fn_params: [UVec2 => graphene_std::SurfaceFrame]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Footprint, fn_params: [Footprint => RenderOutput]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicElement]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => GraphicGroup]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => VectorDataTable]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicElement]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroup]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => VectorDataTable]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => GraphicGroupTable]), #[cfg(feature = "gpu")] - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => ShaderInputFrame]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => WgpuSurface]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => Option]), - async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Footprint, fn_params: [Footprint => TextureFrame]), - register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => ShaderInputFrame]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => TextureFrame]), ]; let mut map: HashMap> = HashMap::new(); for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() { diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index dbf080faa..4e88b41a5 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -1,13 +1,12 @@ use std::sync::Arc; -use graph_craft::{ - concrete, - document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}, - generic, - wasm_application_io::WasmEditorApi, - ProtoNodeIdentifier, -}; -use graphene_std::{transform::Footprint, uuid::NodeId}; +use graph_craft::concrete; +use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}; +use graph_craft::generic; +use graph_craft::wasm_application_io::WasmEditorApi; +use graph_craft::ProtoNodeIdentifier; +use graphene_std::uuid::NodeId; +use graphene_std::Context; // TODO: this is copy pasta from the editor (and does get out of sync) pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc) -> NodeNetwork { @@ -27,18 +26,18 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc syn::Result = fields.iter().enumerate().map(|(i, _)| format_ident!("Node{}", i)).collect(); let input_ident = &input.pat_ident; - let input_type = &input.ty; let field_idents: Vec<_> = fields .iter() @@ -78,12 +77,14 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result = fields .iter() .map(|field| match field { ParsedField::Regular { ty, .. } => ty.clone(), ParsedField::Node { output_type, input_type, .. } => match parsed.is_async { - true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output: core::future::Future + #graphene_core::WasmNotSend>), + true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = impl core::future::Future>), false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>), }, }) @@ -164,7 +165,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result { let name = &pat_ident.ident; - quote! { let #name = self.#name.eval(()); } + quote! { let #name = self.#name.eval(__input.clone()).await; } } ParsedField::Node { pat_ident, .. } => { let name = &pat_ident.ident; @@ -181,16 +182,32 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result quote!(#name: #graphene_core::Node<'n, (), Output = #ty> ), - (ParsedField::Node { input_type, output_type, .. }, false) => { - quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output = #output_type> + #graphene_core::WasmNotSync) + (ParsedField::Regular { ty, .. }, _) => { + let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all"); + let id = future_idents.len(); + let fut_ident = format_ident!("F{}", id); + future_idents.push(fut_ident.clone()); + quote!( + #fut_ident: core::future::Future + #graphene_core::WasmNotSend + 'n, + for<'all> #all_lifetime_ty: #graphene_core::WasmNotSend, + #name: #graphene_core::Node<'n, #input_type, Output = #fut_ident> + #graphene_core::WasmNotSync + ) } (ParsedField::Node { input_type, output_type, .. }, true) => { - quote!(for<'all_input> #name: #graphene_core::Node<'all_input, #input_type, Output: core::future::Future + #graphene_core::WasmNotSend> + #graphene_core::WasmNotSync) + let id = future_idents.len(); + let fut_ident = format_ident!("F{}", id); + future_idents.push(fut_ident.clone()); + + quote!( + #fut_ident: core::future::Future + #graphene_core::WasmNotSend + 'n, + #name: #graphene_core::Node<'n, #input_type, Output = #fut_ident > + #graphene_core::WasmNotSync + ) } + (ParsedField::Node { .. }, false) => unreachable!(), }); } let where_clause = where_clause.clone().unwrap_or(WhereClause { @@ -210,24 +227,16 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result; - #[inline] - fn eval(&'n self, __input: #input_type) -> Self::Output { + let eval_impl = quote! { + type Output = #graphene_core::registry::DynFuture<'n, #output_type>; + #[inline] + fn eval(&'n self, __input: #input_type) -> Self::Output { + Box::pin(async move { #(#eval_args)* - Box::pin(self::#fn_name(__input #(, #field_names)*)) - } - } - } else { - quote! { - type Output = #output_type; - #[inline] - fn eval(&'n self, __input: #input_type) -> Self::Output { - #(#eval_args)* - self::#fn_name(__input #(, #field_names)*) - } + self::#fn_name(__input #(, #field_names)*) #await_keyword + }) } }; let path = match parsed.attributes.path { @@ -245,10 +254,10 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body + pub(crate) #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body #[automatically_derived] - impl<'n, #(#fn_generics,)* #(#struct_generics,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> + impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> #struct_where_clause { #eval_impl @@ -260,7 +269,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result = parsed .fields .iter() @@ -331,9 +340,9 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st match field { ParsedField::Regular { implementations, ty, .. } => { if !implementations.is_empty() { - implementations.iter().map(|ty| (&unit, ty, false)).collect() + implementations.iter().map(|ty| (&unit, ty)).collect() } else { - vec![(&unit, ty, false)] + vec![(&unit, ty)] } } ParsedField::Node { @@ -343,20 +352,19 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st .. } => { if !implementations.is_empty() { - implementations.iter().map(|impl_| (&impl_.input, &impl_.output, true)).collect() + implementations.iter().map(|impl_| (&impl_.input, &impl_.output)).collect() } else { - vec![(input_type, output_type, true)] + vec![(input_type, output_type)] } } } .into_iter() - .map(|(input, out, node)| (substitute_lifetimes(input.clone()), substitute_lifetimes(out.clone()), node)) + .map(|(input, out)| (substitute_lifetimes(input.clone(), "_"), substitute_lifetimes(out.clone(), "_"))) .collect::>() }) .collect(); let max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max(); - let future_node = (!parsed.is_async).then(|| quote!(let node = gcore::registry::FutureWrapperNode::new(node);)); for i in 0..max_implementations.unwrap_or(0) { let mut temp_constructors = Vec::new(); @@ -365,38 +373,24 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st for (j, types) in parameter_types.iter().enumerate() { let field_name = field_names[j]; - let (input_type, output_type, impl_node) = &types[i.min(types.len() - 1)]; + let (input_type, output_type) = &types[i.min(types.len() - 1)]; let node = matches!(parsed.fields[j], ParsedField::Node { .. }); let downcast_node = quote!( - let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone()); - ); - temp_constructors.push(if node { - if !parsed.is_async { - return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters")); - } - downcast_node - } else { - quote!( - #downcast_node - let #field_name = #field_name.eval(()).await; - let #field_name = ClonedNode::new(#field_name); - let #field_name: TypeNode<_, #input_type, #output_type> = TypeNode::new(#field_name); - // try polling futures - ) - }); - temp_node_io.push(quote!(fn_type!(#input_type, #output_type, alias: #output_type))); - match parsed.is_async && *impl_node { - true => panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)), - false => panic_node_types.push(quote!(#input_type, #output_type)), - }; + let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone()); + ); + if node && !parsed.is_async { + return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters")); + } + temp_constructors.push(downcast_node); + temp_node_io.push(quote!(fn_type_fut!(#input_type, #output_type, alias: #output_type))); + panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>)); } let input_type = match parsed.input.implementations.is_empty() { true => parsed.input.ty.clone(), false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(), }; - let node_io = if parsed.is_async { quote!(to_async_node_io) } else { quote!(to_node_io) }; constructors.push(quote!( ( |args| { @@ -404,14 +398,13 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st #(#temp_constructors;)* let node = #struct_name::new(#(#field_names,)*); // try polling futures - #future_node let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node); Box::new(any) as TypeErasedBox<'_> }) }, { let node = #struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*); let params = vec![#(#temp_node_io,)*]; - let mut node_io = NodeIO::<'_, #input_type>::#node_io(&node, params); + let mut node_io = NodeIO::<'_, #input_type>::to_async_node_io(&node, params); node_io } @@ -443,11 +436,11 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st use syn::{visit_mut::VisitMut, GenericArgument, Lifetime, Type}; -struct LifetimeReplacer; +struct LifetimeReplacer(&'static str); impl VisitMut for LifetimeReplacer { fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) { - lifetime.ident = syn::Ident::new("_", lifetime.ident.span()); + lifetime.ident = syn::Ident::new(self.0, lifetime.ident.span()); } fn visit_type_mut(&mut self, ty: &mut Type) { @@ -472,7 +465,7 @@ impl VisitMut for LifetimeReplacer { } #[must_use] -fn substitute_lifetimes(mut ty: Type) -> Type { - LifetimeReplacer.visit_type_mut(&mut ty); +fn substitute_lifetimes(mut ty: Type, lifetime: &'static str) -> Type { + LifetimeReplacer(lifetime).visit_type_mut(&mut ty); ty } diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 96b010c27..f5cbc75ea 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -4,8 +4,11 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, ToTokens}; use syn::parse::{Parse, ParseStream, Parser}; use syn::punctuated::Punctuated; +use syn::spanned::Spanned; use syn::token::{Comma, RArrow}; -use syn::{AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, WhereClause}; +use syn::{ + parse_quote, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, WhereClause, +}; use crate::codegen::generate_node_code; @@ -548,10 +551,12 @@ fn extract_attribute<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Attri // Modify the new_node_fn function to use the code generation pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 { let parse_result = parse_node_fn(attr, item.clone()); - let Ok(parsed_node) = parse_result else { + let Ok(mut parsed_node) = parse_result else { let e = parse_result.unwrap_err(); return Error::new(e.span(), format!("Failed to parse node function: {e}")).to_compile_error(); }; + + parsed_node.replace_impl_trait_in_input(); if let Err(e) = crate::validation::validate_node_fn(&parsed_node) { return Error::new(e.span(), format!("Validation Error:\n{e}")).to_compile_error(); } @@ -564,6 +569,31 @@ pub fn new_node_fn(attr: TokenStream2, item: TokenStream2) -> TokenStream2 { } } +impl ParsedNodeFn { + fn replace_impl_trait_in_input(&mut self) { + if let Type::ImplTrait(impl_trait) = self.input.ty.clone() { + let ident = Ident::new("_Input", impl_trait.span()); + let mut bounds = impl_trait.bounds; + bounds.push(parse_quote!('n)); + self.fn_generics.push(GenericParam::Type(TypeParam { + attrs: Default::default(), + ident: ident.clone(), + colon_token: Some(Default::default()), + bounds, + eq_token: None, + default: None, + })); + self.input.ty = parse_quote!(#ident); + if self.input.implementations.is_empty() { + self.input.implementations.push(parse_quote!(gcore::Context)); + } + } + if self.input.pat_ident.ident == "_" { + self.input.pat_ident.ident = Ident::new("__ctx", self.input.pat_ident.ident.span()); + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -774,7 +804,7 @@ mod tests { let attr = quote!(category("Vector: Shape")); let input = quote!( /// Test - fn circle(_: (), #[default(50.)] radius: f64) -> VectorData { + fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> VectorData { // Implementation details... } ); @@ -795,7 +825,7 @@ mod tests { where_clause: None, input: Input { pat_ident: pat_ident("_"), - ty: parse_quote!(()), + ty: parse_quote!(impl Ctx), implementations: Punctuated::new(), }, output_type: parse_quote!(VectorData), diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index 0130bfa8a..2a480bee2 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -6,9 +6,11 @@ pub use executor::GpuExecutor; use dyn_any::{DynAny, StaticType}; use gpu_executor::{ComputePassDimensions, GPUConstant, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer}; -use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; +use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle, TextureFrame}; +use graphene_core::raster::image::ImageFrameTable; +use graphene_core::raster::{Image, SRGBA8}; use graphene_core::transform::{Footprint, Transform}; -use graphene_core::{Color, Cow, Node, SurfaceFrame, Type}; +use graphene_core::{Color, Cow, Ctx, ExtractFootprint, Node, SurfaceFrame, Type}; use anyhow::{bail, Result}; use futures::Future; @@ -823,12 +825,12 @@ impl ShaderInputNode { } #[node_macro::node(category(""))] -async fn uniform<'a: 'n, T: ToUniformBuffer + Send + 'n>(_: (), #[implementations(f32, DAffine2)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { +async fn uniform<'a: 'n, T: ToUniformBuffer + Send + 'n>(_: impl Ctx, #[implementations(f32, DAffine2)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { executor.create_uniform_buffer(data).unwrap() } #[node_macro::node(category(""))] -async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: (), #[implementations(Vec)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { +async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: impl Ctx, #[implementations(Vec)] data: T, executor: &'a WgpuExecutor) -> WgpuShaderInput { executor .create_storage_buffer( data, @@ -843,18 +845,18 @@ async fn storage<'a: 'n, T: ToStorageBuffer + Send + 'n>(_: (), #[implementation } #[node_macro::node(category(""))] -async fn create_output_buffer<'a: 'n>(_: (), size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc { +async fn create_output_buffer<'a: 'n>(_: impl Ctx + 'a, size: usize, executor: &'a WgpuExecutor, ty: Type) -> Arc { Arc::new(executor.create_output_buffer(size, ty, true).unwrap()) } #[node_macro::node(skip_impl)] -async fn create_compute_pass<'a: 'n>(_: (), layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer { +async fn create_compute_pass<'a: 'n>(_: impl Ctx + 'a, layout: PipelineLayout, executor: &'a WgpuExecutor, output: WgpuShaderInput, instances: ComputePassDimensions) -> CommandBuffer { executor.create_compute_pass(&layout, Some(output.into()), instances).unwrap() } #[node_macro::node(category("Debug: GPU"))] async fn create_pipeline_layout( - _: (), + _: impl Ctx, shader: impl Node<(), Output = ShaderHandle>, entry_point: String, bind_group: impl Node<(), Output = Bindgroup>, @@ -869,14 +871,14 @@ async fn create_pipeline_layout( } #[node_macro::node(category(""))] -async fn read_output_buffer<'a: 'n>(_: (), buffer: Arc, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec { +async fn read_output_buffer<'a: 'n>(_: impl Ctx + 'a, buffer: Arc, executor: &'a WgpuExecutor, _compute_pass: ()) -> Vec { executor.read_output_buffer(buffer).await.unwrap() } pub type WindowHandle = Arc>; #[node_macro::node(skip_impl)] -fn create_gpu_surface<'a: 'n, Io: ApplicationIo + 'a + Send + Sync>(_: (), editor_api: &'a EditorApi) -> Option { +fn create_gpu_surface<'a: 'n, Io: ApplicationIo + 'a + Send + Sync>(_: impl Ctx + 'a, editor_api: &'a EditorApi) -> Option { let canvas = editor_api.application_io.as_ref()?.window()?; let executor = editor_api.application_io.as_ref()?.gpu_executor()?; Some(Arc::new(executor.create_surface(canvas).ok()?)) @@ -889,7 +891,13 @@ pub struct ShaderInputFrame { } #[node_macro::node(category(""))] -async fn render_texture<'a: 'n>(_: (), footprint: Footprint, image: impl Node, surface: Option, executor: &'a WgpuExecutor) -> SurfaceFrame { +async fn render_texture<'a: 'n>( + _: impl Ctx + 'a, + footprint: Footprint, + image: impl Node, + surface: Option, + executor: &'a WgpuExecutor, +) -> SurfaceFrame { let surface = surface.unwrap(); let surface_id = surface.window_id; let image = image.eval(footprint).await; @@ -904,34 +912,29 @@ async fn render_texture<'a: 'n>(_: (), footprint: Footprint, image: impl Node( -// #[implementations((), Footprint)] footprint: F, -// #[implementations(() -> ImageFrameTable, Footprint -> ImageFrameTable)] input: impl Node>, -// executor: &'a WgpuExecutor, -// ) -> TextureFrame { -// // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); -// let input = input.eval(footprint).await; -// let input = input.one_item(); +#[node_macro::node(category(""))] +async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: ImageFrameTable, executor: &'a WgpuExecutor) -> TextureFrame { + // let new_data: Vec = input.image.data.into_iter().map(|c| c.into()).collect(); -// let new_data: Vec = input.image.data.iter().map(|x| (*x).into()).collect(); -// let new_image = Image { -// width: input.image.width, -// height: input.image.height, -// data: new_data, -// base64_string: None, -// }; + let input = input.one_item(); + let new_data: Vec = input.image.data.iter().map(|x| (*x).into()).collect(); + let new_image = Image { + width: input.image.width, + height: input.image.height, + data: new_data, + base64_string: None, + }; -// let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap(); -// let texture = match shader_input { -// ShaderInput::TextureBuffer(buffer, _) => buffer, -// ShaderInput::StorageTextureBuffer(buffer, _) => buffer, -// _ => unreachable!("Unsupported ShaderInput type"), -// }; + let shader_input = executor.create_texture_buffer(new_image, TextureBufferOptions::Texture).unwrap(); + let texture = match shader_input { + ShaderInput::TextureBuffer(buffer, _) => buffer, + ShaderInput::StorageTextureBuffer(buffer, _) => buffer, + _ => unreachable!("Unsupported ShaderInput type"), + }; -// TextureFrame { -// texture: texture.into(), -// transform: input.transform, -// alpha_blend: Default::default(), -// } -// } + TextureFrame { + texture: texture.into(), + transform: input.transform, + alpha_blend: Default::default(), + } +} diff --git a/website/content/volunteer/guide/project-setup/_index.md b/website/content/volunteer/guide/project-setup/_index.md index 9da71b5c1..a40c6244b 100644 --- a/website/content/volunteer/guide/project-setup/_index.md +++ b/website/content/volunteer/guide/project-setup/_index.md @@ -21,7 +21,7 @@ Next, install the dependencies required for development builds: ```sh cargo install cargo-watch cargo install wasm-pack -cargo install -f wasm-bindgen-cli@0.2.99 +cargo install -f wasm-bindgen-cli@0.2.100 ``` Regarding the last one: you'll likely get faster build times if you manually install that specific version of `wasm-bindgen-cli`. It is supposed to be installed automatically but a version mismatch causes it to reinstall every single recompilation. It may need to be manually updated periodically to match the version of the `wasm-bindgen` dependency in [`Cargo.toml`](https://github.com/GraphiteEditor/Graphite/blob/master/Cargo.toml).