From 26473a80026c4e2c119c892f2e0ff5638768fbf2 Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Sat, 3 Jun 2023 01:18:44 +0200 Subject: [PATCH] Restructure node graph execution to be safer (#1277) * Reorganize file structure * Remove all unsafe code * Add testcase for debugging ub * Convert into proper test with fail condition * General cleanup * Fix tests * Add feature guard for deallocation * Use raw pointer for storing values to avoid violating aliasing rules * Add comment explaining the disabling of simd128 * Fix brush node * Fix formatting --- Cargo.lock | 8 + editor/Cargo.toml | 1 + editor/src/application.rs | 58 ++++++- editor/src/messages/message.rs | 2 - .../document/document_message_handler.rs | 4 - .../graph_operation_message_handler.rs | 6 +- .../document_node_types.rs | 3 +- .../messages/tool/tool_messages/brush_tool.rs | 13 +- editor/src/node_graph_executor.rs | 31 ++-- frontend/wasm/.cargo/config.toml | 10 +- libraries/dyn-any/src/lib.rs | 8 +- node-graph/compilation-client/src/main.rs | 2 +- node-graph/gcore/src/lib.rs | 19 ++- node-graph/graph-craft/Cargo.toml | 3 +- node-graph/graph-craft/src/document.rs | 4 +- node-graph/graph-craft/src/document/value.rs | 2 +- .../src/{executor.rs => graphene_compiler.rs} | 0 node-graph/graph-craft/src/lib.rs | 2 +- node-graph/graph-craft/src/proto.rs | 59 ++++++- node-graph/gstd/src/any.rs | 61 +++---- node-graph/gstd/src/brush.rs | 90 +++++++++- node-graph/gstd/src/executor.rs | 6 +- node-graph/gstd/src/raster.rs | 2 +- node-graph/interpreted-executor/Cargo.toml | 1 + .../src/{executor.rs => dynamic_executor.rs} | 89 +++------- node-graph/interpreted-executor/src/lib.rs | 15 +- .../interpreted-executor/src/node_registry.rs | 159 ++++-------------- node-graph/vulkan-executor/src/executor.rs | 2 +- node-graph/wgpu-executor/src/executor.rs | 2 +- 29 files changed, 363 insertions(+), 299 deletions(-) rename node-graph/graph-craft/src/{executor.rs => graphene_compiler.rs} (100%) rename node-graph/interpreted-executor/src/{executor.rs => dynamic_executor.rs} (64%) diff --git a/Cargo.lock b/Cargo.lock index cf95b933..6e0cbb89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1762,6 +1762,7 @@ dependencies = [ "derivative", "dyn-any", "env_logger", + "futures", "glam", "graph-craft", "graphene-core", @@ -2206,6 +2207,7 @@ dependencies = [ "num-traits", "once_cell", "serde", + "typed-arena", ] [[package]] @@ -5010,6 +5012,12 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.16.0" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index f8008d94..29c26157 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -50,3 +50,4 @@ package = "graphite-document-legacy" [dev-dependencies] env_logger = "0.8.4" test-case = "2.1" +futures = "0.3.28" diff --git a/editor/src/application.rs b/editor/src/application.rs index 1fe21fe3..46c62928 100644 --- a/editor/src/application.rs +++ b/editor/src/application.rs @@ -18,10 +18,7 @@ impl Editor { pub fn handle_message>(&mut self, message: T) -> Vec { self.dispatcher.handle_message(message); - let mut responses = Vec::new(); - std::mem::swap(&mut responses, &mut self.dispatcher.responses); - - responses + std::mem::take(&mut self.dispatcher.responses) } pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque) { @@ -62,3 +59,56 @@ pub fn commit_hash() -> String { pub fn commit_branch() -> String { format!("Branch: {}", env!("GRAPHITE_GIT_COMMIT_BRANCH")) } + +#[cfg(test)] +mod test { + use crate::messages::{input_mapper::utility_types::input_mouse::ViewportBounds, prelude::*}; + + #[test] + fn debug_ub() { + let mut editor = super::Editor::new(); + let mut responses = Vec::new(); + use super::Message::*; + + let messages: Vec = vec![ + Init, + Preferences( + PreferencesMessage::Load { + preferences: r#"{"imaginate_server_hostname":"https://exchange-encoding-watched-insured.trycloudflare.com/","imaginate_refresh_frequency":1,"zoom_with_scroll":false}"#.to_string(), + }, + ), + PortfolioMessage::OpenDocumentFileWithId { + document_id: 0, + document_name: "".into(), + document_is_auto_saved: true, + document_is_saved: true, + document_serialized_content: +r#" +{"document_legacy":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":12825788055422975214,"layer_ids":[12825788055422975213],"layers":[{"visible":true,"name":null,"data":{"Layer":{"network":{"inputs":[0],"outputs":[{"node_id":1,"node_output_index":0}],"nodes":{"0":{"name":"Input Frame","inputs":[{"Network":{"Concrete":{"name":"graphene_core::application_io::EditorApi","size":80,"align":8}}}],"implementation":{"Network":{"inputs":[0],"outputs":[{"node_id":0,"node_output_index":0}],"nodes":{"0":{"name":"Input Frame_impl","inputs":[{"Network":{"Concrete":{"name":"graphene_core::application_io::EditorApi","size":80,"align":8}}}],"implementation":{"Unresolved":{"name":"graphene_core::ExtractImageFrame"}},"metadata":{"position":[0,0]},"path":null}},"disabled":[],"previous_outputs":null}},"metadata":{"position":[8,4]},"path":null},"11577035356642256919":{"name":"Transform","inputs":[{"Node":{"node_id":0,"output_index":0,"lambda":false}},{"Value":{"tagged_value":{"DVec2":[703.2276466129997,473.0379249237632]},"exposed":false}},{"Value":{"tagged_value":{"F64":0.0},"exposed":false}},{"Value":{"tagged_value":{"DVec2":[345.616055733087,237.05356066324276]},"exposed":false}},{"Value":{"tagged_value":{"DVec2":[0.0,0.0]},"exposed":false}},{"Value":{"tagged_value":{"DVec2":[0.5,0.5]},"exposed":false}}],"implementation":{"Network":{"inputs":[0,0,0,0,0,0],"outputs":[{"node_id":0,"node_output_index":0}],"nodes":{"0":{"name":"Transform_impl","inputs":[{"Network":{"Concrete":{"name":"graphene_core::vector::vector_data::VectorData","size":248,"align":8}}},{"Network":{"Concrete":{"name":"glam::f64::dvec2::DVec2","size":16,"align":8}}},{"Network":{"Concrete":{"name":"f64","size":8,"align":8}}},{"Network":{"Concrete":{"name":"glam::f64::dvec2::DVec2","size":16,"align":8}}},{"Network":{"Concrete":{"name":"glam::f64::dvec2::DVec2","size":16,"align":8}}},{"Network":{"Concrete":{"name":"glam::f64::dvec2::DVec2","size":16,"align":8}}}],"implementation":{"Unresolved":{"name":"graphene_core::transform::TransformNode<_, _, _, _, _>"}},"metadata":{"position":[0,0]},"path":null}},"disabled":[],"previous_outputs":null}},"metadata":{"position":[16,4]},"path":null},"1":{"name":"Output","inputs":[{"Node":{"node_id":11577035356642256919,"output_index":0,"lambda":false}}],"implementation":{"Network":{"inputs":[0],"outputs":[{"node_id":0,"node_output_index":0}],"nodes":{"0":{"name":"Output_impl","inputs":[{"Network":{"Concrete":{"name":"graphene_core::raster::image::ImageFrame","size":72,"align":8}}}],"implementation":{"Unresolved":{"name":"graphene_core::ops::IdNode"}},"metadata":{"position":[0,0]},"path":null}},"disabled":[],"previous_outputs":null}},"metadata":{"position":[24,4]},"path":null}},"disabled":[],"previous_outputs":null}}},"transform":{"matrix2":[345.616055733087,0.0,-0.0,237.05356066324276],"translation":[530.919618746456,355.01114459214176]},"preserve_aspect":true,"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[0.5833333598242877,0.0,0.0,0.5833333598242877],"translation":[11.0,214.99999999999994]},"preserve_aspect":true,"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0},"document_network":{"inputs":[],"outputs":[{"node_id":0,"node_output_index":0}],"nodes":{"0":{"name":"Output","inputs":[{"Value":{"tagged_value":{"GraphicGroup":[]},"exposed":true}}],"implementation":{"Unresolved":{"name":"graphene_core::ops::IdNode"}},"metadata":{"position":[8,4]},"path":null}},"disabled":[],"previous_outputs":null}},"saved_document_identifier":0,"auto_saved_document_identifier":0,"name":"Untitled Document","version":"0.0.16","document_mode":"DesignMode","view_mode":"Normal","snapping_enabled":true,"overlays_visible":true,"layer_metadata":[[[],{"selected":false,"expanded":true}],[[12825788055422975213],{"selected":false,"expanded":false}]],"layer_range_selection_reference":[],"navigation_handler":{"pan":[-960.0,-540.5],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":0.5833333598242877,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":17677129199720758749,"layer_ids":[17677129199720758748],"layers":[{"visible":true,"name":null,"data":{"Shape":{"shape":{"elements":[{"points":[{"position":[0.0,0.0],"manipulator_type":"Anchor"},null,null]},{"points":[{"position":[0.0,1.0],"manipulator_type":"Anchor"},null,null]},{"points":[{"position":[1.0,1.0],"manipulator_type":"Anchor"},null,null]},{"points":[{"position":[1.0,0.0],"manipulator_type":"Anchor"},null,null]},{"points":[null,null,null]}],"element_ids":[1,2,3,4,5],"next_id":5},"style":{"stroke":null,"fill":{"Solid":{"red":1.0,"green":1.0,"blue":1.0,"alpha":1.0}}},"render_index":1}},"transform":{"matrix2":[1920.0,0.0,-0.0,1080.0],"translation":[0.0,0.0]},"preserve_aspect":true,"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[0.5833333598242877,0.0,0.0,0.5833333598242877],"translation":[11.0,214.99999999999994]},"preserve_aspect":true,"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0},"document_network":{"inputs":[],"outputs":[{"node_id":0,"node_output_index":0}],"nodes":{"0":{"name":"Output","inputs":[{"Value":{"tagged_value":{"GraphicGroup":[]},"exposed":true}}],"implementation":{"Unresolved":{"name":"graphene_core::ops::IdNode"}},"metadata":{"position":[8,4]},"path":null}},"disabled":[],"previous_outputs":null}},"artboard_ids":[17677129199720758748]},"properties_panel_message_handler":{"active_selection":null}} +"#.into(), + }.into(), + InputPreprocessorMessage::BoundsOfViewports{bounds_of_viewports: vec![ViewportBounds::from_slice(&[0., 0., 1920., 1080.])]}.into(), + ]; + + use futures::executor::block_on; + for message in messages { + block_on(crate::node_graph_executor::run_node_graph()); + let mut res = VecDeque::new(); + editor.poll_node_graph_evaluation(&mut res); + //println!("node_graph_poll: {:#?}", res); + + //println!("in: {:#?}", message); + let res = editor.handle_message(message); + //println!("out: {:#?}", res); + responses.push(res); + } + let responses = responses.pop().unwrap(); + let trigger_message = responses[responses.len() - 2].clone(); + if let FrontendMessage::TriggerRasterizeRegionBelowLayer { size, .. } = trigger_message { + assert!(size.x > 0. && size.y > 0.); + } else { + panic!(); + } + println!("responses: {:#?}", responses); + } +} diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index c66fee41..368ff5ce 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -3,8 +3,6 @@ use crate::messages::prelude::*; use graphite_proc_macros::*; use serde::{Deserialize, Serialize}; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; #[remain::sorted] #[impl_message] diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 29d628b1..5a4d0218 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1001,10 +1001,6 @@ impl DocumentMessageHandler { // Calculate the size of the region to be exported and generate an SVG of the artwork below this layer within that region let transform = self.document_legacy.multiply_transforms(&layer_path).unwrap(); let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()); - // TODO: Fix this hack - // This is a hack to prevent the compiler from optimizing out the size calculation which likely is due - // to undefined behavior. THIS IS NOT A FIX. - log::trace!("size: {:?}", size); let svg = self.render_document(size, transform.inverse(), persistent_data, DocumentRenderMode::OnlyBelowLayerInFolder(&layer_path)); self.restore_document_transform(old_transforms); diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs index 0613ddcf..16d5e2d5 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs @@ -224,11 +224,7 @@ impl<'a> ModifyInputsContext<'a> { fn brush_modify(&mut self, strokes: Vec) { self.modify_inputs("Brush", false, |inputs| { - if matches!(inputs[0], NodeInput::Node { .. }) { - inputs[1] = core::mem::replace(&mut inputs[0], NodeInput::value(TaggedValue::None, false)); - } - inputs[0] = NodeInput::value(TaggedValue::None, false); - inputs[3] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false); + inputs[2] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false); }); } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index e2a7c98e..c655ac0b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -669,9 +669,8 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "Brush", category: "Brush", - identifier: NodeImplementation::proto("graphene_std::brush::BrushNode"), + identifier: NodeImplementation::proto("graphene_std::brush::BrushNode<_, _>"), inputs: vec![ - DocumentInputType::value("None", TaggedValue::None, false), DocumentInputType::value("Background", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Bounds", TaggedValue::ImageFrame(ImageFrame::empty()), true), DocumentInputType::value("Trace", TaggedValue::BrushStrokes(Vec::new()), false), diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index 138b988e..6c88bcb9 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -14,7 +14,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use document_legacy::layers::layer_layer::CachedOutputData; use document_legacy::LayerId; use graph_craft::document::value::TaggedValue; -use graph_craft::document::{NodeId, NodeInput, NodeNetwork}; +use graph_craft::document::{NodeInput, NodeNetwork}; use graphene_core::raster::{BlendMode, ImageFrame}; use graphene_core::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle}; use graphene_core::Color; @@ -300,7 +300,6 @@ impl ToolTransition for BrushTool { struct BrushToolData { strokes: Vec, layer_path: Vec, - node_path: Vec, transform: DAffine2, } @@ -315,7 +314,7 @@ impl BrushToolData { let network = &layer.network; for (node, _node_id) in network.primary_flow() { if node.name == "Brush" { - let points_input = node.inputs.get(3)?; + let points_input = node.inputs.get(2)?; let NodeInput::Value { tagged_value: TaggedValue::BrushStrokes(strokes), .. } = points_input else { continue; }; @@ -332,7 +331,7 @@ impl BrushToolData { matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)).then_some(&self.layer_path) } - fn update_strokes(&self, brush_options: &BrushOptions, responses: &mut VecDeque) { + fn update_strokes(&self, responses: &mut VecDeque) { let layer = self.layer_path.clone(); let strokes = self.strokes.clone(); responses.add(GraphOperationMessage::Brush { layer, strokes }); @@ -394,7 +393,7 @@ impl Fsm for BrushToolFsmState { if new_layer { add_brush_render(tool_options, tool_data, responses); } - tool_data.update_strokes(tool_options, responses); + tool_data.update_strokes(responses); BrushToolFsmState::Drawing } @@ -403,7 +402,7 @@ impl Fsm for BrushToolFsmState { if let Some(stroke) = tool_data.strokes.last_mut() { stroke.trace.push(BrushInputSample { position: layer_position }) } - tool_data.update_strokes(tool_options, responses); + tool_data.update_strokes(responses); BrushToolFsmState::Drawing } @@ -448,7 +447,7 @@ impl Fsm for BrushToolFsmState { } } -fn add_brush_render(tool_options: &BrushOptions, data: &BrushToolData, responses: &mut VecDeque) { +fn add_brush_render(_tool_options: &BrushOptions, data: &BrushToolData, responses: &mut VecDeque) { let mut network = NodeNetwork::default(); let output_node = network.push_output_node(); if let Some(node) = network.nodes.get_mut(&output_node) { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 60129071..8054d0a3 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -9,7 +9,7 @@ use document_legacy::{LayerId, Operation}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork}; -use graph_craft::executor::Compiler; +use graph_craft::graphene_compiler::Compiler; use graph_craft::{concrete, Type, TypeDescriptor}; use graphene_core::application_io::ApplicationIo; use graphene_core::raster::{Image, ImageFrame}; @@ -19,7 +19,7 @@ use graphene_core::vector::style::ViewMode; use graphene_core::wasm_application_io::WasmApplicationIo; use graphene_core::{Color, EditorApi, SurfaceFrame, SurfaceId}; -use interpreted_executor::executor::DynamicExecutor; +use interpreted_executor::dynamic_executor::DynamicExecutor; use glam::{DAffine2, DVec2}; use std::borrow::Cow; @@ -147,7 +147,7 @@ impl NodeRuntime { return Err(e); } - use graph_craft::executor::Executor; + use graph_craft::graphene_compiler::Executor; let result = match self.executor.input_type() { Some(t) if t == concrete!(EditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()), @@ -155,7 +155,7 @@ impl NodeRuntime { _ => Err("Invalid input type".to_string()), }?; - if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform }) = result { + if let TaggedValue::SurfaceFrame(SurfaceFrame { surface_id, transform: _ }) = result { let old_id = self.canvas_cache.insert(path.to_vec(), surface_id); if let Some(old_id) = old_id { if old_id != surface_id { @@ -206,6 +206,19 @@ impl NodeRuntime { } } } +pub fn introspect_node(path: &[NodeId]) -> Option> { + NODE_RUNTIME + .try_with(|runtime| { + let runtime = runtime.try_borrow(); + if let Ok(ref runtime) = runtime { + if let Some(ref mut runtime) = runtime.as_ref() { + return runtime.executor.introspect(path).flatten(); + } + } + None + }) + .unwrap_or(None) +} pub async fn run_node_graph() { let result = NODE_RUNTIME.try_with(|runtime| { @@ -226,7 +239,6 @@ pub async fn run_node_graph() { #[derive(Debug)] pub struct NodeGraphExecutor { - pub(crate) executor: DynamicExecutor, sender: Sender, receiver: Receiver, // TODO: This is a memory leak since layers are never removed @@ -250,7 +262,6 @@ impl Default for NodeGraphExecutor { }); Self { - executor: Default::default(), futures: Default::default(), sender: request_sender, receiver: response_reciever, @@ -275,12 +286,12 @@ impl NodeGraphExecutor { generation_id } - pub fn update_font_cache(&self, font_cache: FontCache) { - self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache)).expect("Failed to send font cache update"); + pub fn introspect_node(&self, path: &[NodeId]) -> Option> { + introspect_node(path) } - pub fn introspect_node(&self, path: &[NodeId]) -> Option> { - self.executor.introspect(path).flatten() + pub fn update_font_cache(&self, font_cache: FontCache) { + self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache)).expect("Failed to send font cache update"); } pub fn previous_output_type(&self, path: &[LayerId]) -> Option { diff --git a/frontend/wasm/.cargo/config.toml b/frontend/wasm/.cargo/config.toml index a7fd7be9..49f5ff49 100644 --- a/frontend/wasm/.cargo/config.toml +++ b/frontend/wasm/.cargo/config.toml @@ -1,6 +1,14 @@ [target.wasm32-unknown-unknown] #rustflags = ["-C", "target-feature=+simd128,+atomics,+bulk-memory,+mutable-globals","--cfg=web_sys_unstable_apis"] -rustflags = ["-C", "target-feature=+simd128","--cfg=web_sys_unstable_apis"] +rustflags = [ + # Currently disabled because of https://github.com/GraphiteEditor/Graphite/issues/1262 + # The current simd implementation leads to undefined behavior + #"-C", + #"target-feature=+simd128", + "-C", + "link-arg=--max-memory=4294967296", + "--cfg=web_sys_unstable_apis", +] [unstable] build-std = ["panic_abort", "std"] diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index b962defd..cedf45c3 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -56,13 +56,13 @@ impl<'a, T: DynAny<'a> + 'a> UpcastFrom for dyn DynAny<'a> + 'a { } } -pub trait DynAny<'a> { +pub trait DynAny<'a>: 'a { fn type_id(&self) -> TypeId; #[cfg(feature = "log-bad-types")] fn type_name(&self) -> &'static str; } -impl<'a, T: StaticType> DynAny<'a> for T { +impl<'a, T: StaticType + 'a> DynAny<'a> for T { fn type_id(&self) -> core::any::TypeId { core::any::TypeId::of::() } @@ -71,7 +71,7 @@ impl<'a, T: StaticType> DynAny<'a> for T { core::any::type_name::() } } -pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> { +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>() { // SAFETY: caller guarantees that T is the correct type let ptr = i as *const dyn DynAny<'a> as *const V; @@ -82,7 +82,7 @@ pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> { } #[cfg(feature = "alloc")] -pub fn downcast<'a, V: StaticType>(i: Box + 'a>) -> Result, String> { +pub fn downcast<'a, V: StaticType + 'a>(i: Box + 'a>) -> Result, String> { let type_id = DynAny::type_id(i.as_ref()); if type_id == core::any::TypeId::of::<::Static>() { // SAFETY: caller guarantees that T is the correct type diff --git a/node-graph/compilation-client/src/main.rs b/node-graph/compilation-client/src/main.rs index 0080d60d..87207e60 100644 --- a/node-graph/compilation-client/src/main.rs +++ b/node-graph/compilation-client/src/main.rs @@ -14,7 +14,7 @@ fn main() { let client = reqwest::blocking::Client::new(); let network = add_network(); - let compiler = graph_craft::executor::Compiler {}; + let compiler = graph_craft::graphene_compiler::Compiler {}; let proto_network = compiler.compile_single(network, true).unwrap(); let io = ShaderIO { diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index c68fd432..d0ae7148 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -96,13 +96,6 @@ where { } -/*impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn for<'n> Node<'n, I, Output = O> { - type Output = O; - - fn eval(&'i self, input: I) -> Self::Output { - (**self).eval(input) - } -}*/ impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for &'s N { type Output = O; @@ -118,8 +111,16 @@ impl<'i, 's: 'i, I: 'i, O: 'i, N: Node<'i, I, Output = O>> Node<'i, I> for Box> Node<'i, I> for alloc::sync::Arc { + type Output = O; -impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn for<'a> Node<'a, I, Output = O> { + fn eval(&'i self, input: I) -> Self::Output { + (**self).eval(input) + } +} + +impl<'i, I: 'i, O: 'i> Node<'i, I> for &'i dyn Node<'i, I, Output = O> { type Output = O; fn eval(&'i self, input: I) -> Self::Output { @@ -130,7 +131,7 @@ use core::pin::Pin; use dyn_any::StaticTypeSized; #[cfg(feature = "alloc")] -impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin Node<'a, I, Output = O> + 'i>> { +impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin + 'i>> { type Output = O; fn eval(&'i self, input: I) -> Self::Output { diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index eaf62b3e..63769351 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -5,8 +5,9 @@ edition = "2021" license = "MIT OR Apache-2.0" [features] -default = [] +default = ["dealloc_nodes"] serde = ["dep:serde", "graphene-core/serde", "glam/serde", "bezier-rs/serde"] +dealloc_nodes = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index ec81831e..c4a079c2 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -892,7 +892,7 @@ impl<'a> Iterator for RecursiveNodeIter<'a> { #[cfg(test)] mod test { - use std::{cell::Cell, sync::atomic::AtomicU64}; + use std::sync::atomic::AtomicU64; use super::*; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; @@ -1193,7 +1193,7 @@ mod test { .collect(), ..Default::default() }; - let mut new_ids = 101..; + let _new_ids = 101..; network.flatten_with_fns(1, |self_id, inner_id| self_id * 10 + inner_id, || 10000); network.flatten_with_fns(2, |self_id, inner_id| self_id * 10 + inner_id, || 10001); network.remove_dead_nodes(); diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 3b9f4131..7e649a73 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -1,5 +1,5 @@ use super::DocumentNode; -use crate::executor::Any; +use crate::graphene_compiler::Any; pub use crate::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateStatus}; use crate::proto::{Any as DAny, FutureAny}; diff --git a/node-graph/graph-craft/src/executor.rs b/node-graph/graph-craft/src/graphene_compiler.rs similarity index 100% rename from node-graph/graph-craft/src/executor.rs rename to node-graph/graph-craft/src/graphene_compiler.rs diff --git a/node-graph/graph-craft/src/lib.rs b/node-graph/graph-craft/src/lib.rs index dd2fe61a..066ae85f 100644 --- a/node-graph/graph-craft/src/lib.rs +++ b/node-graph/graph-craft/src/lib.rs @@ -8,5 +8,5 @@ pub use graphene_core::{concrete, generic, NodeIdentifier, Type, TypeDescriptor} pub mod document; pub mod proto; -pub mod executor; +pub mod graphene_compiler; pub mod imaginate_input; diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index c829380e..00270d93 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -1,5 +1,8 @@ use std::borrow::Cow; + use std::collections::{HashMap, HashSet}; +use std::ops::Deref; +use std::sync::Arc; use std::hash::Hash; use xxhash_rust::xxh3::Xxh3; @@ -18,9 +21,59 @@ pub type Any<'n> = Box + 'n>; pub type FutureAny<'n> = DynFuture<'n, Any<'n>>; pub type TypeErasedNode<'n> = dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n; pub type TypeErasedPinnedRef<'n> = Pin<&'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n)>; +pub type TypeErasedRef<'n> = &'n (dyn for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n); +pub type TypeErasedBox<'n> = Box NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n>; pub type TypeErasedPinned<'n> = Pin NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n>>; -pub type NodeConstructor = for<'a> fn(Vec>) -> DynFuture<'static, TypeErasedPinned<'static>>; +pub type NodeConstructor = for<'a> fn(Vec>) -> DynFuture<'static, TypeErasedBox<'static>>; + +#[derive(Clone)] +pub struct NodeContainer { + #[cfg(feature = "dealloc_nodes")] + pub node: *mut TypeErasedNode<'static>, + #[cfg(not(feature = "dealloc_nodes"))] + pub node: TypeErasedRef<'static>, +} + +impl Deref for NodeContainer { + type Target = TypeErasedNode<'static>; + + #[cfg(feature = "dealloc_nodes")] + fn deref(&self) -> &Self::Target { + unsafe { &*(self.node as *const TypeErasedNode) } + #[cfg(not(feature = "dealloc_nodes"))] + self.node + } + #[cfg(not(feature = "dealloc_nodes"))] + fn deref(&self) -> &Self::Target { + self.node + } +} + +#[cfg(feature = "dealloc_nodes")] +impl Drop for NodeContainer { + fn drop(&mut self) { + unsafe { self.dealloc_unchecked() } + } +} + +impl core::fmt::Debug for NodeContainer { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NodeContainer").finish() + } +} + +impl NodeContainer { + pub fn new(node: TypeErasedBox<'static>) -> Arc { + let node = Box::leak(node); + Arc::new(Self { node }) + } + + #[cfg(feature = "dealloc_nodes")] + unsafe fn dealloc_unchecked(&mut self) { + std::mem::drop(Box::from_raw(self.node)); + } +} #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Default, PartialEq, Clone, Hash, Eq)] @@ -406,7 +459,7 @@ impl ProtoNetwork { } /// The `TypingContext` is used to store the types of the nodes indexed by their stable node id. -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Default, Clone)] pub struct TypingContext { lookup: Cow<'static, HashMap>>, inferred: HashMap, @@ -539,7 +592,7 @@ impl TypingContext { dbg!(&self.inferred); Err(format!( "No implementations found for {identifier} with \ninput: {input:?} and \nparameters: {parameters:?}.\nOther Implementations found: {:?}", - impls, + impls.keys().collect::>(), )) } [(org_nio, output)] => { diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 3bb5149c..2b20b224 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -1,9 +1,9 @@ use dyn_any::StaticType; -pub use graph_craft::proto::{Any, TypeErasedNode, TypeErasedPinned, TypeErasedPinnedRef}; +pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode}; use graph_craft::proto::{DynFuture, FutureAny}; use graphene_core::NodeIO; pub use graphene_core::{generic, ops, Node}; -use std::marker::PhantomData; +use std::{marker::PhantomData, sync::Arc}; pub struct DynAnyNode { node: Node, @@ -124,15 +124,15 @@ impl<'i, N> FutureWrapperNode { } pub trait IntoTypeErasedNode<'n> { - fn into_type_erased(self) -> TypeErasedPinned<'n>; + fn into_type_erased(self) -> TypeErasedBox<'n>; } impl<'n, N: 'n> IntoTypeErasedNode<'n> for N where N: for<'i> NodeIO<'i, Any<'i>, Output = FutureAny<'i>> + 'n, { - fn into_type_erased(self) -> TypeErasedPinned<'n> { - Box::pin(self) + fn into_type_erased(self) -> TypeErasedBox<'n> { + Box::new(self) } } @@ -159,13 +159,13 @@ where /// Boxes the input and downcasts the output. /// Wraps around a node taking Box and returning Box -#[derive(Clone, Copy)] -pub struct DowncastBothNode<'a, I, O> { - node: TypeErasedPinnedRef<'a>, +#[derive(Clone)] +pub struct DowncastBothNode { + node: Arc, _i: PhantomData, _o: PhantomData, } -impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothNode<'n, I, O> { +impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothNode { type Output = DynFuture<'input, O>; #[inline] fn eval(&'input self, input: I) -> Self::Output { @@ -180,8 +180,8 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i } } } -impl<'n, I, O> DowncastBothNode<'n, I, O> { - pub const fn new(node: TypeErasedPinnedRef<'n>) -> Self { +impl DowncastBothNode { + pub const fn new(node: Arc) -> Self { Self { node, _i: core::marker::PhantomData, @@ -191,12 +191,12 @@ impl<'n, I, O> DowncastBothNode<'n, I, O> { } /// Boxes the input and downcasts the output. /// Wraps around a node taking Box and returning Box -#[derive(Clone, Copy)] -pub struct DowncastBothRefNode<'a, I, O> { - node: TypeErasedPinnedRef<'a>, +#[derive(Clone)] +pub struct DowncastBothRefNode { + node: Arc, _i: PhantomData<(I, O)>, } -impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothRefNode<'n, I, O> { +impl<'input, O: 'input + StaticType, I: 'input + StaticType> Node<'input, I> for DowncastBothRefNode { type Output = DynFuture<'input, &'input O>; #[inline] fn eval(&'input self, input: I) -> Self::Output { @@ -210,18 +210,18 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i } } } -impl<'n, I, O> DowncastBothRefNode<'n, I, O> { - pub const fn new(node: TypeErasedPinnedRef<'n>) -> Self { +impl DowncastBothRefNode { + pub const fn new(node: Arc) -> Self { Self { node, _i: core::marker::PhantomData } } } -pub struct ComposeTypeErased<'a> { - first: TypeErasedPinnedRef<'a>, - second: TypeErasedPinnedRef<'a>, +pub struct ComposeTypeErased { + first: Arc, + second: Arc, } -impl<'i, 'a: 'i> Node<'i, Any<'i>> for ComposeTypeErased<'a> { +impl<'i, 'a: 'i> Node<'i, Any<'i>> for ComposeTypeErased { type Output = DynFuture<'i, Any<'i>>; fn eval(&'i self, input: Any<'i>) -> Self::Output { Box::pin(async move { @@ -231,13 +231,13 @@ impl<'i, 'a: 'i> Node<'i, Any<'i>> for ComposeTypeErased<'a> { } } -impl<'a> ComposeTypeErased<'a> { - pub const fn new(first: TypeErasedPinnedRef<'a>, second: TypeErasedPinnedRef<'a>) -> Self { +impl ComposeTypeErased { + pub const fn new(first: Arc, second: Arc) -> Self { ComposeTypeErased { first, second } } } -pub fn input_node(n: TypeErasedPinnedRef) -> DowncastBothNode<(), O> { +pub fn input_node(n: Arc) -> DowncastBothNode<(), O> { DowncastBothNode::new(n) } @@ -267,22 +267,23 @@ mod test { //let add = DynAnyNode::new(AddNode::new()).into_type_erased(); //add.eval(Box::new(&("32", 32u32))); let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(ValueNode::new(FutureWrapperNode { node: AddNode::new() })); - let type_erased = Box::pin(dyn_any) as TypeErasedPinned; + let type_erased = Box::new(dyn_any) as TypeErasedBox; let _ref_type_erased = type_erased.as_ref(); - //let type_erased = Box::pin(dyn_any) as TypeErasedPinned<'_>; + //let type_erased = Box::pin(dyn_any) as TypeErasedBox<'_>; type_erased.eval(Box::new(&("32", 32u32))); } #[test] - pub fn dyn_input_invalid_eval_panic_() { + pub fn dyn_input_compose() { //let add = DynAnyNode::new(AddNode::new()).into_type_erased(); //add.eval(Box::new(&("32", 32u32))); let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(ValueNode::new(FutureWrapperNode { node: AddNode::new() })); - let type_erased = Box::pin(dyn_any) as TypeErasedPinned<'_>; + let type_erased = Box::new(dyn_any) as TypeErasedBox<'_>; type_erased.eval(Box::new((4u32, 2u32))); let id_node = FutureWrapperNode::new(IdNode::new()); - let type_erased_id = Box::pin(id_node) as TypeErasedPinned; - let type_erased = ComposeTypeErased::new(type_erased.as_ref(), type_erased_id.as_ref()); + let any_id = DynAnyNode::::new(ValueNode::new(id_node)); + let type_erased_id = Box::new(any_id) as TypeErasedBox; + let type_erased = ComposeTypeErased::new(NodeContainer::new(type_erased), NodeContainer::new(type_erased_id)); type_erased.eval(Box::new((4u32, 2u32))); //let downcast: DowncastBothNode<(u32, u32), u32> = DowncastBothNode::new(type_erased.as_ref()); //downcast.eval((4u32, 2u32)); diff --git a/node-graph/gstd/src/brush.rs b/node-graph/gstd/src/brush.rs index ebd1f5ae..7903cd92 100644 --- a/node-graph/gstd/src/brush.rs +++ b/node-graph/gstd/src/brush.rs @@ -1,11 +1,12 @@ -use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode}; +use crate::raster::{blend_image_closure, BlendImageTupleNode, EmptyImageNode, ExtendImageNode}; use graphene_core::raster::adjustments::blend_colors; +use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; use graphene_core::raster::{Alpha, Color, Image, ImageFrame, Pixel, Sample}; use graphene_core::raster::{BlendMode, BlendNode}; use graphene_core::transform::{Transform, TransformMut}; -use graphene_core::value::{CopiedNode, ValueNode}; -use graphene_core::vector::brush_stroke::BrushStyle; +use graphene_core::value::{ClonedNode, CopiedNode, OnceCellNode, ValueNode}; +use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle}; use graphene_core::vector::VectorData; use graphene_core::Node; use node_macro::node_fn; @@ -279,6 +280,89 @@ pub fn blend_with_mode(background: ImageFrame, foreground: ImageFrame { + bounds: Bounds, + strokes: Strokes, +} + +#[node_macro::node_fn(BrushNode)] +async fn brush(image: ImageFrame, bounds: ImageFrame, strokes: Vec) -> ImageFrame { + let stroke_bbox = strokes.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); + let image_bbox = Bbox::from_transform(image.transform).to_axis_aligned_bbox(); + let bbox = stroke_bbox.union(&image_bbox); + + let mut background_bounds = bbox.to_transform(); + + if bounds.transform != DAffine2::ZERO { + background_bounds = bounds.transform; + } + + let has_erase_strokes = strokes.iter().any(|s| s.style.blend_mode == BlendMode::Erase); + let blank_image = ImageFrame { + image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::TRANSPARENT), + transform: background_bounds, + }; + let opaque_image = ImageFrame { + image: Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE), + transform: background_bounds, + }; + let mut erase_restore_mask = has_erase_strokes.then_some(opaque_image); + let mut actual_image = ExtendImageNode::new(OnceCellNode::new(blank_image)).eval(image); + for stroke in strokes { + let normal_blend = BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)); + + // Create brush texture. + // TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes. + let brush_texture = create_brush_texture(stroke.style.clone()); + + // Compute transformation from stroke texture space into layer space, and create the stroke texture. + let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect(); + let mut bbox = stroke.bounding_box(); + bbox.start = bbox.start.floor(); + bbox.end = bbox.end.floor(); + let stroke_size = bbox.size() + DVec2::splat(stroke.style.diameter); + // For numerical stability we want to place the first blit point at a stable, integer offset + // in layer space. + let snap_offset = positions[0].floor() - positions[0]; + let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.); + let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size); + + match stroke.style.blend_mode { + BlendMode::Erase => { + if let Some(mask) = erase_restore_mask { + let blend_params = BlendNode::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 = Some(blit_node.eval(mask)); + } + } + + // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. + BlendMode::Restore => { + if let Some(mask) = erase_restore_mask { + let blend_params = BlendNode::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 = Some(blit_node.eval(mask)); + } + } + + blend_mode => { + let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend)); + let empty_stroke_texture = EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)).eval(stroke_to_layer); + let stroke_texture = blit_node.eval(empty_stroke_texture); + // TODO: Is this the correct way to do opacity in blending? + actual_image = blend_with_mode(actual_image, stroke_texture, blend_mode, stroke.style.color.a() * 100.); + } + } + } + + if let Some(mask) = erase_restore_mask { + let blend_params = BlendNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.)); + let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params)); + actual_image = blend_executor.eval((actual_image, mask)); + } + actual_image +} + #[cfg(test)] mod test { use super::*; diff --git a/node-graph/gstd/src/executor.rs b/node-graph/gstd/src/executor.rs index 3748cd90..f6d250b6 100644 --- a/node-graph/gstd/src/executor.rs +++ b/node-graph/gstd/src/executor.rs @@ -18,7 +18,7 @@ pub struct GpuCompiler { // TODO: Move to graph-craft #[node_macro::node_fn(GpuCompiler)] async fn compile_gpu(node: &'input DocumentNode, mut typing_context: TypingContext, io: ShaderIO) -> compilation_client::Shader { - let compiler = graph_craft::executor::Compiler {}; + let compiler = graph_craft::graphene_compiler::Compiler {}; let DocumentNodeImplementation::Network(ref network) = node.implementation else { panic!() }; let proto_networks: Vec<_> = compiler.compile(network.clone(), true).collect(); @@ -44,7 +44,7 @@ pub struct MapGpuNode { #[node_macro::node_fn(MapGpuNode)] async fn map_gpu(image: ImageFrame, node: DocumentNode) -> ImageFrame { log::debug!("Executing gpu node"); - let compiler = graph_craft::executor::Compiler {}; + let compiler = graph_craft::graphene_compiler::Compiler {}; let inner_network = NodeNetwork::value_network(node); log::debug!("inner_network: {:?}", inner_network); @@ -262,7 +262,7 @@ async fn blend_gpu_image(foreground: ImageFrame, background: ImageFrame + Transform, Background: RasterMut + Transform + Sample>( foreground: Frame, - mut background: Background, + background: Background, map_fn: &MapFn, ) -> Background where diff --git a/node-graph/interpreted-executor/Cargo.toml b/node-graph/interpreted-executor/Cargo.toml index 5f32433f..e7493d1e 100644 --- a/node-graph/interpreted-executor/Cargo.toml +++ b/node-graph/interpreted-executor/Cargo.toml @@ -27,3 +27,4 @@ serde = { version = "1", features = ["derive"], optional = true } glam = { version = "0.22" } once_cell = "1.17.0" futures = "0.3.28" +typed-arena = "2.0.2" diff --git a/node-graph/interpreted-executor/src/executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs similarity index 64% rename from node-graph/interpreted-executor/src/executor.rs rename to node-graph/interpreted-executor/src/dynamic_executor.rs index c5d81ed8..0b6e26e4 100644 --- a/node-graph/interpreted-executor/src/executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -1,18 +1,17 @@ use std::collections::{HashMap, HashSet}; use std::error::Error; -use std::sync::{Arc, RwLock}; + +use std::sync::Arc; use dyn_any::StaticType; use graph_craft::document::value::{TaggedValue, UpcastNode}; use graph_craft::document::NodeId; -use graph_craft::executor::Executor; -use graph_craft::proto::{ConstructionArgs, LocalFuture, ProtoNetwork, ProtoNode, TypingContext}; +use graph_craft::graphene_compiler::Executor; +use graph_craft::proto::{ConstructionArgs, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, TypeErasedBox, TypingContext}; use graph_craft::Type; -use graphene_std::any::{TypeErasedPinned, TypeErasedPinnedRef}; use crate::node_registry; -#[derive(Debug, Clone)] pub struct DynamicExecutor { output: NodeId, tree: BorrowTree, @@ -79,45 +78,14 @@ impl<'a, I: StaticType + 'a> Executor for &'a DynamicExecutor { } } -pub struct NodeContainer<'n> { - pub node: TypeErasedPinned<'n>, - // the dependencies are only kept to ensure that the nodes are not dropped while still in use - _dependencies: Vec>>>, -} - -impl<'a> core::fmt::Debug for NodeContainer<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NodeContainer").finish() - } -} - -impl<'a> NodeContainer<'a> { - pub fn new(node: TypeErasedPinned<'a>, _dependencies: Vec>>>) -> Self { - Self { node, _dependencies } - } - - /// Return a static reference to the TypeErasedNode - /// # Safety - /// This is unsafe because the returned reference is only valid as long as the NodeContainer is alive - pub unsafe fn erase_lifetime(self) -> NodeContainer<'static> { - std::mem::transmute(self) - } -} -impl NodeContainer<'static> { - pub unsafe fn static_ref(&self) -> TypeErasedPinnedRef<'static> { - let s = &*(self as *const Self); - *(&s.node.as_ref() as *const TypeErasedPinnedRef<'static>) - } -} - -#[derive(Default, Debug, Clone)] +#[derive(Default)] pub struct BorrowTree { - nodes: HashMap>>>, + nodes: HashMap>, source_map: HashMap, NodeId>, } impl BorrowTree { - pub async fn new(proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result { + pub async 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? @@ -133,9 +101,7 @@ impl BorrowTree { self.push_node(id, node, typing_context).await?; } else { let Some(node_container) = self.nodes.get_mut(&id) else { continue }; - let mut node_container_writer = node_container.write().unwrap(); - let node = node_container_writer.node.as_mut(); - node.reset(); + node_container.reset(); } old_nodes.remove(&id); } @@ -143,40 +109,32 @@ impl BorrowTree { Ok(old_nodes.into_iter().collect()) } - fn node_refs(&self, nodes: &[NodeId]) -> Vec> { - self.node_deps(nodes).into_iter().map(|node| unsafe { node.read().unwrap().static_ref() }).collect() - } - fn node_deps(&self, nodes: &[NodeId]) -> Vec>>> { + fn node_deps(&self, nodes: &[NodeId]) -> Vec> { nodes.iter().map(|node| self.nodes.get(node).unwrap().clone()).collect() } - fn store_node(&mut self, node: Arc>>, id: NodeId) -> Arc>> { - self.nodes.insert(id, node.clone()); - node + fn store_node(&mut self, node: Arc, id: NodeId) { + self.nodes.insert(id, node); } pub fn introspect(&self, node_path: &[NodeId]) -> Option>> { let id = self.source_map.get(node_path)?; let node = self.nodes.get(id)?; - let reader = node.read().unwrap(); - let node = reader.node.as_ref(); Some(node.serialize()) } - pub fn get(&self, id: NodeId) -> Option>>> { + pub fn get(&self, id: NodeId) -> Option> { self.nodes.get(&id).cloned() } pub async fn eval<'i, I: StaticType + 'i, O: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Option { let node = self.nodes.get(&id).cloned()?; - let reader = node.read().unwrap(); - let output = reader.node.eval(Box::new(input)); + let output = node.eval(Box::new(input)); dyn_any::downcast::(output.await).ok().map(|o| *o) } pub async fn eval_tagged_value<'i, I: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Result { - let node = self.nodes.get(&id).cloned().ok_or_else(|| "Output node not found in executor")?; - let reader = node.read().unwrap(); - let output = reader.node.eval(Box::new(input)); + let node = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?; + let output = node.eval(Box::new(input)); TaggedValue::try_from_any(output.await) } @@ -196,23 +154,18 @@ impl BorrowTree { match construction_args { ConstructionArgs::Value(value) => { let upcasted = UpcastNode::new(value); - let node = Box::pin(upcasted) as TypeErasedPinned<'_>; - let node = NodeContainer { node, _dependencies: vec![] }; - let node = unsafe { node.erase_lifetime() }; - self.store_node(Arc::new(node.into()), id); + let node = Box::new(upcasted) as TypeErasedBox<'_>; + let node = NodeContainer::new(node); + self.store_node(node.into(), id); } ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), ConstructionArgs::Nodes(ids) => { let ids: Vec<_> = ids.iter().map(|(id, _)| *id).collect(); - let construction_nodes = self.node_refs(&ids); + let construction_nodes = self.node_deps(&ids); let constructor = typing_context.constructor(id).ok_or(format!("No constructor found for node {:?}", identifier))?; let node = constructor(construction_nodes).await; - let node = NodeContainer { - node, - _dependencies: self.node_deps(&ids), - }; - let node = unsafe { node.erase_lifetime() }; - self.store_node(Arc::new(node.into()), id); + let node = NodeContainer::new(node); + self.store_node(node.into(), id); } }; Ok(()) diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 6e72bee8..c1b85079 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -1,7 +1,4 @@ -#[macro_use] -extern crate log; - -pub mod executor; +pub mod dynamic_executor; pub mod node_registry; #[cfg(test)] @@ -72,8 +69,8 @@ mod tests { ..Default::default() }; - use crate::executor::DynamicExecutor; - use graph_craft::executor::{Compiler, Executor}; + use crate::dynamic_executor::DynamicExecutor; + use graph_craft::graphene_compiler::{Compiler, Executor}; let compiler = Compiler {}; let protograph = compiler.compile_single(network, true).expect("Graph should be generated"); @@ -120,12 +117,12 @@ mod tests { ..Default::default() }; - use crate::executor::DynamicExecutor; - use graph_craft::executor::Compiler; + use crate::dynamic_executor::DynamicExecutor; + use graph_craft::graphene_compiler::Compiler; let compiler = Compiler {}; let protograph = compiler.compile_single(network, true).expect("Graph should be generated"); - let _exec = block_on(DynamicExecutor::new(protograph)).map(|e| panic!("The network should not type check: {:#?}", e)).unwrap_err(); + let _exec = block_on(DynamicExecutor::new(protograph)).map(|e| panic!("The network should not type check ")).unwrap_err(); } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 0e5444d2..308b735e 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -1,7 +1,7 @@ -use graph_craft::proto::{NodeConstructor, TypeErasedPinned}; +use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graphene_core::ops::IdNode; use graphene_core::quantization::QuantizationChannels; -use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox}; + use graphene_core::raster::color::Color; use graphene_core::structural::Then; use graphene_core::value::{ClonedNode, CopiedNode, ValueNode}; @@ -14,8 +14,7 @@ use graphene_core::{fn_type, raster::*}; use graphene_core::{Cow, NodeIdentifier, Type, TypeDescriptor}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; -use graphene_std::brush; -use graphene_std::raster::BlendImageTupleNode; + use graphene_std::raster::*; use dyn_any::StaticType; @@ -51,7 +50,7 @@ macro_rules! register_node { let node = construct_node!(args, $path, [$($type),*]).await; let node = graphene_std::any::FutureWrapperNode::new(node); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - Box::pin(any) as TypeErasedPinned + Box::new(any) as TypeErasedBox }) }, { @@ -79,7 +78,7 @@ macro_rules! async_node { args.reverse(); let node = <$path>::new($(graphene_std::any::input_node::<$type>(args.pop().expect("Not enough arguments provided to construct node"))),*); let any: DynAnyNode<$input, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, { @@ -118,7 +117,7 @@ macro_rules! raster_node { let node = construct_node!(args, $path, [$($type),*]).await; let node = graphene_std::any::FutureWrapperNode::new(node); let any: DynAnyNode = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, { @@ -134,7 +133,7 @@ macro_rules! raster_node { let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); let map_node = graphene_std::any::FutureWrapperNode::new(map_node); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, { @@ -150,7 +149,7 @@ macro_rules! raster_node { let map_node = graphene_std::raster::MapImageNode::new(graphene_core::value::ValueNode::new(node)); let map_node = graphene_std::any::FutureWrapperNode::new(map_node); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(map_node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, { @@ -170,7 +169,7 @@ fn node_registry() -> HashMap, params: []), vec![( NodeIdentifier::new("graphene_core::ops::IdNode"), - |_| Box::pin(async move { Box::pin(FutureWrapperNode::new(IdNode::new())) as TypeErasedPinned }), + |_| Box::pin(async move { FutureWrapperNode::new(IdNode::new()).into_type_erased() }), NodeIOTypes::new(generic!(I), generic!(I), vec![]), )], // TODO: create macro to impl for all types @@ -205,10 +204,10 @@ fn node_registry() -> HashMap = DowncastBothNode::new(args[0]).eval(()).await; - let channel_g: ImageFrame = DowncastBothNode::new(args[1]).eval(()).await; - let channel_b: ImageFrame = DowncastBothNode::new(args[2]).eval(()).await; - let channel_a: ImageFrame = DowncastBothNode::new(args[3]).eval(()).await; + let channel_r: ImageFrame = DowncastBothNode::new(args[0].clone()).eval(()).await; + let channel_g: ImageFrame = DowncastBothNode::new(args[1].clone()).eval(()).await; + let channel_b: ImageFrame = DowncastBothNode::new(args[2].clone()).eval(()).await; + let channel_a: ImageFrame = 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)); @@ -232,7 +231,7 @@ fn node_registry() -> HashMap = graphene_std::any::DynAnyNode::new(ValueNode::new(final_image)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, NodeIOTypes::new( @@ -256,11 +255,11 @@ fn node_registry() -> HashMap"), |args| { Box::pin(async move { - let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0]); + let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone()); //let document_node = ClonedNode::new(document_node.eval(())); let node = graphene_std::executor::MapGpuNode::new(document_node); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, NodeIOTypes::new(concrete!(ImageFrame), concrete!(ImageFrame), vec![fn_type!(graph_craft::document::DocumentNode)]), @@ -270,13 +269,13 @@ fn node_registry() -> HashMap"), |args| { Box::pin(async move { - let background: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); - let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); - let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2]); + let background: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0].clone()); + let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone()); + let opacity: DowncastBothNode<(), f32> = DowncastBothNode::new(args[2].clone()); let node = graphene_std::executor::BlendGpuImageNode::new(background, blend_mode, opacity); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, NodeIOTypes::new( @@ -289,7 +288,7 @@ fn node_registry() -> HashMap"), |args| { Box::pin(async move { - let node = ComposeTypeErased::new(args[0], args[1]); + let node = ComposeTypeErased::new(args[0].clone(), args[1].clone()); node.into_type_erased() }) }, @@ -300,99 +299,7 @@ fn node_registry() -> HashMap, input: &Vec, params: []), - vec![( - NodeIdentifier::new("graphene_std::brush::BrushNode"), - |args| { - use graphene_core::structural::*; - use graphene_core::value::*; - use graphene_std::brush::*; - - Box::pin(async move { - let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); - let bounds: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[1]); - let strokes: DowncastBothNode<(), Vec> = DowncastBothNode::new(args[2]); - - let image_val = image.eval(()).await; - let strokes_val = strokes.eval(()).await; - let stroke_bbox = strokes_val.iter().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO); - let image_bbox = Bbox::from_transform(image_val.transform).to_axis_aligned_bbox(); - let bbox = stroke_bbox.union(&image_bbox); - - let mut background_bounds = CopiedNode::new(bbox.to_transform()); - let bounds_transform = bounds.eval(()).await.transform; - if bounds_transform != DAffine2::ZERO { - background_bounds = CopiedNode::new(bounds_transform); - } - - let has_erase_strokes = strokes_val.iter().any(|s| s.style.blend_mode == BlendMode::Erase); - let blank_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT))); - let opaque_image = background_bounds.then(EmptyImageNode::new(CopiedNode::new(Color::WHITE))); - let mut erase_restore_mask = has_erase_strokes.then(|| opaque_image.eval(())); - let mut actual_image = ExtendImageNode::new(blank_image).eval(image_val); - for stroke in strokes_val { - let normal_blend = BlendNode::new(CopiedNode::new(BlendMode::Normal), CopiedNode::new(100.)); - - // Create brush texture. - // TODO: apply rotation from layer to stamp for non-rotationally-symmetric brushes. - let brush_texture = brush::create_brush_texture(stroke.style.clone()); - - // Compute transformation from stroke texture space into layer space, and create the stroke texture. - let positions: Vec<_> = stroke.compute_blit_points().into_iter().collect(); - let mut bbox = stroke.bounding_box(); - bbox.start = bbox.start.floor(); - bbox.end = bbox.end.floor(); - let stroke_size = bbox.size() + DVec2::splat(stroke.style.diameter); - // For numerical stability we want to place the first blit point at a stable, integer offset - // in layer space. - let snap_offset = positions[0].floor() - positions[0]; - let stroke_origin_in_layer = bbox.start - snap_offset - DVec2::splat(stroke.style.diameter / 2.); - let stroke_to_layer = DAffine2::from_translation(stroke_origin_in_layer) * DAffine2::from_scale(stroke_size); - - match stroke.style.blend_mode { - BlendMode::Erase => { - if let Some(mask) = erase_restore_mask { - let blend_params = BlendNode::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 = Some(blit_node.eval(mask)); - } - } - - // Yes, this is essentially the same as the above, but we duplicate to inline the blend mode. - BlendMode::Restore => { - if let Some(mask) = erase_restore_mask { - let blend_params = BlendNode::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 = Some(blit_node.eval(mask)); - } - } - - blend_mode => { - let blit_node = BlitNode::new(ClonedNode::new(brush_texture), ClonedNode::new(positions), ClonedNode::new(normal_blend)); - let empty_stroke_texture = EmptyImageNode::new(CopiedNode::new(Color::TRANSPARENT)).eval(stroke_to_layer); - let stroke_texture = blit_node.eval(empty_stroke_texture); - // TODO: Is this the correct way to do opacity in blending? - actual_image = brush::blend_with_mode(actual_image, stroke_texture, blend_mode, stroke.style.color.a() * 100.); - } - } - } - - if let Some(mask) = erase_restore_mask { - let blend_params = BlendNode::new(CopiedNode::new(BlendMode::MultiplyAlpha), CopiedNode::new(100.)); - let blend_executor = BlendImageTupleNode::new(ValueNode::new(blend_params)); - actual_image = blend_executor.eval((actual_image, mask)); - } - - // TODO: there *has* to be a better way to do this. - let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(FutureWrapperNode::new(ClonedNode::new(actual_image)))); - Box::pin(any) as TypeErasedPinned - }) - }, - NodeIOTypes::new( - concrete!(()), - concrete!(ImageFrame), - vec![fn_type!(ImageFrame), fn_type!(ImageFrame), fn_type!(Vec)], - ), - )], + async_node!(graphene_std::brush::BrushNode<_, _>, input: ImageFrame, output: ImageFrame, params: [ImageFrame, Vec]), // Filters raster_node!(graphene_core::raster::LuminanceNode<_>, params: [LuminanceCalculation]), raster_node!(graphene_core::raster::ExtractChannelNode<_>, params: [RedGreenBlue]), @@ -404,13 +311,13 @@ fn node_registry() -> HashMap"), |args| { Box::pin(async move { - let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0]); - let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1]); - let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2]); + let image: DowncastBothNode<(), ImageFrame> = DowncastBothNode::new(args[0].clone()); + let blend_mode: DowncastBothNode<(), BlendMode> = DowncastBothNode::new(args[1].clone()); + let opacity: DowncastBothNode<(), f64> = DowncastBothNode::new(args[2].clone()); let blend_node = graphene_core::raster::BlendNode::new(CopiedNode::new(blend_mode.eval(()).await), CopiedNode::new(opacity.eval(()).await)); let node = graphene_std::raster::BlendImageNode::new(image, FutureWrapperNode::new(ValueNode::new(blend_node))); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, NodeIOTypes::new( @@ -438,24 +345,24 @@ fn node_registry() -> HashMap = DowncastBothNode::new(args[0]); + let brightness: DowncastBothNode<(), f64> = DowncastBothNode::new(args[0].clone()); let brightness = ClonedNode::new(brightness.eval(()).await as f32); - let contrast: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1]); + let contrast: DowncastBothNode<(), f64> = DowncastBothNode::new(args[1].clone()); let contrast = ClonedNode::new(contrast.eval(()).await as f32); - let use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2]); + let use_legacy: DowncastBothNode<(), bool> = DowncastBothNode::new(args[2].clone()); if use_legacy.eval(()).await { let generate_brightness_contrast_legacy_mapper_node = GenerateBrightnessContrastLegacyMapperNode::new(brightness, contrast); let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_legacy_mapper_node.eval(()))); let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() } else { let generate_brightness_contrast_mapper_node = GenerateBrightnessContrastMapperNode::new(brightness, contrast); let map_image_frame_node = graphene_std::raster::MapImageNode::new(ValueNode::new(generate_brightness_contrast_mapper_node.eval(()))); let map_image_frame_node = FutureWrapperNode::new(map_image_frame_node); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(ValueNode::new(map_image_frame_node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() } }) }, @@ -495,10 +402,10 @@ fn node_registry() -> HashMap"), |args| { Box::pin(async move { - let node: DowncastBothNode, graphene_core::EditorApi> = graphene_std::any::DowncastBothNode::new(args[0]); + let node: DowncastBothNode, graphene_core::EditorApi> = graphene_std::any::DowncastBothNode::new(args[0].clone()); let node = >::new(node); let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(graphene_core::value::ValueNode::new(node)); - Box::pin(any) as TypeErasedPinned + any.into_type_erased() }) }, NodeIOTypes::new( diff --git a/node-graph/vulkan-executor/src/executor.rs b/node-graph/vulkan-executor/src/executor.rs index 31bf2fb5..0826f9c0 100644 --- a/node-graph/vulkan-executor/src/executor.rs +++ b/node-graph/vulkan-executor/src/executor.rs @@ -2,7 +2,7 @@ use std::error::Error; use super::context::Context; -use graph_craft::executor::Executor; +use graph_craft::graphene_compiler::Executor; use graph_craft::proto::LocalFuture; use graphene_core::gpu::PushConstants; diff --git a/node-graph/wgpu-executor/src/executor.rs b/node-graph/wgpu-executor/src/executor.rs index 1c3c5b5c..f82c0268 100644 --- a/node-graph/wgpu-executor/src/executor.rs +++ b/node-graph/wgpu-executor/src/executor.rs @@ -5,7 +5,7 @@ use wgpu::util::DeviceExt; use super::context::Context; use bytemuck::Pod; use dyn_any::StaticTypeSized; -use graph_craft::{executor::Executor, proto::LocalFuture}; +use graph_craft::{graphene_compiler::Executor, proto::LocalFuture}; #[derive(Debug)] pub struct GpuExecutor<'a, I: StaticTypeSized, O> {