From 9bdf4541d038a9fb9d9bcf56d184a29bd15bd13b Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 30 Jul 2026 10:58:51 +0000 Subject: [PATCH] Replace borrowed scope injections with arcs --- editor/src/node_graph_executor/runtime.rs | 8 ++--- node-graph/graph-craft/src/document/value.rs | 8 ++--- .../src/dynamic_executor.rs | 2 +- .../interpreted-executor/src/node_registry.rs | 12 +++---- node-graph/interpreted-executor/src/util.rs | 2 +- node-graph/libraries/core-types/src/gnode.rs | 2 +- node-graph/libraries/core-types/src/ops.rs | 4 +-- .../libraries/core-types/src/registry.rs | 3 +- node-graph/libraries/wgpu-executor/src/lib.rs | 12 +++++++ .../wgpu-executor/src/texture_conversion.rs | 29 ++++++++-------- .../nodes/gstd/src/platform_application_io.rs | 34 +++++++------------ .../nodes/gstd/src/render_background.rs | 4 +-- node-graph/nodes/gstd/src/render_cache.rs | 8 ++--- node-graph/nodes/gstd/src/render_node.rs | 4 +-- .../nodes/gstd/src/render_pixel_preview.rs | 4 +-- 15 files changed, 68 insertions(+), 68 deletions(-) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index 088e7513bb..804ccb36d8 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -257,10 +257,10 @@ impl NodeRuntime { .application_io .as_ref() .unwrap() - .gpu_executor() + .gpu_executor_arc() .expect("GPU executor should be available when we receive a texture"); - let raster_cpu = Raster::new_gpu(texture).convert(Footprint::BOUNDLESS, executor).await; + let raster_cpu = Raster::new_gpu(texture).convert(Footprint::BOUNDLESS, wgpu_executor::WgpuExecutorHandle(executor)).await; let (data, width, height) = raster_cpu.to_flat_u8(); @@ -281,10 +281,10 @@ impl NodeRuntime { .application_io .as_ref() .unwrap() - .gpu_executor() + .gpu_executor_arc() .expect("GPU executor should be available when we receive a texture"); - let raster_cpu = Raster::new_gpu(texture).convert(Footprint::BOUNDLESS, executor).await; + let raster_cpu = Raster::new_gpu(texture).convert(Footprint::BOUNDLESS, wgpu_executor::WgpuExecutorHandle(executor)).await; self.sender.send_eyedropper_preview(raster_cpu); continue; diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index ba3170fccb..35458cb5b0 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -259,12 +259,12 @@ macro_rules! tagged_value { Self::NodeIdPath(_) => concrete!(List), Self::DocumentNode(_) => concrete!(DocumentNode), Self::ContextFeatures(_) => concrete!(ContextFeatures), - Self::EditorApi(_) => concrete!(&PlatformEditorApi), + Self::EditorApi(_) => concrete!(Arc), Self::ResourceHash(_) => concrete!(ResourceHash), } } - /// Materializes the value exactly as [`Self::to_dynany`] does and wraps it in a `ClonedNode` behind an [`EdgeHandle`], whose type matches [`Self::ty`]. + /// Materializes the value as [`Self::to_dynany`] does, wrapped in a `ClonedNode` edge typed by [`Self::ty`]. pub fn to_edge(self) -> Result { match self { // =============== @@ -309,12 +309,12 @@ macro_rules! tagged_value { } Self::DocumentNode(node) => Ok(value_edge(node)), Self::ContextFeatures(features) => Ok(value_edge(features)), - Self::EditorApi(_) => Err("EditorApi values are wired by the executor, not as value edges".to_string()), + Self::EditorApi(x) => Ok(value_edge(x)), Self::ResourceHash(x) => Ok(value_edge(x)), } } - /// Evaluates a typed edge and converts the landed value into a tagged value; the eval-boundary companion of [`Self::to_edge`] with the coverage of [`Self::try_from_any`]. + /// Evaluates a typed edge and converts the landed value into a tagged value, with the coverage of [`Self::try_from_any`]. pub fn from_edge(handle: EdgeHandle, ctx: &Context) -> Result, String> { let ty = handle.ty().clone(); // =============== diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 2f904676a5..5289fd90b2 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -17,7 +17,7 @@ use std::sync::{Arc, Mutex, PoisonError}; const ARENA_CAPACITY: usize = 1 << 20; -/// Dropped tasks never complete; replaced by the host spawner when the runtime scope wiring lands. +/// Dropped tasks never complete. pub struct NoopSpawner; impl Spawner for NoopSpawner { diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 46d4c3a3ef..41540a38a4 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -23,7 +23,7 @@ use graphene_std::{Artboard, Context, Graphic, ProtoNodeIdentifier, SourceId, co use node_registry_macros::{async_node, convert_node, into_node}; use std::collections::HashMap; #[cfg(feature = "gpu")] -use wgpu_executor::WgpuExecutor; +use wgpu_executor::WgpuExecutorHandle; fn node_registry() -> HashMap> { let mut node_types: Vec<(ProtoNodeIdentifier, RegistryEntry)> = vec![ @@ -90,8 +90,6 @@ fn node_registry() -> HashMap> { convert_node!(from: List>, to: AttributeValueDyn), convert_node!(from: List, to: AttributeValueDyn), // into_node!(from: List>, to: List>), - #[cfg(feature = "gpu")] - into_node!(from: &PlatformEditorApi, to: &WgpuExecutor), convert_node!(from: DVec2, to: DVec2), convert_node!(from: List, to: List), convert_node!(from: DVec2, to: List), @@ -101,13 +99,13 @@ fn node_registry() -> HashMap> { convert_node!(from: IVec2, to: String), convert_node!(from: DAffine2, to: String), #[cfg(feature = "gpu")] - convert_node!(from: List>, to: List>, converter: &WgpuExecutor), + convert_node!(from: List>, to: List>, converter: WgpuExecutorHandle), #[cfg(feature = "gpu")] - convert_node!(from: List>, to: List>, converter: &WgpuExecutor), + convert_node!(from: List>, to: List>, converter: WgpuExecutorHandle), #[cfg(feature = "gpu")] - convert_node!(from: List>, to: List>, converter: &WgpuExecutor), + convert_node!(from: List>, to: List>, converter: WgpuExecutorHandle), #[cfg(feature = "gpu")] - convert_node!(from: List>, to: List>, converter: &WgpuExecutor, async), + convert_node!(from: List>, to: List>, converter: WgpuExecutorHandle, async), // ============= // MONITOR NODES // ============= diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index a1dcb5dfe0..c151f85d68 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -114,7 +114,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc)))]; NodeNetwork { exports: vec![NodeInput::node(NodeId(1), 0)], diff --git a/node-graph/libraries/core-types/src/gnode.rs b/node-graph/libraries/core-types/src/gnode.rs index 870e5fdc22..da9b73fff5 100644 --- a/node-graph/libraries/core-types/src/gnode.rs +++ b/node-graph/libraries/core-types/src/gnode.rs @@ -31,7 +31,7 @@ pub trait GNode { GPoll::Final(Extent::Free) } - /// Introspection access to node-resident records, for example the monitor's captured io; `None` for ordinary nodes. + /// Introspection access to node-resident records; `None` for ordinary nodes. fn serialize(&self) -> Option> { None } diff --git a/node-graph/libraries/core-types/src/ops.rs b/node-graph/libraries/core-types/src/ops.rs index 14d3b5b152..2c3eb462cc 100644 --- a/node-graph/libraries/core-types/src/ops.rs +++ b/node-graph/libraries/core-types/src/ops.rs @@ -47,9 +47,7 @@ pub trait Convert: Sized { fn convert(self, footprint: Footprint, converter: C) -> T; } -/// The genuinely asynchronous counterpart of [`Convert`], for conversions whose work completes outside -/// the evaluation, such as the GPU-to-CPU texture readback. Consumed by the `convert_async` kernel on -/// the async source tier; a conversion pair implements exactly one of the two traits. +/// The asynchronous counterpart of [`Convert`]; a conversion pair implements exactly one of the two traits. pub trait ConvertAsync: Sized { #[must_use] fn convert(self, footprint: Footprint, converter: C) -> crate::runtime::SourceFuture; diff --git a/node-graph/libraries/core-types/src/registry.rs b/node-graph/libraries/core-types/src/registry.rs index 8a320e5e45..6034166b3a 100644 --- a/node-graph/libraries/core-types/src/registry.rs +++ b/node-graph/libraries/core-types/src/registry.rs @@ -180,8 +180,7 @@ impl std::fmt::Debug for EdgeHandle { } } -// SAFETY: wasm is single threaded, so the marker-free payload never actually crosses a thread; -// mirrors the old NodeContainer assertion that let the executor live in a sync static. +// SAFETY: wasm is single threaded, so the marker-free payload never actually crosses a thread. #[cfg(target_family = "wasm")] unsafe impl Send for EdgeHandle {} // SAFETY: as in Send. diff --git a/node-graph/libraries/wgpu-executor/src/lib.rs b/node-graph/libraries/wgpu-executor/src/lib.rs index 9af2c73972..23c6e602a2 100644 --- a/node-graph/libraries/wgpu-executor/src/lib.rs +++ b/node-graph/libraries/wgpu-executor/src/lib.rs @@ -60,6 +60,18 @@ impl std::fmt::Debug for WgpuExecutor { } } +/// Owned Arc handle carrying the executor as an ordinary wire value. +#[derive(Clone, Debug)] +pub struct WgpuExecutorHandle(pub std::sync::Arc); + +impl std::ops::Deref for WgpuExecutorHandle { + type Target = WgpuExecutor; + + fn deref(&self) -> &WgpuExecutor { + &self.0 + } +} + impl<'a, T: ApplicationIo> From<&'a EditorApi> for &'a WgpuExecutor { fn from(editor_api: &'a EditorApi) -> Self { editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap() diff --git a/node-graph/libraries/wgpu-executor/src/texture_conversion.rs b/node-graph/libraries/wgpu-executor/src/texture_conversion.rs index 9a8634672f..cdfa4c26fa 100644 --- a/node-graph/libraries/wgpu-executor/src/texture_conversion.rs +++ b/node-graph/libraries/wgpu-executor/src/texture_conversion.rs @@ -8,6 +8,7 @@ use core_types::runtime::SourceFuture; use core_types::transform::Footprint; use raster_types::Image; use raster_types::{CPU, GPU, Raster}; +use crate::WgpuExecutorHandle; use wgpu::util::{DeviceExt, TextureDataOrder}; use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages}; @@ -41,15 +42,15 @@ fn upload_to_texture(device: &wgpu::Device, queue: &wgpu::Queue, image: &Raster< /// Passthrough conversion for GPU `List`s - no conversion needed -impl<'i> Convert>, &'i WgpuExecutor> for List> { - fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List> { +impl Convert>, WgpuExecutorHandle> for List> { + fn convert(self, _: Footprint, _converter: WgpuExecutorHandle) -> List> { self } } /// Converts a `List>` to `List>` by uploading each image to a texture -impl<'i> Convert>, &'i WgpuExecutor> for List> { - fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List> { +impl Convert>, WgpuExecutorHandle> for List> { + fn convert(self, _: Footprint, executor: WgpuExecutorHandle) -> List> { let device = &executor.context().device; let queue = executor.context().queue.lock(); let list = self @@ -68,8 +69,8 @@ impl<'i> Convert>, &'i WgpuExecutor> for List> { } /// Converts single CPU raster to GPU by uploading to texture -impl<'i> Convert, &'i WgpuExecutor> for Raster { - fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Raster { +impl Convert, WgpuExecutorHandle> for Raster { + fn convert(self, _: Footprint, executor: WgpuExecutorHandle) -> Raster { let device = &executor.context().device; let queue = executor.context().queue.lock(); let texture = upload_to_texture(device, &queue, &self); @@ -80,8 +81,8 @@ impl<'i> Convert, &'i WgpuExecutor> for Raster { } /// Passthrough conversion for CPU `List`s - no conversion needed -impl<'i> Convert>, &'i WgpuExecutor> for List> { - fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List> { +impl Convert>, WgpuExecutorHandle> for List> { + fn convert(self, _: Footprint, _converter: WgpuExecutorHandle) -> List> { self } } @@ -191,8 +192,8 @@ impl RasterGpuToRasterCpuConverter { } /// Converts a `List>` to `List>` by downloading texture data in one go then asynchronously maps all buffers and processes the results. -impl<'i> ConvertAsync>, &'i WgpuExecutor> for List> { - fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> SourceFuture>> { +impl ConvertAsync>, WgpuExecutorHandle> for List> { + fn convert(self, _: Footprint, executor: WgpuExecutorHandle) -> SourceFuture>> { let device = executor.context().device.clone(); let queue = executor.context().queue.lock(); @@ -235,8 +236,8 @@ impl<'i> ConvertAsync>, &'i WgpuExecutor> for List> } /// Converts single GPU raster to CPU by downloading texture data -impl<'i> ConvertAsync, &'i WgpuExecutor> for Raster { - fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> SourceFuture> { +impl ConvertAsync, WgpuExecutorHandle> for Raster { + fn convert(self, _: Footprint, executor: WgpuExecutorHandle) -> SourceFuture> { let device = executor.context().device.clone(); let queue = executor.context().queue.lock(); @@ -256,10 +257,10 @@ impl<'i> ConvertAsync, &'i WgpuExecutor> for Raster { /// /// Accepts either individual raster data or a `List` of raster elements and converts it to the GPU format using the WgpuExecutor's device and queue. #[node_macro::node(category(""))] -pub fn upload_texture<'a, T: Convert>, &'a WgpuExecutor>>( +pub fn upload_texture>, WgpuExecutorHandle>>( _: impl Ctx, #[implementations(List>, List>)] input: T, - executor: &'a WgpuExecutor, + executor: WgpuExecutorHandle, ) -> List> { input.convert(Footprint::DEFAULT, executor) } diff --git a/node-graph/nodes/gstd/src/platform_application_io.rs b/node-graph/nodes/gstd/src/platform_application_io.rs index 0b2ffd66a2..46e05c38dd 100644 --- a/node-graph/nodes/gstd/src/platform_application_io.rs +++ b/node-graph/nodes/gstd/src/platform_application_io.rs @@ -264,12 +264,12 @@ where } #[node_macro::node(category(""), inject_scope)] -pub fn editor_api<'a>(_: impl Ctx, #[scope("editor-api")] editor_api: &'a PlatformEditorApi) -> &'a PlatformEditorApi { +pub fn editor_api(_: impl Ctx, #[scope("editor-api")] editor_api: Arc) -> Arc { editor_api } #[node_macro::node(category(""))] -pub fn resource<'a>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> SourceFuture> { +pub fn resource(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: Arc) -> SourceFuture> { let application_io = editor_api.application_io.clone(); Box::pin(async move { let Some(application_io) = application_io else { @@ -283,26 +283,18 @@ pub fn resource<'a>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIF } #[node_macro::node(category(""), inject_scope)] -pub fn wgpu_executor<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> &'a ::wgpu_executor::WgpuExecutor { - editor_api - .application_io - .as_ref() - .expect("ApplicationIo not not available") - .gpu_executor() - .expect("GPU executor not available") +pub fn wgpu_executor(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: Arc) -> ::wgpu_executor::WgpuExecutorHandle { + ::wgpu_executor::WgpuExecutorHandle( + editor_api + .application_io + .as_ref() + .expect("ApplicationIo not not available") + .gpu_executor_arc() + .expect("GPU executor not available"), + ) } #[node_macro::node(category(""), inject_scope)] -pub fn try_wgpu_executor<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> Option<&'a ::wgpu_executor::WgpuExecutor> { - editor_api.application_io.as_ref()?.gpu_executor() -} - -#[node_macro::node(category(""), inject_scope)] -pub fn wgpu_executor_arc<'a>(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> std::sync::Arc<::wgpu_executor::WgpuExecutor> { - editor_api - .application_io - .as_ref() - .expect("ApplicationIo not available") - .gpu_executor_arc() - .expect("GPU executor not available") +pub fn try_wgpu_executor(_: impl Ctx, #[scope(editor_api::IDENTIFIER)] editor_api: Arc) -> Option<::wgpu_executor::WgpuExecutorHandle> { + editor_api.application_io.as_ref()?.gpu_executor_arc().map(::wgpu_executor::WgpuExecutorHandle) } diff --git a/node-graph/nodes/gstd/src/render_background.rs b/node-graph/nodes/gstd/src/render_background.rs index 52d9c87653..3726169333 100644 --- a/node-graph/nodes/gstd/src/render_background.rs +++ b/node-graph/nodes/gstd/src/render_background.rs @@ -119,9 +119,9 @@ fn render_background<'a>( } #[node_macro::node(category(""), inject_scope)] -fn composite_background_pipeline<'a>( +fn composite_background_pipeline( _ctx: impl Ctx, - #[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>, + #[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option, #[data] pipeline: WgpuPipelineCache, ) -> WgpuPipelineCache { if let Some(executor) = executor { diff --git a/node-graph/nodes/gstd/src/render_cache.rs b/node-graph/nodes/gstd/src/render_cache.rs index f37d4c3099..8e58eae8e0 100644 --- a/node-graph/nodes/gstd/src/render_cache.rs +++ b/node-graph/nodes/gstd/src/render_cache.rs @@ -322,10 +322,10 @@ fn flood_fill(start: &TileCoord, tile_set: &HashSet, visited: &mut Ha } #[node_macro::node(category(""))] -pub fn render_output_cache<'a>( +pub fn render_output_cache( ctx: impl Ctx + ExtractAll + DeriveCtx, - #[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>, - #[scope(crate::platform_application_io::editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi, + #[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option, + #[scope(crate::platform_application_io::editor_api::IDENTIFIER)] editor_api: std::sync::Arc, data: impl Node, Output = RenderOutput>, #[data] tile_cache: TileCache, ) -> Result { @@ -420,7 +420,7 @@ pub fn render_output_cache<'a>( let executor = executor.expect("GPU executor not available"); let output_texture = executor.request_texture(physical_resolution); - let combined_metadata = composite_cached_regions(&all_regions, &output_texture, &device_origin_offset, &footprint.transform, executor); + let combined_metadata = composite_cached_regions(&all_regions, &output_texture, &device_origin_offset, &footprint.transform, &executor); Ok(RenderOutput { data: RenderOutputType::Texture(output_texture), diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index 76755feb90..ad0dcc5609 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -72,9 +72,9 @@ fn render_intermediate( } #[node_macro::node(category(""))] -fn render<'a>( +fn render( ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, - #[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>, + #[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option, data: RenderIntermediate, ) -> RenderOutput { let footprint = ctx.footprint(); diff --git a/node-graph/nodes/gstd/src/render_pixel_preview.rs b/node-graph/nodes/gstd/src/render_pixel_preview.rs index bbcd2e78ca..3f9cc1c6ea 100644 --- a/node-graph/nodes/gstd/src/render_pixel_preview.rs +++ b/node-graph/nodes/gstd/src/render_pixel_preview.rs @@ -72,9 +72,9 @@ pub fn render_pixel_preview( } #[node_macro::node(category(""), inject_scope)] -fn pixel_preview_pipeline<'a>( +fn pixel_preview_pipeline( _ctx: impl Ctx, - #[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option<&'a WgpuExecutor>, + #[scope(crate::platform_application_io::try_wgpu_executor::IDENTIFIER)] executor: Option, #[data] pipeline: WgpuPipelineCache, ) -> WgpuPipelineCache { if let Some(executor) = executor {