From 07b95ba59b6628c858fd902d4ad99c77c791f018 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Thu, 30 Jul 2026 20:16:04 +0000 Subject: [PATCH] Remove vestigial async from the execution path --- editor/src/node_graph_executor/runtime.rs | 16 ++--- .../graph-craft/src/graphene_compiler.rs | 4 +- node-graph/graphene-cli/src/export.rs | 17 ++--- node-graph/graphene-cli/src/main.rs | 19 +++-- .../src/dynamic_executor.rs | 69 +++++++++---------- node-graph/interpreted-executor/src/lib.rs | 3 +- 6 files changed, 59 insertions(+), 69 deletions(-) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index c35d667a4e..c5af3f6b44 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -268,7 +268,7 @@ impl NodeRuntime { .into(); if let Some(graph) = self.old_graph.clone() { // We ignore this result as compilation errors should have been reported in an earlier iteration - let _ = self.update_network(graph).await; + let _ = self.update_network(graph); } } GraphRuntimeRequest::GraphUpdate(GraphUpdate { @@ -283,7 +283,7 @@ impl NodeRuntime { self.resources = resources; self.node_graph_errors.clear(); - let result = self.update_network(network).await; + let result = self.update_network(network); let node_graph_errors = self.node_graph_errors.clone(); self.update_thumbnails = true; @@ -298,7 +298,7 @@ impl NodeRuntime { render_config.export_format = ExportFormat::Svg; } - let result = self.execute_network(render_config).await; + let result = self.execute_network(render_config); let mut responses = VecDeque::new(); // TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes if !render_config.for_eyedropper { @@ -406,7 +406,7 @@ impl NodeRuntime { None } - async fn update_network(&mut self, graph: NodeNetwork) -> Result { + fn update_network(&mut self, graph: NodeNetwork) -> Result { let mut scoped_network = wrap_network_in_scope(graph, self.editor_api.clone()); if let Err(e) = self.preprocessor.preprocess(&mut scoped_network, &|resource_id| self.resources.hash(&resource_id)) { @@ -429,16 +429,16 @@ impl NodeRuntime { .collect::>(); assert_ne!(proto_network.nodes.len(), 0, "No proto nodes exist?"); - self.executor.update(proto_network).await.map_err(|(types, e)| { + self.executor.update(proto_network).map_err(|(types, e)| { self.node_graph_errors.clone_from(&e); (types, format!("{e:?}")) }) } - async fn execute_network(&mut self, render_config: RenderConfig) -> Result { + fn execute_network(&mut self, render_config: RenderConfig) -> Result { use graph_craft::graphene_compiler::Executor; - match (&self.executor).execute(render_config).await.map_err(|e| e.to_string())? { + match (&self.executor).execute(render_config).map_err(|e| e.to_string())? { GPoll::Final(value) | GPoll::Partial(value) => Ok(value), GPoll::Fallback(boxed) => { let (value, error) = *boxed; @@ -609,7 +609,7 @@ fn expand_to_thumbnail_aspect(bounds: [DVec2; 2]) -> [DVec2; 2] { [center - half, center + half] } -pub async fn introspect_node(path: &[NodeId]) -> Result, IntrospectError> { +pub fn introspect_node(path: &[NodeId]) -> Result, IntrospectError> { let runtime = NODE_RUNTIME.lock(); if let Some(ref mut runtime) = runtime.as_ref() { return runtime.executor.introspect(path); diff --git a/node-graph/graph-craft/src/graphene_compiler.rs b/node-graph/graph-craft/src/graphene_compiler.rs index 22dac658a9..fbf5be5a65 100644 --- a/node-graph/graph-craft/src/graphene_compiler.rs +++ b/node-graph/graph-craft/src/graphene_compiler.rs @@ -1,5 +1,5 @@ use crate::document::NodeNetwork; -use crate::proto::{LocalFuture, ProtoNetwork}; +use crate::proto::ProtoNetwork; use std::error::Error; pub struct Compiler {} @@ -33,5 +33,5 @@ impl Compiler { } pub trait Executor { - fn execute(&self, input: I) -> LocalFuture<'_, Result>>; + fn execute(&self, input: I) -> Result>; } diff --git a/node-graph/graphene-cli/src/export.rs b/node-graph/graphene-cli/src/export.rs index 7f76022769..ecec073ee9 100644 --- a/node-graph/graphene-cli/src/export.rs +++ b/node-graph/graphene-cli/src/export.rs @@ -1,3 +1,4 @@ +use futures::executor::block_on; use graph_craft::document::value::{RenderOutputType, TaggedValue, UVec2}; use graph_craft::graphene_compiler::Executor; use graphene_std::application_io::{ExportFormat, RenderConfig, TimingInformation}; @@ -14,10 +15,10 @@ use std::time::Duration; const SOURCE_COMPLETION_TIMEOUT: Duration = Duration::from_secs(30); -async fn execute_until_final(executor: &DynamicExecutor, render_config: RenderConfig, completion: &Receiver<()>) -> Result> { +fn execute_until_final(executor: &DynamicExecutor, render_config: RenderConfig, completion: &Receiver<()>) -> Result> { loop { while completion.try_recv().is_ok() {} - match executor.execute(render_config.clone()).await? { + match executor.execute(render_config.clone())? { GPoll::Final(value) => return Ok(value), GPoll::Fallback(boxed) => { let (value, error) = *boxed; @@ -52,7 +53,7 @@ pub fn detect_file_type(path: &Path) -> Result { } } -pub async fn export_document( +pub fn export_document( executor: &DynamicExecutor, wgpu_executor: wgpu_executor::WgpuExecutorHandle, output_path: PathBuf, @@ -82,7 +83,7 @@ pub async fn export_document( } // Execute the graph - let result = execute_until_final(executor, render_config, completion).await?; + let result = execute_until_final(executor, render_config, completion)?; // Handle the result based on output type match result { @@ -95,7 +96,7 @@ pub async fn export_document( RenderOutputType::Texture(texture) => { // Convert GPU texture to CPU buffer let gpu_raster = Raster::::new_gpu(texture); - let cpu_raster: Raster = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor.clone()).await; + let cpu_raster: Raster = block_on(gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor.clone())); let (data, width, height) = cpu_raster.to_flat_u8(); // Encode and write raster image @@ -174,7 +175,7 @@ impl AnimationParams { } /// Export an animated GIF by rendering multiple frames at different animation times -pub async fn export_gif( +pub fn export_gif( executor: &DynamicExecutor, wgpu_executor: wgpu_executor::WgpuExecutorHandle, output_path: PathBuf, @@ -221,14 +222,14 @@ pub async fn export_gif( } // Execute the graph for this frame - let result = execute_until_final(executor, render_config, completion).await?; + let result = execute_until_final(executor, render_config, completion)?; // Extract RGBA data from result let (data, img_width, img_height) = match result { TaggedValue::RenderOutput(output) => match output.data { RenderOutputType::Texture(texture) => { let gpu_raster = Raster::::new_gpu(texture); - let cpu_raster: Raster = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor.clone()).await; + let cpu_raster: Raster = block_on(gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor.clone())); cpu_raster.to_flat_u8() } RenderOutputType::Buffer { data, width, height } => (data, width, height), diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index a4d9562461..61a4a66068 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -125,8 +125,7 @@ struct GlobalOpts { verbose: u8, } -#[tokio::main] -async fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { let app = App::parse(); let log_level = app.global_opts.verbose; @@ -154,9 +153,7 @@ async fn main() -> Result<(), Box> { let gdd = if is_gdd { let archive = std::fs::read(document_path).map_err(|error| format!("Failed to read document {}: {error}", document_path.display()))?; let container = AnyContainer::Memory(MemoryBackend::new()); - let gdd = document_format::Gdd::open_from_archive(archive.as_ref(), container, GddV1Layout) - .await - .map_err(|error| format!("Failed to open document: {error}"))?; + let gdd = block_on(document_format::Gdd::open_from_archive(archive.as_ref(), container, GddV1Layout)).map_err(|error| format!("Failed to open document: {error}"))?; Some(gdd) } else { None @@ -164,7 +161,7 @@ async fn main() -> Result<(), Box> { if let Command::ExtractLegacyDoc { ref document } = app.command { let Some(gdd) = &gdd else { return Err("ExtractLegacyDoc requires a .gdd document".into()) }; - let Some(legacy_doc) = gdd.read_legacy_document().await else { + let Some(legacy_doc) = block_on(gdd.read_legacy_document()) else { return Err("gdd file did not contain a legacy .graphite document".into()); }; let mut new_path = document.clone(); @@ -177,7 +174,7 @@ async fn main() -> Result<(), Box> { // Build the runtime network: from the `.gdd` registry, or by loading a legacy `.graphite` document. let node_network = match &gdd { Some(gdd) => { - let declarations = gdd.declarations(gdd).await; + let declarations = block_on(gdd.declarations(gdd)); let (node_network, _metadata) = gdd.registry().to_runtime_with_metadata(&declarations)?; node_network } @@ -188,7 +185,7 @@ async fn main() -> Result<(), Box> { }; log::info!("Creating GPU context"); - let mut application_io = PlatformApplicationIo::new().await; + let mut application_io = block_on(PlatformApplicationIo::new()); if let Some(gdd) = &gdd { application_io.inject_resource_proxy(Box::new(gdd.resource_proxy())); } @@ -257,9 +254,9 @@ async fn main() -> Result<(), Box> { // Perform export based on file type if file_type == export::FileType::Gif { let animation = export::AnimationParams::new(fps, frames, duration); - export::export_gif(&executor, wgpu_executor_ref.clone(), output, scale, (width, height), animation, &completion_receiver).await?; + export::export_gif(&executor, wgpu_executor_ref.clone(), output, scale, (width, height), animation, &completion_receiver)?; } else { - export::export_document(&executor, wgpu_executor_ref.clone(), output, file_type, scale, (width, height), transparent, &completion_receiver).await?; + export::export_document(&executor, wgpu_executor_ref.clone(), output, file_type, scale, (width, height), transparent, &completion_receiver)?; } } _ => unreachable!("All other commands should be handled before this match statement is run"), @@ -316,7 +313,7 @@ fn compile_graph(network: NodeNetwork, editor_api: Arc, gdd: } fn create_executor(proto_network: ProtoNetwork, runtime: Arc) -> Result> { - let mut executor = block_on(DynamicExecutor::new(proto_network)).map_err(|errors| errors.iter().map(|e| format!("{e:?}")).reduce(|acc, e| format!("{acc}\n{e}")).unwrap_or_default())?; + let mut executor = DynamicExecutor::new(proto_network).map_err(|errors| errors.iter().map(|e| format!("{e:?}")).reduce(|acc, e| format!("{acc}\n{e}")).unwrap_or_default())?; executor.set_runtime(runtime); Ok(executor) } diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 74ea205699..c5879d3d41 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -9,7 +9,7 @@ use graph_craft::Type; use graph_craft::document::NodeId; use graph_craft::document::value::TaggedValue; use graph_craft::graphene_compiler::Executor; -use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, ProtoNetwork, ProtoNode, TypingContext}; +use graph_craft::proto::{ConstructionArgs, GraphError, ProtoNetwork, ProtoNode, TypingContext}; use graph_craft::proto::{GraphErrorType, GraphErrors}; use std::collections::{HashMap, HashSet}; use std::error::Error; @@ -65,12 +65,12 @@ pub struct ResolvedDocumentNodeTypesDelta { } impl DynamicExecutor { - pub async fn new(proto_network: ProtoNetwork) -> Result { + pub fn new(proto_network: ProtoNetwork) -> Result { let mut typing_context = TypingContext::new(&node_registry::NODE_REGISTRY); typing_context.update(&proto_network)?; let output = proto_network.output; let sources = proto_network.source_ids(); - let tree = BorrowTree::new(proto_network, &typing_context).await?; + let tree = BorrowTree::new(proto_network, &typing_context)?; let runtime = noop_runtime(); runtime.retain_sources(&sources); @@ -96,7 +96,7 @@ impl DynamicExecutor { /// Updates the existing [`BorrowTree`] to reflect the new [`ProtoNetwork`], reusing nodes where possible. #[cfg_attr(debug_assertions, inline(never))] - pub async fn update(&mut self, proto_network: ProtoNetwork) -> Result { + pub fn update(&mut self, proto_network: ProtoNetwork) -> Result { self.output = proto_network.output; self.typing_context.update(&proto_network).map_err(|e| { // If there is an error then get types that have been resolved before the error @@ -120,11 +120,7 @@ impl DynamicExecutor { })?; let sources = proto_network.source_ids(); - let (add, orphaned) = self - .tree - .update(proto_network, &self.typing_context) - .await - .map_err(|e| (ResolvedDocumentNodeTypesDelta::default(), e))?; + let (add, orphaned) = self.tree.update(proto_network, &self.typing_context).map_err(|e| (ResolvedDocumentNodeTypesDelta::default(), e))?; self.runtime.retain_sources(&sources); self.live_sources = sources; let old_to_remove = core::mem::replace(&mut self.orphaned_nodes, orphaned); @@ -174,27 +170,25 @@ impl Executor> for &DynamicExecutor where I: VarArg + Send + Sync + std::panic::RefUnwindSafe, { - fn execute(&self, input: I) -> LocalFuture<'_, Result, Box>> { - Box::pin(async move { - let Some(handle) = self.tree.get(self.output) else { - return Err("Output node not found in executor".into()); - }; - let mut arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner); - let result = eval_root(&mut arena, &self.runtime, &input, |ctx| match TaggedValue::from_edge(handle.duplicate(), ctx) { - Ok(poll) => poll.map(Ok), - Err(error) => GPoll::Final(Err(error)), - }); - match result { - GPoll::Final(value) => Ok(GPoll::Final(value?)), - GPoll::Partial(value) => Ok(GPoll::Partial(value?)), - GPoll::Fallback(boxed) => { - let (value, error) = *boxed; - Ok(GPoll::Fallback(Box::new((value?, error)))) - } - GPoll::Pending => Ok(GPoll::Pending), - GPoll::Error(error) => Ok(GPoll::Error(error)), + fn execute(&self, input: I) -> Result, Box> { + let Some(handle) = self.tree.get(self.output) else { + return Err("Output node not found in executor".into()); + }; + let mut arena = self.arena.lock().unwrap_or_else(PoisonError::into_inner); + let result = eval_root(&mut arena, &self.runtime, &input, |ctx| match TaggedValue::from_edge(handle.duplicate(), ctx) { + Ok(poll) => poll.map(Ok), + Err(error) => GPoll::Final(Err(error)), + }); + match result { + GPoll::Final(value) => Ok(GPoll::Final(value?)), + GPoll::Partial(value) => Ok(GPoll::Partial(value?)), + GPoll::Fallback(boxed) => { + let (value, error) = *boxed; + Ok(GPoll::Fallback(Box::new((value?, error)))) } - }) + GPoll::Pending => Ok(GPoll::Pending), + GPoll::Error(error) => Ok(GPoll::Error(error)), + } } } pub fn eval_root(arena: &mut Arena, runtime: &GraphRuntime, call_argument: DynSlot, eval: impl FnOnce(&ContextImpl) -> GPoll) -> GPoll { @@ -264,23 +258,23 @@ pub struct BorrowTree { } impl BorrowTree { - pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result { + pub fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result { let mut nodes = BorrowTree::default(); for (id, node) in proto_network.nodes { - nodes.push_node(id, node, typing_context).await? + nodes.push_node(id, node, typing_context)? } Ok(nodes) } /// Pushes new nodes into the tree and return orphaned nodes - pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec, HashSet), GraphErrors> { + pub fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec, HashSet), GraphErrors> { let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect(); let mut new_nodes: Vec<_> = Vec::new(); // TODO: Problem: When a passthrough node is connected directly to an export the first input to the passthrough node is not added to the proto network, while the second input is. This means the primary input does not have a type. for (id, node) in proto_network.nodes { if !self.nodes.contains_key(&id) { new_nodes.push(node.original_location.path.clone().unwrap_or_default().into()); - self.push_node(id, node, typing_context).await?; + self.push_node(id, node, typing_context)?; } else if self.update_source_map(id, typing_context, &node) { new_nodes.push(node.original_location.path.clone().unwrap_or_default().into()); } @@ -342,10 +336,10 @@ impl BorrowTree { /// use interpreted_executor::node_registry; /// /// - /// async fn example() -> Result<(), GraphErrors> { + /// fn example() -> Result<(), GraphErrors> { /// let (proto_network, node_id, proto_node) = ProtoNetwork::example(); /// let typing_context = TypingContext::default(); - /// let mut borrow_tree = BorrowTree::new(proto_network, &typing_context).await?; + /// let mut borrow_tree = BorrowTree::new(proto_network, &typing_context)?; /// /// // Assert that the node exists in the BorrowTree /// assert!(borrow_tree.get(node_id).is_some(), "Node should exist before removal"); @@ -442,7 +436,7 @@ impl BorrowTree { /// - `Nodes`: Constructs a node using other nodes as dependencies. /// - Uses the constructor function from the `typing_context` for `Nodes` construction arguments. /// - Returns an error if no constructor is found for the given node ID. - async fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> { + fn push_node(&mut self, id: NodeId, proto_node: ProtoNode, typing_context: &TypingContext) -> Result<(), GraphErrors> { self.update_source_map(id, typing_context, &proto_node); let path = proto_node.original_location.path.clone().unwrap_or_default(); @@ -534,8 +528,7 @@ mod test { let mut tree = BorrowTree::default(); let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32).into()), vec![]); let context = TypingContext::default(); - let future = tree.push_node(NodeId(0), val_1_protonode, &context); - futures::executor::block_on(future).unwrap(); + tree.push_node(NodeId(0), val_1_protonode, &context).unwrap(); let _node = tree.get(NodeId(0)).unwrap(); let arena = Arena::new(64); diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 68081de5a8..44fca1526a 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -5,7 +5,6 @@ pub mod util; #[cfg(test)] mod tests { use core_types::*; - use futures::executor::block_on; use graphene_core::ops::passthrough; #[test] @@ -47,6 +46,6 @@ mod tests { let compiler = Compiler {}; let protograph = compiler.compile_single(network).expect("Graph should be generated"); - let _exec = block_on(DynamicExecutor::new(protograph)).map(|_e| panic!("The network should not type check ")).unwrap_err(); + let _exec = DynamicExecutor::new(protograph).map(|_e| panic!("The network should not type check ")).unwrap_err(); } }