Remove vestigial async from the execution path

This commit is contained in:
Dennis Kobert
2026-07-30 20:16:04 +00:00
parent 9e248cf90c
commit 07b95ba59b
6 changed files with 59 additions and 69 deletions

View File

@@ -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<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, String)> {
fn update_network(&mut self, graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, String)> {
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::<Vec<_>>();
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<TaggedValue, String> {
fn execute_network(&mut self, render_config: RenderConfig) -> Result<TaggedValue, String> {
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<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
pub fn introspect_node(path: &[NodeId]) -> Result<Arc<dyn std::any::Any + Send + Sync + 'static>, IntrospectError> {
let runtime = NODE_RUNTIME.lock();
if let Some(ref mut runtime) = runtime.as_ref() {
return runtime.executor.introspect(path);

View File

@@ -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<I, O> {
fn execute(&self, input: I) -> LocalFuture<'_, Result<O, Box<dyn Error>>>;
fn execute(&self, input: I) -> Result<O, Box<dyn Error>>;
}

View File

@@ -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<TaggedValue, Box<dyn Error>> {
fn execute_until_final(executor: &DynamicExecutor, render_config: RenderConfig, completion: &Receiver<()>) -> Result<TaggedValue, Box<dyn Error>> {
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<FileType, String> {
}
}
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::<GPU>::new_gpu(texture);
let cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor.clone()).await;
let cpu_raster: Raster<CPU> = 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::<GPU>::new_gpu(texture);
let cpu_raster: Raster<CPU> = gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor.clone()).await;
let cpu_raster: Raster<CPU> = block_on(gpu_raster.convert(Footprint::BOUNDLESS, wgpu_executor.clone()));
cpu_raster.to_flat_u8()
}
RenderOutputType::Buffer { data, width, height } => (data, width, height),

View File

@@ -125,8 +125,7 @@ struct GlobalOpts {
verbose: u8,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), Box<dyn Error>> {
let app = App::parse();
let log_level = app.global_opts.verbose;
@@ -154,9 +153,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
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<dyn Error>> {
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<dyn Error>> {
// 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<dyn Error>> {
};
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<dyn Error>> {
// 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<PlatformEditorApi>, gdd:
}
fn create_executor(proto_network: ProtoNetwork, runtime: Arc<DynGraphRuntime>) -> Result<DynamicExecutor, Box<dyn Error>> {
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)
}

View File

@@ -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<Self, GraphErrors> {
pub fn new(proto_network: ProtoNetwork) -> Result<Self, GraphErrors> {
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<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, GraphErrors)> {
pub fn update(&mut self, proto_network: ProtoNetwork) -> Result<ResolvedDocumentNodeTypesDelta, (ResolvedDocumentNodeTypesDelta, GraphErrors)> {
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<I> Executor<I, GPoll<TaggedValue>> for &DynamicExecutor
where
I: VarArg + Send + Sync + std::panic::RefUnwindSafe,
{
fn execute(&self, input: I) -> LocalFuture<'_, Result<GPoll<TaggedValue>, Box<dyn Error>>> {
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<GPoll<TaggedValue>, Box<dyn Error>> {
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<S, T>(arena: &mut Arena, runtime: &GraphRuntime<S>, call_argument: DynSlot, eval: impl FnOnce(&ContextImpl) -> GPoll<T>) -> GPoll<T> {
@@ -264,23 +258,23 @@ pub struct BorrowTree {
}
impl BorrowTree {
pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
pub fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<BorrowTree, GraphErrors> {
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<Path>, HashSet<NodeId>), GraphErrors> {
pub fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<Path>, HashSet<NodeId>), 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);

View File

@@ -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();
}
}