Replace borrowed scope injections with arcs

This commit is contained in:
Dennis Kobert
2026-07-30 10:58:51 +00:00
parent 382ad9d15c
commit 9bdf4541d0
15 changed files with 68 additions and 68 deletions

View File

@@ -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;

View File

@@ -259,12 +259,12 @@ macro_rules! tagged_value {
Self::NodeIdPath(_) => concrete!(List<NodeId>),
Self::DocumentNode(_) => concrete!(DocumentNode),
Self::ContextFeatures(_) => concrete!(ContextFeatures),
Self::EditorApi(_) => concrete!(&PlatformEditorApi),
Self::EditorApi(_) => concrete!(Arc<PlatformEditorApi>),
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<EdgeHandle, String> {
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<GPoll<Self>, String> {
let ty = handle.ty().clone();
// ===============

View File

@@ -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 {

View File

@@ -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<ProtoNodeIdentifier, Vec<RegistryEntry>> {
let mut node_types: Vec<(ProtoNodeIdentifier, RegistryEntry)> = vec![
@@ -90,8 +90,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
convert_node!(from: List<Raster<GPU>>, to: AttributeValueDyn),
convert_node!(from: List<Graphic>, to: AttributeValueDyn),
// into_node!(from: List<Raster<CPU>>, to: List<Raster<SRGBA8>>),
#[cfg(feature = "gpu")]
into_node!(from: &PlatformEditorApi, to: &WgpuExecutor),
convert_node!(from: DVec2, to: DVec2),
convert_node!(from: List<Vector>, to: List<Vector>),
convert_node!(from: DVec2, to: List<Vector>),
@@ -101,13 +99,13 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
convert_node!(from: IVec2, to: String),
convert_node!(from: DAffine2, to: String),
#[cfg(feature = "gpu")]
convert_node!(from: List<Raster<CPU>>, to: List<Raster<CPU>>, converter: &WgpuExecutor),
convert_node!(from: List<Raster<CPU>>, to: List<Raster<CPU>>, converter: WgpuExecutorHandle),
#[cfg(feature = "gpu")]
convert_node!(from: List<Raster<CPU>>, to: List<Raster<GPU>>, converter: &WgpuExecutor),
convert_node!(from: List<Raster<CPU>>, to: List<Raster<GPU>>, converter: WgpuExecutorHandle),
#[cfg(feature = "gpu")]
convert_node!(from: List<Raster<GPU>>, to: List<Raster<GPU>>, converter: &WgpuExecutor),
convert_node!(from: List<Raster<GPU>>, to: List<Raster<GPU>>, converter: WgpuExecutorHandle),
#[cfg(feature = "gpu")]
convert_node!(from: List<Raster<GPU>>, to: List<Raster<CPU>>, converter: &WgpuExecutor, async),
convert_node!(from: List<Raster<GPU>>, to: List<Raster<CPU>>, converter: WgpuExecutorHandle, async),
// =============
// MONITOR NODES
// =============

View File

@@ -114,7 +114,7 @@ pub fn wrap_network_in_scope(network: NodeNetwork, editor_api: Arc<PlatformEdito
..Default::default()
},
];
let scope_injections = vec![("editor-api".to_string(), (NodeId(2), concrete!(&PlatformEditorApi)))];
let scope_injections = vec![("editor-api".to_string(), (NodeId(2), concrete!(std::sync::Arc<PlatformEditorApi>)))];
NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],

View File

@@ -31,7 +31,7 @@ pub trait GNode<Input> {
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<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
None
}

View File

@@ -47,9 +47,7 @@ pub trait Convert<T, C>: 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<T, C>: Sized {
#[must_use]
fn convert(self, footprint: Footprint, converter: C) -> crate::runtime::SourceFuture<T>;

View File

@@ -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.

View File

@@ -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<WgpuExecutor>);
impl std::ops::Deref for WgpuExecutorHandle {
type Target = WgpuExecutor;
fn deref(&self) -> &WgpuExecutor {
&self.0
}
}
impl<'a, T: ApplicationIo<Executor = WgpuExecutor>> From<&'a EditorApi<T>> for &'a WgpuExecutor {
fn from(editor_api: &'a EditorApi<T>) -> Self {
editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap()

View File

@@ -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<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List<Raster<GPU>> {
impl Convert<List<Raster<GPU>>, WgpuExecutorHandle> for List<Raster<GPU>> {
fn convert(self, _: Footprint, _converter: WgpuExecutorHandle) -> List<Raster<GPU>> {
self
}
}
/// Converts a `List<Raster<CPU>>` to `List<Raster<GPU>>` by uploading each image to a texture
impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> List<Raster<GPU>> {
impl Convert<List<Raster<GPU>>, WgpuExecutorHandle> for List<Raster<CPU>> {
fn convert(self, _: Footprint, executor: WgpuExecutorHandle) -> List<Raster<GPU>> {
let device = &executor.context().device;
let queue = executor.context().queue.lock();
let list = self
@@ -68,8 +69,8 @@ impl<'i> Convert<List<Raster<GPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
}
/// Converts single CPU raster to GPU by uploading to texture
impl<'i> Convert<Raster<GPU>, &'i WgpuExecutor> for Raster<CPU> {
fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> Raster<GPU> {
impl Convert<Raster<GPU>, WgpuExecutorHandle> for Raster<CPU> {
fn convert(self, _: Footprint, executor: WgpuExecutorHandle) -> Raster<GPU> {
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<Raster<GPU>, &'i WgpuExecutor> for Raster<CPU> {
}
/// Passthrough conversion for CPU `List`s - no conversion needed
impl<'i> Convert<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<CPU>> {
fn convert(self, _: Footprint, _converter: &'i WgpuExecutor) -> List<Raster<CPU>> {
impl Convert<List<Raster<CPU>>, WgpuExecutorHandle> for List<Raster<CPU>> {
fn convert(self, _: Footprint, _converter: WgpuExecutorHandle) -> List<Raster<CPU>> {
self
}
}
@@ -191,8 +192,8 @@ impl RasterGpuToRasterCpuConverter {
}
/// Converts a `List<Raster<GPU>>` to `List<Raster<CPU>>` by downloading texture data in one go then asynchronously maps all buffers and processes the results.
impl<'i> ConvertAsync<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<GPU>> {
fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> SourceFuture<List<Raster<CPU>>> {
impl ConvertAsync<List<Raster<CPU>>, WgpuExecutorHandle> for List<Raster<GPU>> {
fn convert(self, _: Footprint, executor: WgpuExecutorHandle) -> SourceFuture<List<Raster<CPU>>> {
let device = executor.context().device.clone();
let queue = executor.context().queue.lock();
@@ -235,8 +236,8 @@ impl<'i> ConvertAsync<List<Raster<CPU>>, &'i WgpuExecutor> for List<Raster<GPU>>
}
/// Converts single GPU raster to CPU by downloading texture data
impl<'i> ConvertAsync<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {
fn convert(self, _: Footprint, executor: &'i WgpuExecutor) -> SourceFuture<Raster<CPU>> {
impl ConvertAsync<Raster<CPU>, WgpuExecutorHandle> for Raster<GPU> {
fn convert(self, _: Footprint, executor: WgpuExecutorHandle) -> SourceFuture<Raster<CPU>> {
let device = executor.context().device.clone();
let queue = executor.context().queue.lock();
@@ -256,10 +257,10 @@ impl<'i> ConvertAsync<Raster<CPU>, &'i WgpuExecutor> for Raster<GPU> {
///
/// 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<List<Raster<GPU>>, &'a WgpuExecutor>>(
pub fn upload_texture<T: Convert<List<Raster<GPU>>, WgpuExecutorHandle>>(
_: impl Ctx,
#[implementations(List<Raster<CPU>>, List<Raster<GPU>>)] input: T,
executor: &'a WgpuExecutor,
executor: WgpuExecutorHandle,
) -> List<Raster<GPU>> {
input.convert(Footprint::DEFAULT, executor)
}

View File

@@ -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<PlatformEditorApi>) -> Arc<PlatformEditorApi> {
editor_api
}
#[node_macro::node(category(""))]
pub fn resource<'a>(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: &'a PlatformEditorApi) -> SourceFuture<GPoll<Resource>> {
pub fn resource(_: impl Ctx, hash: ResourceHash, #[scope(editor_api::IDENTIFIER)] editor_api: Arc<PlatformEditorApi>) -> SourceFuture<GPoll<Resource>> {
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<PlatformEditorApi>) -> ::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<PlatformEditorApi>) -> Option<::wgpu_executor::WgpuExecutorHandle> {
editor_api.application_io.as_ref()?.gpu_executor_arc().map(::wgpu_executor::WgpuExecutorHandle)
}

View File

@@ -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<wgpu_executor::WgpuExecutorHandle>,
#[data] pipeline: WgpuPipelineCache,
) -> WgpuPipelineCache {
if let Some(executor) = executor {

View File

@@ -322,10 +322,10 @@ fn flood_fill(start: &TileCoord, tile_set: &HashSet<TileCoord>, 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<wgpu_executor::WgpuExecutorHandle>,
#[scope(crate::platform_application_io::editor_api::IDENTIFIER)] editor_api: std::sync::Arc<PlatformEditorApi>,
data: impl Node<Context<'_>, Output = RenderOutput>,
#[data] tile_cache: TileCache,
) -> Result<RenderOutput, Interrupt> {
@@ -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),

View File

@@ -72,9 +72,9 @@ fn render_intermediate<T: 'static + Render + WasmNotSend + Send + Sync>(
}
#[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<wgpu_executor::WgpuExecutorHandle>,
data: RenderIntermediate,
) -> RenderOutput {
let footprint = ctx.footprint();

View File

@@ -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<wgpu_executor::WgpuExecutorHandle>,
#[data] pipeline: WgpuPipelineCache,
) -> WgpuPipelineCache {
if let Some(executor) = executor {