From 3657b37574ad120108cc7641d335583a2a94725c Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Wed, 10 Jul 2024 14:18:21 +0200 Subject: [PATCH] Deprecate LetNodes in favor of new scope API (#1814) * WIP * Start deprecating let nodes * Replace WasmEditorApi network imports with new Scope input * Add missing unwrap * Add #[serde(default)] to scope_injections * Restructure WasmEditorApi definition to be available as a TaggedValue * Fix text node * Use stable toolchain in nix shell again * Code review * FIx text node and remove all remaining warnings * Require executor input to be 'static --------- Co-authored-by: Keavon Chambers --- Cargo.lock | 9 +- Cargo.toml | 1 + editor/src/dispatcher.rs | 105 +++--- .../export_dialog_message_handler.rs | 2 +- .../new_document_dialog_message_handler.rs | 4 +- .../preferences_dialog_message_handler.rs | 2 +- .../document/document_message_handler.rs | 2 +- .../document/graph_operation/utility_types.rs | 2 +- .../node_graph/document_node_types.rs | 354 ++++-------------- .../document/node_graph/node_properties.rs | 22 +- .../utility_types/document_metadata.rs | 4 +- .../tool/tool_messages/ellipse_tool.rs | 2 +- .../tool/tool_messages/freehand_tool.rs | 2 +- .../messages/tool/tool_messages/line_tool.rs | 2 +- .../messages/tool/tool_messages/path_tool.rs | 8 +- .../messages/tool/tool_messages/pen_tool.rs | 2 +- .../tool/tool_messages/polygon_tool.rs | 2 +- .../tool/tool_messages/rectangle_tool.rs | 2 +- .../tool/tool_messages/spline_tool.rs | 2 +- .../messages/tool/tool_messages/text_tool.rs | 2 +- editor/src/node_graph_executor.rs | 83 ++-- node-graph/gcore/src/application_io.rs | 124 +++--- .../gcore/src/graphic_element/renderer.rs | 2 +- node-graph/gcore/src/lib.rs | 2 +- node-graph/gcore/src/memo.rs | 85 +---- node-graph/gcore/src/raster/adjustments.rs | 2 +- node-graph/gcore/src/raster/curve.rs | 4 +- node-graph/gcore/src/text.rs | 2 +- node-graph/gcore/src/text/font_cache.rs | 2 +- node-graph/gcore/src/value.rs | 17 + .../gpu-compiler-bin-wrapper/src/lib.rs | 2 +- node-graph/gpu-executor/Cargo.toml | 1 - node-graph/gpu-executor/src/lib.rs | 28 +- node-graph/graph-craft/Cargo.toml | 14 + node-graph/graph-craft/src/document.rs | 57 ++- node-graph/graph-craft/src/document/value.rs | 24 ++ .../graph-craft/src/graphene_compiler.rs | 7 + node-graph/graph-craft/src/lib.rs | 2 + node-graph/graph-craft/src/null.png | Bin 0 -> 94 bytes .../graph-craft/src/wasm_application_io.rs | 228 +++++++++++ node-graph/graphene-cli/src/main.rs | 15 +- node-graph/gstd/Cargo.toml | 2 +- node-graph/gstd/src/gpu_nodes.rs | 5 +- node-graph/gstd/src/imaginate.rs | 5 +- node-graph/gstd/src/null.png | Bin 241 -> 0 bytes node-graph/gstd/src/raster.rs | 4 +- node-graph/gstd/src/vector.rs | 2 +- node-graph/gstd/src/wasm_application_io.rs | 254 ++----------- .../src/dynamic_executor.rs | 18 +- .../interpreted-executor/src/node_registry.rs | 175 +++------ node-graph/wgpu-executor/Cargo.toml | 1 - node-graph/wgpu-executor/src/executor.rs | 20 +- node-graph/wgpu-executor/src/lib.rs | 29 +- shell.nix | 5 +- website/other/bezier-rs-demos/wasm/src/lib.rs | 2 +- 55 files changed, 774 insertions(+), 980 deletions(-) create mode 100644 node-graph/graph-craft/src/null.png create mode 100644 node-graph/graph-craft/src/wasm_application_io.rs delete mode 100644 node-graph/gstd/src/null.png diff --git a/Cargo.lock b/Cargo.lock index f5f88a5be..5c88792c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2245,7 +2245,6 @@ dependencies = [ "dyn-any", "futures", "glam", - "graph-craft", "graphene-core", "log", "node-macro", @@ -2264,11 +2263,18 @@ dependencies = [ "dyn-any", "glam", "graphene-core", + "js-sys", "log", "num-traits", + "reqwest 0.12.5", "rustc-hash 2.0.0", "serde", "specta", + "url", + "wasm-bindgen", + "web-sys", + "wgpu-executor", + "winit", ] [[package]] @@ -7401,7 +7407,6 @@ dependencies = [ "futures-intrusive", "glam", "gpu-executor", - "graph-craft", "graphene-core", "log", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 9ae696828..95bdafc0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ resolver = "2" dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam"] } graphene-core = { path = "node-graph/gcore" } graph-craft = { path = "node-graph/graph-craft", features = ["serde"] } +wgpu-executor = { path = "node-graph/wgpu-executor" } bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any"] } node-macro = { path = "node-graph/node-macro" } diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 4784eaa33..392ab54fb 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -410,65 +410,78 @@ mod test { assert_eq!(layers_after_copy[5], shape_id); } - #[tokio::test] + #[test] /// This test will fail when you make changes to the underlying serialization format for a document. - async fn check_if_demo_art_opens() { - use crate::messages::layout::utility_types::widget_prelude::*; + fn check_if_demo_art_opens() { + futures::executor::block_on(async { + use crate::messages::layout::utility_types::widget_prelude::*; - let print_problem_to_terminal_on_failure = |value: &String| { - println!(); - println!("-------------------------------------------------"); - println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file."); - println!(); - println!("DisplayDialogError details:"); - println!(); - println!("Description: {value}"); - println!("-------------------------------------------------"); - println!(); + let print_problem_to_terminal_on_failure = |value: &String| { + println!(); + println!("-------------------------------------------------"); + println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file."); + println!(); + println!("DisplayDialogError details:"); + println!(); + println!("Description: {value}"); + println!("-------------------------------------------------"); + println!(); - panic!() - }; + panic!() + }; - init_logger(); - let mut editor = Editor::create(); + init_logger(); + let mut editor = Editor::create(); - for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK { - let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap(); + // UNCOMMENT THIS FOR RUNNING UNDER MIRI + // + // let files = [ + // include_str!("../../demo-artwork/isometric-fountain.graphite"), + // include_str!("../../demo-artwork/just-a-potted-cactus.graphite"), + // include_str!("../../demo-artwork/procedural-string-lights.graphite"), + // include_str!("../../demo-artwork/red-dress.graphite"), + // include_str!("../../demo-artwork/valley-of-spires.graphite"), + // ]; + // for (id, document_serialized_content) in files.iter().enumerate() { + // let document_name = format!("document {id}"); - assert_eq!( - document_serialized_content.lines().count(), - 1, - "Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)", - ); + for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK { + let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap(); - let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile { - document_name: document_name.into(), - document_serialized_content, - }); - println!("Responses:\n{responses:#?}"); + assert_eq!( + document_serialized_content.lines().count(), + 1, + "Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)", + ); - // Check if the graph renders - let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler; - portfolio - .executor - .submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE) - .expect("submit_node_graph_evaluation failed"); - crate::node_graph_executor::run_node_graph().await; - let mut messages = VecDeque::new(); - editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render"); + let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile { + document_name: document_name.into(), + document_serialized_content: document_serialized_content.into(), + }); - for response in responses { - // Check for the existence of the file format incompatibility warning dialog after opening the test file - if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response { - if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value { - if let LayoutGroup::Row { widgets } = &sub_layout[0] { - if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget { - print_problem_to_terminal_on_failure(value); + // Check if the graph renders + let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler; + portfolio + .executor + .submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE) + .expect("submit_node_graph_evaluation failed"); + crate::node_graph_executor::run_node_graph().await; + let mut messages = VecDeque::new(); + editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render"); + + for response in responses { + // Check for the existence of the file format incompatibility warning dialog after opening the test file + if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response { + if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value { + if let LayoutGroup::Row { widgets } = &sub_layout[0] { + if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget { + print_problem_to_terminal_on_failure(value); + } } } } } } - } + }); } } diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs index 3821a8f5f..90cebd9ed 100644 --- a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs @@ -84,7 +84,7 @@ impl LayoutHolder for ExportDialogMessageHandler { NumberInput::new(Some(self.scale_factor)) .unit("") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .disabled(self.file_type == FileType::Svg) .on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into()) .min_width(200) diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index 6f1433aad..058c0e3e7 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -95,7 +95,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler { .label("W") .unit(" px") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .is_integer(true) .disabled(self.infinite) .min_width(100) @@ -106,7 +106,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler { .label("H") .unit(" px") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .is_integer(true) .disabled(self.infinite) .min_width(100) diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs index 7911b4b83..fd56d2c75 100644 --- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs +++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs @@ -63,7 +63,7 @@ impl PreferencesDialogMessageHandler { NumberInput::new(Some(preferences.imaginate_refresh_frequency)) .unit(" seconds") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .min_width(200) .on_update(|number_input: &NumberInput| PreferencesMessage::ImaginateRefreshFrequency { seconds: number_input.value.unwrap() }.into()) .widget_holder(), diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index ddd443fb4..3a4418442 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -306,7 +306,7 @@ impl MessageHandler> for DocumentMessag let insert_index = parent .children(self.metadata()) .enumerate() - .find_map(|(index, item)| self.selected_nodes.selected_layers(self.metadata()).any(|x| x == item).then_some(index as usize)) + .find_map(|(index, item)| self.selected_nodes.selected_layers(self.metadata()).any(|x| x == item).then_some(index)) .unwrap_or(0); // Store a history step before doing anything diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 0b02ed7d7..425996028 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -321,7 +321,7 @@ impl<'a> ModifyInputsContext<'a> { pub fn insert_text(&mut self, text: String, font: Font, size: f64, layer: NodeId) { let text = resolve_document_node_type("Text").expect("Text node does not exist").to_document_node( [ - NodeInput::network(graph_craft::concrete!(graphene_std::wasm_application_io::WasmEditorApi), 0), + NodeInput::scope("editor-api"), NodeInput::value(TaggedValue::String(text), false), NodeInput::value(TaggedValue::Font(font), false), NodeInput::value(TaggedValue::F64(size), false), diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs index 91a01c997..fc0fc9dc3 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_types.rs @@ -20,6 +20,7 @@ use graphene_core::text::Font; use graphene_core::transform::Footprint; use graphene_core::vector::VectorData; use graphene_core::*; +use graphene_std::application_io::RenderConfig; use graphene_std::wasm_application_io::WasmEditorApi; #[cfg(feature = "gpu")] use {gpu_executor::*, graphene_core::application_io::SurfaceHandle, wgpu_executor::WgpuExecutor}; @@ -320,23 +321,6 @@ fn static_nodes() -> Vec { properties: node_properties::artboard_properties, ..Default::default() }, - // TODO: Does this need an internal Cull node to be added to its implementation? - DocumentNodeDefinition { - name: "Input Frame", - category: "Ignore", - implementation: DocumentNodeImplementation::proto("graphene_core::ExtractImageFrame"), - inputs: vec![DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }], - outputs: vec![DocumentOutputType { - name: "Image Frame", - data_type: FrontendGraphDataType::Raster, - }], - properties: node_properties::node_no_properties, - ..Default::default() - }, DocumentNodeDefinition { name: "Load Image", category: "Structural", @@ -345,7 +329,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Load Resource".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 0), NodeInput::network(concrete!(String), 1)], + inputs: vec![NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode<_>")), ..Default::default() }, @@ -373,7 +357,7 @@ fn static_nodes() -> Vec { DocumentInputType { name: "api", data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), + default: NodeInput::scope("editor-api"), }, DocumentInputType { name: "path", @@ -396,7 +380,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Create Canvas".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 0)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), skip_deduplication: true, ..Default::default() @@ -415,11 +399,6 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }], outputs: vec![DocumentOutputType { name: "Canvas", data_type: FrontendGraphDataType::General, @@ -440,7 +419,7 @@ fn static_nodes() -> Vec { }, DocumentNode { name: "Create Canvas".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), skip_deduplication: true, ..Default::default() @@ -465,18 +444,11 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![ - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, - ], + inputs: vec![DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::Raster, + default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + }], outputs: vec![DocumentOutputType { name: "Canvas", data_type: FrontendGraphDataType::General, @@ -491,7 +463,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Create Canvas".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 2)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), skip_deduplication: true, ..Default::default() @@ -534,11 +506,6 @@ fn static_nodes() -> Vec { false, ), }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, ], properties: node_properties::rasterize_properties, outputs: vec![DocumentOutputType { @@ -547,136 +514,6 @@ fn static_nodes() -> Vec { }], ..Default::default() }, - DocumentNodeDefinition { - // This essentially builds the concept of a closure where we store variables (`let` bindings) so they can be accessed within this scope. - name: "Begin Scope", - category: "Ignore", - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], - nodes: [ - DocumentNode { - name: "SetNode".to_string(), - manual_composition: Some(concrete!(WasmEditorApi)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::SomeNode")), - ..Default::default() - }, - DocumentNode { - name: "LetNode".to_string(), - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::LetNode<_>")), - ..Default::default() - }, - DocumentNode { - name: "RefNode".to_string(), - manual_composition: Some(concrete!(())), - inputs: vec![NodeInput::lambda(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::RefNode<_, _>")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - - ..Default::default() - }), - inputs: vec![DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::Raster, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }], - outputs: vec![ - DocumentOutputType { - name: "Scope", - data_type: FrontendGraphDataType::General, - }, - DocumentOutputType { - name: "Binding", - data_type: FrontendGraphDataType::Raster, - }, - ], - properties: |_document_node, _node_id, _context| node_properties::string_properties("Binds the input in a local scope as a variable"), - ..Default::default() - }, - DocumentNodeDefinition { - name: "End Scope", - category: "Ignore", - implementation: DocumentNodeImplementation::proto("graphene_core::memo::EndLetNode<_, _>"), - inputs: vec![ - DocumentInputType { - name: "Scope", - data_type: FrontendGraphDataType::General, - default: NodeInput::value(TaggedValue::None, true), - }, - DocumentInputType { - name: "Data", - data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - }, - ], - outputs: vec![DocumentOutputType { - name: "Frame", - data_type: FrontendGraphDataType::Raster, - }], - properties: |_document_node, _node_id, _context| node_properties::string_properties("Consumes the scope opened by the Begin Scope node and evaluates the contained node network"), - ..Default::default() - }, - DocumentNodeDefinition { - name: "Output", - category: "Ignore", - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(3), 0)], - nodes: [ - DocumentNode { - name: "Create Canvas".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), - skip_deduplication: true, - ..Default::default() - }, - DocumentNode { - name: "Cache".to_string(), - manual_composition: Some(concrete!(())), - inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), - ..Default::default() - }, - DocumentNode { - name: "Conversion".to_string(), - inputs: vec![NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, GraphicGroup>")), - ..Default::default() - }, - DocumentNode { - name: "RenderNode".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1), NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _>")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![ - DocumentInputType { - name: "Output", - data_type: FrontendGraphDataType::Raster, - default: NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::default()), true), - }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, - ], - outputs: vec![], - properties: node_properties::output_properties, - ..Default::default() - }, DocumentNodeDefinition { name: "Image Frame", category: "General", @@ -1150,7 +987,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Extract Executor".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, &WgpuExecutor>")), ..Default::default() }, @@ -1174,18 +1011,11 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![ - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::value(TaggedValue::F64(0.), true), - }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, - ], + inputs: vec![DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::General, + default: NodeInput::value(TaggedValue::F64(0.), true), + }], outputs: vec![DocumentOutputType { name: "Uniform", data_type: FrontendGraphDataType::General, @@ -1201,7 +1031,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Extract Executor".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, &WgpuExecutor>")), ..Default::default() }, @@ -1225,18 +1055,11 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![ - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::value(TaggedValue::None, true), - }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, - ], + inputs: vec![DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::General, + default: NodeInput::value(TaggedValue::None, true), + }], outputs: vec![DocumentOutputType { name: "Storage", data_type: FrontendGraphDataType::General, @@ -1252,7 +1075,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Extract Executor".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 2)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, &WgpuExecutor>")), ..Default::default() }, @@ -1282,11 +1105,6 @@ fn static_nodes() -> Vec { data_type: FrontendGraphDataType::General, default: NodeInput::value(TaggedValue::None, true), }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, DocumentInputType { name: "In", data_type: FrontendGraphDataType::General, @@ -1309,7 +1127,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Extract Executor".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, &WgpuExecutor>")), ..Default::default() }, @@ -1344,11 +1162,6 @@ fn static_nodes() -> Vec { data_type: FrontendGraphDataType::General, default: NodeInput::network(concrete!(gpu_executor::PipelineLayout), 0), }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 1), - }, DocumentInputType { name: "In", data_type: FrontendGraphDataType::General, @@ -1410,7 +1223,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Extract Executor".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, &WgpuExecutor>")), ..Default::default() }, @@ -1434,18 +1247,11 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![ - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::value(TaggedValue::None, true), - }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, - ], + inputs: vec![DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::General, + default: NodeInput::value(TaggedValue::None, true), + }], outputs: vec![DocumentOutputType { name: "PipelineResult", data_type: FrontendGraphDataType::General, @@ -1461,7 +1267,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Extract Executor".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, &WgpuExecutor>")), ..Default::default() }, @@ -1485,18 +1291,11 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![ - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::value(TaggedValue::None, true), - }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, - ], + inputs: vec![DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::General, + default: NodeInput::value(TaggedValue::None, true), + }], outputs: vec![DocumentOutputType { name: "Buffer", data_type: FrontendGraphDataType::General, @@ -1512,7 +1311,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Create Gpu Surface".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 0)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("gpu_executor::CreateGpuSurfaceNode")), ..Default::default() }, @@ -1530,11 +1329,6 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }], outputs: vec![DocumentOutputType { name: "GpuSurface", data_type: FrontendGraphDataType::General, @@ -1550,7 +1344,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Extract Executor".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 2)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, &WgpuExecutor>")), ..Default::default() }, @@ -1582,11 +1376,6 @@ fn static_nodes() -> Vec { data_type: FrontendGraphDataType::General, default: NodeInput::value(TaggedValue::None, true), }, - DocumentInputType { - name: "EditorApi", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, ], outputs: vec![DocumentOutputType { name: "RenderedTexture", @@ -1603,7 +1392,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { name: "Extract Executor".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], + inputs: vec![NodeInput::scope("editor-api")], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, &WgpuExecutor>")), ..Default::default() }, @@ -1627,18 +1416,11 @@ fn static_nodes() -> Vec { .collect(), ..Default::default() }), - inputs: vec![ - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), - }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, - ], + inputs: vec![DocumentInputType { + name: "In", + data_type: FrontendGraphDataType::General, + default: NodeInput::value(TaggedValue::ImageFrame(ImageFrame::empty()), true), + }], outputs: vec![DocumentOutputType { name: "Texture", data_type: FrontendGraphDataType::General, @@ -1657,11 +1439,6 @@ fn static_nodes() -> Vec { data_type: FrontendGraphDataType::General, default: NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true), }, - DocumentInputType { - name: "In", - data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), - }, ], outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)], ..Default::default() @@ -2414,7 +2191,7 @@ fn static_nodes() -> Vec { category: "Vector", implementation: DocumentNodeImplementation::proto("graphene_core::text::TextGeneratorNode<_, _, _>"), inputs: vec![ - DocumentInputType::none(), + DocumentInputType::new("Editor API", FrontendGraphDataType::General, NodeInput::scope("editor-api")), DocumentInputType::value("Text", TaggedValue::String("Lorem ipsum".to_string()), false), DocumentInputType::value( "Font", @@ -2424,7 +2201,7 @@ fn static_nodes() -> Vec { DocumentInputType::value("Size", TaggedValue::F64(24.), false), ], outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::VectorData)], - properties: node_properties::node_section_font, + properties: node_properties::text_properties, ..Default::default() }, DocumentNodeDefinition { @@ -2895,7 +2672,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN name: "Imaginate".into(), inputs: vec![ NodeInput::node(NodeId(0), 0), - NodeInput::network(concrete!(WasmEditorApi), 1), + NodeInput::scope("editor-api"), NodeInput::network(concrete!(ImaginateController), 2), NodeInput::network(concrete!(f64), 3), NodeInput::network(concrete!(Option), 4), @@ -2925,7 +2702,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN DocumentInputType { name: "Editor Api", data_type: FrontendGraphDataType::General, - default: NodeInput::network(concrete!(WasmEditorApi), 0), + default: NodeInput::scope("editor-api"), }, DocumentInputType::value("Controller", TaggedValue::ImaginateController(Default::default()), false), DocumentInputType::value("Seed", TaggedValue::U64(0), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index @@ -2992,18 +2769,9 @@ impl DocumentNodeDefinition { } } -pub fn wrap_network_in_scope(mut network: NodeNetwork, hash: u64) -> NodeNetwork { +pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc) -> NodeNetwork { network.generate_node_paths(&[]); - let mut begin_scope = resolve_document_node_type("Begin Scope") - .expect("Begin Scope node type not found") - .to_document_node(vec![NodeInput::network(concrete!(WasmEditorApi), 0)], DocumentNodeMetadata::position((-12, -3))); - if let DocumentNodeImplementation::Network(g) = &mut begin_scope.implementation { - if let Some(node) = g.nodes.get_mut(&NodeId(0)) { - node.world_state_hash = hash; - } - } - let inner_network = DocumentNode { name: "Scope".to_string(), implementation: DocumentNodeImplementation::Network(network), @@ -3014,13 +2782,13 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, hash: u64) -> NodeNetwork let render_node = graph_craft::document::DocumentNode { name: "Output".into(), - inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(0), 1)], + inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)], implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork { exports: vec![NodeInput::node(NodeId(2), 0)], nodes: [ DocumentNode { name: "Create Canvas".to_string(), - inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 1)], + inputs: vec![NodeInput::network(concrete!(&WasmEditorApi), 1)], implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), skip_deduplication: true, ..Default::default() @@ -3032,10 +2800,11 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, hash: u64) -> NodeNetwork implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")), ..Default::default() }, + // TODO: Add conversion step DocumentNode { name: "RenderNode".to_string(), + manual_composition: Some(concrete!(RenderConfig)), inputs: vec![ - NodeInput::network(concrete!(WasmEditorApi), 1), NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T))), 0), NodeInput::node(NodeId(1), 0), ], @@ -3055,17 +2824,20 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, hash: u64) -> NodeNetwork // wrap the inner network in a scope let nodes = vec![ - begin_scope, inner_network, render_node, - resolve_document_node_type("End Scope") - .expect("End Scope node type not found") - .to_document_node(vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)], DocumentNodeMetadata::position((2, -3))), + DocumentNode { + name: "Editor Api".into(), + implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), + inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)], + ..Default::default() + }, ]; NodeNetwork { - exports: vec![NodeInput::node(NodeId(3), 0)], + exports: vec![NodeInput::node(NodeId(1), 0)], nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(), + scope_injections: [("editor-api".to_string(), (NodeId(2), concrete!(&WasmEditorApi)))].into_iter().collect(), ..Default::default() } } @@ -3097,7 +2869,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, hash: u64) -> NodeNetwork // let mut network = NodeNetwork { ..Default::default() }; // network.push_node_to_document_network(text_generator.to_document_node( // [ -// NodeInput::network(concrete!(WasmEditorApi), 0), +// NodeInput::scope("editor-api"), // NodeInput::value(TaggedValue::String(text), false), // NodeInput::value(TaggedValue::Font(font), false), // NodeInput::value(TaggedValue::F64(size), false), diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 74523beca..5a1513331 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -295,8 +295,8 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name NumberInput::new(Some(dvec2.x)) .label(x) .unit(unit) - .min(min.unwrap_or(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64))) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64))) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)), node_id, index)) .on_commit(commit_value) .widget_holder(), @@ -304,8 +304,8 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name NumberInput::new(Some(dvec2.y)) .label(y) .unit(unit) - .min(min.unwrap_or(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64))) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64))) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())), node_id, index)) .on_commit(commit_value) .widget_holder(), @@ -323,8 +323,8 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name .int() .label(x) .unit(unit) - .min(min.unwrap_or(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64))) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64))) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(update_value(update_x, node_id, index)) .on_commit(commit_value) .widget_holder(), @@ -333,8 +333,8 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name .int() .label(y) .unit(unit) - .min(min.unwrap_or(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64))) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64))) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(update_value(update_y, node_id, index)) .on_commit(commit_value) .widget_holder(), @@ -353,7 +353,7 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name .label(x) .unit(unit) .min(min.unwrap_or(0.)) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(update_value(update_x, node_id, index)) .on_commit(commit_value) .widget_holder(), @@ -363,7 +363,7 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name .label(y) .unit(unit) .min(min.unwrap_or(0.)) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(update_value(update_y, node_id, index)) .on_commit(commit_value) .widget_holder(), @@ -1769,7 +1769,7 @@ pub fn rasterize_properties(document_node: &DocumentNode, node_id: NodeId, _cont footprint_widget(document_node, node_id, 1) } -pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub fn text_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let text = text_area_widget(document_node, node_id, 1, "Text", true); let (font, style) = font_inputs(document_node, node_id, 2, "Font", true); let size = number_widget(document_node, node_id, 3, "Size", NumberInput::default().unit(" px").min(1.), true); diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index c371e7f45..93caa8db7 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -177,13 +177,13 @@ impl DocumentMetadata { // Should refer to output node - let mut awaiting_horizontal_flow = vec![(NodeId(std::u64::MAX), LayerNodeIdentifier::ROOT_PARENT)]; + let mut awaiting_horizontal_flow = vec![(NodeId(u64::MAX), LayerNodeIdentifier::ROOT_PARENT)]; let mut awaiting_primary_flow = vec![]; while let Some((horizontal_root_node_id, mut parent_layer_node)) = awaiting_horizontal_flow.pop() { let horizontal_flow_iter = graph.upstream_flow_back_from_nodes(vec![horizontal_root_node_id], FlowType::HorizontalFlow); // Skip the horizontal_root_node_id node - for (current_node, current_node_id) in horizontal_flow_iter.skip(if horizontal_root_node_id == NodeId(std::u64::MAX) { 0 } else { 1 }) { + for (current_node, current_node_id) in horizontal_flow_iter.skip(if horizontal_root_node_id == NodeId(u64::MAX) { 0 } else { 1 }) { if !current_node.visible { self.hidden.insert(current_node_id); } diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index b24e0d69f..c60b2572a 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -78,7 +78,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .unit(" px") .label("Weight") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() } diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index b3e44e4e4..6d2cffbfc 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -87,7 +87,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .unit(" px") .label("Weight") .min(1.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() } diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index 1b8c057f8..c64f3c7ec 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -75,7 +75,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .unit(" px") .label("Weight") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() } diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index 0d8403e5a..529a6a96d 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -98,8 +98,8 @@ impl LayoutHolder for PathTool { .label("X") .min_width(120) .disabled(x.is_none()) - .min(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64)) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .min(-((1_u64 << f64::MANTISSA_DIGITS) as f64)) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(move |number_input: &NumberInput| { if let Some(new_x) = number_input.value.or(x) { PathToolMessage::SelectedPointXChanged { new_x }.into() @@ -114,8 +114,8 @@ impl LayoutHolder for PathTool { .label("Y") .min_width(120) .disabled(y.is_none()) - .min(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64)) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .min(-((1_u64 << f64::MANTISSA_DIGITS) as f64)) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(move |number_input: &NumberInput| { if let Some(new_y) = number_input.value.or(y) { PathToolMessage::SelectedPointYChanged { new_y }.into() diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 315ec4a27..c2223d2f9 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -96,7 +96,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .unit(" px") .label("Weight") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() } diff --git a/editor/src/messages/tool/tool_messages/polygon_tool.rs b/editor/src/messages/tool/tool_messages/polygon_tool.rs index b183aad63..1ce063075 100644 --- a/editor/src/messages/tool/tool_messages/polygon_tool.rs +++ b/editor/src/messages/tool/tool_messages/polygon_tool.rs @@ -113,7 +113,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .unit(" px") .label("Weight") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() } diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 885419210..2fd446235 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -65,7 +65,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .unit(" px") .label("Weight") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() } diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index 2c01d559b..6ff85d218 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -86,7 +86,7 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder { .unit(" px") .label("Weight") .min(0.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()) .widget_holder() } diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index bb5ced1f0..6e892466c 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -104,7 +104,7 @@ fn create_text_widgets(tool: &TextTool) -> Vec { .label("Size") .int() .min(1.) - .max((1_u64 << std::f64::MANTISSA_DIGITS) as f64) + .max((1_u64 << f64::MANTISSA_DIGITS) as f64) .on_update(|number_input: &NumberInput| TextToolMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into()) .widget_holder(); vec![ diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index ca6adb4ad..a9251fe3d 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -39,14 +39,11 @@ pub struct NodeRuntime { executor: DynamicExecutor, receiver: Receiver, sender: InternalNodeGraphUpdateSender, - - /// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`]. - font_cache: FontCache, - /// Imaginate preferences made available to the graph through the [`WasmEditorApi`]. imaginate_preferences: ImaginatePreferences, + recompile_graph: bool, + + editor_api: Arc, - /// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web). - wasm_application_io: Option, graph_hash: Option, node_graph_errors: GraphErrors, resolved_types: ResolvedDocumentNodeTypes, @@ -89,7 +86,7 @@ pub struct ExecutionRequest { pub struct ExecutionResponse { execution_id: u64, result: Result, - responses: VecDeque, + responses: VecDeque, new_click_targets: HashMap>, new_vector_modify: HashMap, new_upstream_transforms: HashMap, @@ -103,6 +100,7 @@ pub enum NodeGraphUpdate { NodeGraphUpdateMessage(NodeGraphUpdateMessage), } +#[derive(Clone)] struct InternalNodeGraphUpdateSender(Sender); impl InternalNodeGraphUpdateSender { @@ -126,12 +124,19 @@ impl NodeRuntime { Self { executor: DynamicExecutor::default(), receiver, - sender: InternalNodeGraphUpdateSender(sender), + sender: InternalNodeGraphUpdateSender(sender.clone()), + imaginate_preferences: ImaginatePreferences::default(), + recompile_graph: true, - font_cache: FontCache::default(), - imaginate_preferences: Default::default(), + editor_api: WasmEditorApi { + font_cache: FontCache::default(), + imaginate_preferences: Box::new(ImaginatePreferences::default()), + node_graph_message_sender: Box::new(InternalNodeGraphUpdateSender(sender)), + + application_io: None, + } + .into(), - wasm_application_io: None, graph_hash: None, node_graph_errors: Vec::new(), resolved_types: ResolvedDocumentNodeTypes::default(), @@ -153,8 +158,26 @@ impl NodeRuntime { requests.reverse(); for request in requests { match request { - NodeRuntimeMessage::FontCacheUpdate(font_cache) => self.font_cache = font_cache, - NodeRuntimeMessage::ImaginatePreferencesUpdate(preferences) => self.imaginate_preferences = preferences, + NodeRuntimeMessage::FontCacheUpdate(font_cache) => { + self.editor_api = WasmEditorApi { + font_cache, + application_io: self.editor_api.application_io.clone(), + node_graph_message_sender: Box::new(self.sender.clone()), + imaginate_preferences: Box::new(self.imaginate_preferences.clone()), + } + .into(); + self.recompile_graph = true; + } + NodeRuntimeMessage::ImaginatePreferencesUpdate(preferences) => { + self.editor_api = WasmEditorApi { + font_cache: self.editor_api.font_cache.clone(), + application_io: self.editor_api.application_io.clone(), + node_graph_message_sender: Box::new(self.sender.clone()), + imaginate_preferences: Box::new(preferences), + } + .into(); + self.recompile_graph = true; + } NodeRuntimeMessage::ExecutionRequest(ExecutionRequest { execution_id, graph, render_config, .. }) => { @@ -182,23 +205,21 @@ impl NodeRuntime { } async fn execute_network(&mut self, graph: NodeNetwork, render_config: RenderConfig) -> Result { - if self.wasm_application_io.is_none() { - self.wasm_application_io = Some(WasmApplicationIo::new().await); + if self.editor_api.application_io.is_none() { + self.editor_api = WasmEditorApi { + application_io: Some(WasmApplicationIo::new().await.into()), + font_cache: self.editor_api.font_cache.clone(), + node_graph_message_sender: Box::new(self.sender.clone()), + imaginate_preferences: Box::new(ImaginatePreferences::default()), + } + .into(); } - let editor_api = WasmEditorApi { - font_cache: &self.font_cache, - imaginate_preferences: &self.imaginate_preferences, - application_io: self.wasm_application_io.as_ref().unwrap(), - node_graph_message_sender: &self.sender, - render_config, - image_frame: None, - }; - + let editor_api = &self.editor_api; // Required to ensure that the appropriate proto nodes are reinserted when the Editor API changes. let mut graph_input_hash = DefaultHasher::new(); editor_api.font_cache.hash(&mut graph_input_hash); - let font_hash_code = graph_input_hash.finish(); + let _font_hash_code = graph_input_hash.finish(); graph.hash(&mut graph_input_hash); let hash_code = graph_input_hash.finish(); @@ -206,8 +227,8 @@ impl NodeRuntime { self.graph_hash = None; } - if self.graph_hash.is_none() { - let scoped_network = wrap_network_in_scope(graph, font_hash_code); + if self.graph_hash.is_none() || self.recompile_graph { + let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone()); self.monitor_nodes = scoped_network .recursive_nodes() @@ -235,7 +256,7 @@ impl NodeRuntime { use graph_craft::graphene_compiler::Executor; let result = match self.executor.input_type() { - Some(t) if t == concrete!(WasmEditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()), + Some(t) if t == concrete!(RenderConfig) => (&self.executor).execute(render_config).await.map_err(|e| e.to_string()), Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()), Some(t) => Err(format!("Invalid input type {t:?}")), _ => Err(format!("No input type:\n{:?}", self.node_graph_errors)), @@ -259,7 +280,7 @@ impl NodeRuntime { } /// Updates state data - pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque) { + pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque) { // TODO: Consider optimizing this since it's currently O(m*n^2), with a sort it could be made O(m * n*log(n)) self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id))); @@ -325,7 +346,7 @@ impl NodeRuntime { let old_thumbnail_svg = self.thumbnail_renders.entry(parent_network_node_id).or_default(); if old_thumbnail_svg != &new_thumbnail_svg { - responses.add(FrontendMessage::UpdateNodeThumbnail { + responses.push_back(FrontendMessage::UpdateNodeThumbnail { id: parent_network_node_id, value: new_thumbnail_svg.to_svg_string(), }); @@ -576,7 +597,7 @@ impl NodeGraphExecutor { transform, } = execution_response; - responses.extend(existing_responses); + responses.extend(existing_responses.into_iter().map(Into::into)); responses.add(NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors }); responses.add(NodeGraphMessage::SendGraph); responses.add(OverlaysMessage::Draw); diff --git a/node-graph/gcore/src/application_io.rs b/node-graph/gcore/src/application_io.rs index e98eacf86..3bb50d869 100644 --- a/node-graph/gcore/src/application_io.rs +++ b/node-graph/gcore/src/application_io.rs @@ -1,16 +1,15 @@ -use crate::raster::ImageFrame; use crate::text::FontCache; use crate::transform::{Footprint, Transform, TransformMut}; use crate::vector::style::ViewMode; -use crate::{Color, Node}; -use dyn_any::{StaticType, StaticTypeSized}; +use dyn_any::{DynAny, StaticType, StaticTypeSized}; use alloc::sync::Arc; use core::fmt::Debug; use core::future::Future; use core::hash::{Hash, Hasher}; use core::pin::Pin; +use core::ptr::addr_of; use glam::DAffine2; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -133,6 +132,12 @@ pub trait NodeGraphUpdateSender { fn send(&self, message: NodeGraphUpdateMessage); } +impl NodeGraphUpdateSender for std::sync::Mutex { + fn send(&self, message: NodeGraphUpdateMessage) { + self.lock().as_mut().unwrap().send(message) + } +} + pub trait GetImaginatePreferences { fn get_host_name(&self) -> &str; } @@ -148,7 +153,7 @@ pub enum ExportFormat { Canvas, } -#[derive(Debug, Default, Clone, Copy, PartialEq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)] pub struct RenderConfig { pub viewport: Footprint, pub export_format: ExportFormat, @@ -157,82 +162,69 @@ pub struct RenderConfig { pub for_export: bool, } -pub struct EditorApi<'a, Io> { - // TODO: Is `image_frame` still used? I think it's only ever set to None. - pub image_frame: Option>, - pub font_cache: &'a FontCache, - pub application_io: &'a Io, - pub node_graph_message_sender: &'a dyn NodeGraphUpdateSender, - pub imaginate_preferences: &'a dyn GetImaginatePreferences, - pub render_config: RenderConfig, +struct Logger; + +impl NodeGraphUpdateSender for Logger { + fn send(&self, message: NodeGraphUpdateMessage) { + log::warn!("dispatching message with fallback node graph update sender {:?}", message); + } } -impl<'a, Io> Clone for EditorApi<'a, Io> { - fn clone(&self) -> Self { +struct DummyPreferences; + +impl GetImaginatePreferences for DummyPreferences { + fn get_host_name(&self) -> &str { + "dummy_endpoint" + } +} + +pub struct EditorApi { + /// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`]. + pub font_cache: FontCache, + /// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web). + pub application_io: Option>, + pub node_graph_message_sender: Box, + /// Imaginate preferences made available to the graph through the [`WasmEditorApi`]. + pub imaginate_preferences: Box, +} + +impl Eq for EditorApi {} + +impl Default for EditorApi { + fn default() -> Self { Self { - image_frame: self.image_frame.clone(), - font_cache: self.font_cache, - application_io: self.application_io, - node_graph_message_sender: self.node_graph_message_sender, - imaginate_preferences: self.imaginate_preferences, - render_config: self.render_config, + font_cache: FontCache::default(), + application_io: None, + node_graph_message_sender: Box::new(Logger), + imaginate_preferences: Box::new(DummyPreferences), } } } -impl<'a, T> PartialEq for EditorApi<'a, T> { - fn eq(&self, other: &Self) -> bool { - self.image_frame == other.image_frame && self.font_cache == other.font_cache - } -} - -impl<'a, T> Hash for EditorApi<'a, T> { +impl Hash for EditorApi { fn hash(&self, state: &mut H) { - self.image_frame.hash(state); self.font_cache.hash(state); + self.application_io.as_ref().map_or(0, |io| io.as_ref() as *const _ as usize).hash(state); + (self.node_graph_message_sender.as_ref() as *const dyn NodeGraphUpdateSender).hash(state); + (self.imaginate_preferences.as_ref() as *const dyn GetImaginatePreferences).hash(state); } } -impl<'a, T> Debug for EditorApi<'a, T> { +impl PartialEq for EditorApi { + fn eq(&self, other: &Self) -> bool { + self.font_cache == other.font_cache + && self.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) == other.application_io.as_ref().map_or(0, |io| addr_of!(io) as usize) + && std::ptr::eq(self.node_graph_message_sender.as_ref() as *const _, other.node_graph_message_sender.as_ref() as *const _) + && std::ptr::eq(self.imaginate_preferences.as_ref() as *const _, other.imaginate_preferences.as_ref() as *const _) + } +} + +impl Debug for EditorApi { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("EditorApi").field("image_frame", &self.image_frame).field("font_cache", &self.font_cache).finish() + f.debug_struct("EditorApi").field("font_cache", &self.font_cache).finish() } } -unsafe impl StaticType for EditorApi<'_, T> { - type Static = EditorApi<'static, T::Static>; -} - -impl<'a, T> AsRef> for EditorApi<'a, T> { - fn as_ref(&self) -> &EditorApi<'a, T> { - self - } -} - -// Required for the EndLetNode -impl<'a, IO> From> for Footprint { - fn from(value: EditorApi<'a, IO>) -> Self { - value.render_config.viewport - } -} - -// Required for the EndLetNode -impl<'a, IO> From> for () { - fn from(_value: EditorApi<'a, IO>) -> Self {} -} - -#[derive(Debug, Clone, Copy, Default)] -pub struct ExtractImageFrame; - -impl<'a: 'input, 'input, T> Node<'input, EditorApi<'a, T>> for ExtractImageFrame { - type Output = ImageFrame; - fn eval(&'input self, editor_api: EditorApi<'a, T>) -> Self::Output { - editor_api.image_frame.unwrap_or(ImageFrame::identity()) - } -} - -impl ExtractImageFrame { - pub fn new() -> Self { - Self - } +unsafe impl StaticType for EditorApi { + type Static = EditorApi; } diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 7c3be602c..43c834180 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -24,7 +24,7 @@ impl ClickTarget { /// Does the click target intersect the rectangle pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool { // Check if the matrix is not invertible - if layer_transform.matrix2.determinant().abs() <= std::f64::EPSILON { + if layer_transform.matrix2.determinant().abs() <= f64::EPSILON { return false; } let quad = layer_transform.inverse() * document_quad; diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 93f3193af..bd4fe3114 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -171,7 +171,7 @@ impl<'i, I: 'i, O: 'i> Node<'i, I> for Pin<&'i (dyn NodeIO<'i, I, Output = O> + } #[cfg(feature = "alloc")] -pub use crate::application_io::{ExtractImageFrame, SurfaceFrame, SurfaceId}; +pub use crate::application_io::{SurfaceFrame, SurfaceId}; #[cfg(feature = "wasm")] pub type WasmSurfaceHandle = application_io::SurfaceHandle; #[cfg(feature = "wasm")] diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index f440cec5e..2e2d229ef 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -4,7 +4,6 @@ use core::future::Future; #[cfg(feature = "alloc")] use alloc::sync::Arc; use core::cell::Cell; -use core::marker::PhantomData; use core::pin::Pin; /// Caches the output of a given Node and acts as a proxy @@ -46,7 +45,7 @@ impl MemoNode { } /// Caches the output of a given Node and acts as a proxy. -/// In contrast to the relgular `MemoNode`. This node ignores all input. +/// In contrast to the regular `MemoNode`. This node ignores all input. /// Using this node might result in the document not updating properly, /// use with caution. #[derive(Default)] @@ -137,85 +136,3 @@ impl MonitorNode { MonitorNode { io: Cell::new(None), node } } } - -// Caches the output of a given Node and acts as a proxy -/// It provides two modes of operation, it can either be set -/// when calling the node with a `Some` variant or the last -/// value that was added is returned when calling it with `None` -#[derive(Default)] -pub struct LetNode { - // We have to use an append only data structure to make sure the references - // to the cache entries are always valid - // TODO: We only ever access the last value so there is not really a reason for us - // to store the previous entries. This should be reworked in the future - cache: Cell>, -} -impl<'i, T: 'i + Clone> Node<'i, Option> for LetNode { - type Output = T; - fn eval(&'i self, input: Option) -> Self::Output { - if let Some(input) = input { - self.cache.set(Some(input.clone())); - input - } else { - let value = self.cache.take(); - self.cache.set(value.clone()); - value.expect("LetNode was not initialized. This can happen if you try to evaluate a node that depends on the EditorApi in the node_registry") - } - } - fn reset(&self) { - self.cache.set(None); - } -} - -impl LetNode { - pub fn new() -> LetNode { - LetNode { cache: Default::default() } - } -} - -/// Caches the output of a given Node and acts as a proxy -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct EndLetNode { - input: Input, - parameter: PhantomData, -} -impl<'i, T: 'i, Parameter: 'i + From, Input> Node<'i, T> for EndLetNode -where - Input: Node<'i, Parameter>, -{ - type Output = ::Output; - fn eval(&'i self, t: T) -> Self::Output { - let result = self.input.eval(Parameter::from(t)); - result - } -} - -impl EndLetNode { - pub const fn new(input: Input) -> EndLetNode { - EndLetNode { input, parameter: PhantomData } - } -} - -pub use crate::ops::SomeNode as InitNode; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct RefNode { - let_node: Let, - _t: PhantomData, -} - -impl<'i, T: 'i, Let> Node<'i, ()> for RefNode -where - Let: for<'a> Node<'a, Option>, -{ - type Output = >>::Output; - fn eval(&'i self, _: ()) -> Self::Output { - self.let_node.eval(None) - } -} - -impl RefNode { - pub const fn new(let_node: Let) -> RefNode { - RefNode { let_node, _t: PhantomData } - } -} diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 58fc134fd..70523174e 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -319,7 +319,7 @@ fn levels_node(color: Color, input_start: f64, input_mid: f64, input_end: f64, o }; // Input levels (Range: 0-1) - let highlights_minus_shadows = (input_highlights - input_shadows).max(f32::EPSILON).min(1.); + let highlights_minus_shadows = (input_highlights - input_shadows).clamp(f32::EPSILON, 1.); let color = color.map_rgb(|c| ((c - input_shadows).max(0.) / highlights_minus_shadows).min(1.)); // Midtones (Range: 0-1) diff --git a/node-graph/gcore/src/raster/curve.rs b/node-graph/gcore/src/raster/curve.rs index 8316d85a1..705958c37 100644 --- a/node-graph/gcore/src/raster/curve.rs +++ b/node-graph/gcore/src/raster/curve.rs @@ -110,7 +110,7 @@ impl CubicSplines { // Eliminate the current column in all rows below the current one for row_below_current in row + 1..4 { - assert!(augmented_matrix[row][row].abs() > core::f32::EPSILON); + assert!(augmented_matrix[row][row].abs() > f32::EPSILON); let scale_factor = augmented_matrix[row_below_current][row] / augmented_matrix[row][row]; for col in row..5 { @@ -122,7 +122,7 @@ impl CubicSplines { // Gaussian elimination: back substitution let mut solutions = [0.; 4]; for col in (0..4).rev() { - assert!(augmented_matrix[col][col].abs() > core::f32::EPSILON); + assert!(augmented_matrix[col][col].abs() > f32::EPSILON); solutions[col] = augmented_matrix[col][4] / augmented_matrix[col][col]; diff --git a/node-graph/gcore/src/text.rs b/node-graph/gcore/src/text.rs index 35f76babb..fd4aeeb89 100644 --- a/node-graph/gcore/src/text.rs +++ b/node-graph/gcore/src/text.rs @@ -15,7 +15,7 @@ pub struct TextGeneratorNode { } #[node_fn(TextGeneratorNode)] -fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData { +fn generate_text<'a: 'input, T: 'a>(editor: &'a EditorApi, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData { let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data)); crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None), false) } diff --git a/node-graph/gcore/src/text/font_cache.rs b/node-graph/gcore/src/text/font_cache.rs index 6eb09add1..ecdf2ec70 100644 --- a/node-graph/gcore/src/text/font_cache.rs +++ b/node-graph/gcore/src/text/font_cache.rs @@ -21,7 +21,7 @@ impl Default for Font { } } /// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`) -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, DynAny)] pub struct FontCache { /// Actual font file data used for rendering a font with ttf_parser and rustybuzz font_file_data: HashMap>, diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 136952ea4..48389f02f 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -39,6 +39,23 @@ impl From for ValueNode { } } +#[derive(Default, Debug, Clone, Copy)] +pub struct AsRefNode, U>(pub T, PhantomData); + +impl<'i, T: 'i + AsRef, U: 'i> Node<'i, ()> for AsRefNode { + type Output = &'i U; + #[inline(always)] + fn eval(&'i self, _input: ()) -> Self::Output { + self.0.as_ref() + } +} + +impl, U> AsRefNode { + pub const fn new(value: T) -> AsRefNode { + AsRefNode(value, PhantomData) + } +} + #[derive(Default, Debug, Clone)] pub struct RefCellMutNode(pub RefCell); diff --git a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs index a47f99993..17524071d 100644 --- a/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs +++ b/node-graph/gpu-compiler/gpu-compiler-bin-wrapper/src/lib.rs @@ -4,7 +4,7 @@ use graph_craft::{proto::ProtoNetwork, Type}; use std::io::Write; pub fn compile_spirv(request: &CompileRequest, compile_dir: Option<&str>, manifest_path: &str) -> anyhow::Result> { - let serialized_graph = serde_json::to_string(&gpu_executor::CompileRequest { + let serialized_graph = serde_json::to_string(&graph_craft::graphene_compiler::CompileRequest { networks: request.networks.clone(), io: request.shader_io.clone(), })?; diff --git a/node-graph/gpu-executor/Cargo.toml b/node-graph/gpu-executor/Cargo.toml index f9474da9b..2af7d5313 100644 --- a/node-graph/gpu-executor/Cargo.toml +++ b/node-graph/gpu-executor/Cargo.toml @@ -13,7 +13,6 @@ node-macro = { path = "../node-macro" } # Workspace dependencies graphene-core = { workspace = true, features = ["std", "alloc", "gpu"] } -graph-craft = { workspace = true } dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] } num-traits = { workspace = true } log = { workspace = true } diff --git a/node-graph/gpu-executor/src/lib.rs b/node-graph/gpu-executor/src/lib.rs index dc9778bb6..e3e338c60 100644 --- a/node-graph/gpu-executor/src/lib.rs +++ b/node-graph/gpu-executor/src/lib.rs @@ -1,14 +1,12 @@ -use bytemuck::{Pod, Zeroable}; -use graph_craft::proto::ProtoNetwork; +use dyn_any::{StaticType, StaticTypeSized}; +use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; +use graphene_core::raster::{Image, ImageFrame, Pixel, SRGBA8}; use graphene_core::*; use anyhow::Result; -use dyn_any::{StaticType, StaticTypeSized}; +use bytemuck::{Pod, Zeroable}; use futures::Future; use glam::{DAffine2, UVec3}; -use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; -use graphene_core::raster::{Image, ImageFrame, Pixel, SRGBA8}; - use std::borrow::Cow; use std::pin::Pin; use std::sync::Arc; @@ -61,15 +59,9 @@ pub trait GpuExecutor { fn create_surface(&self, window: SurfaceHandle) -> Result>>; } -pub trait SpirVCompiler { - fn compile(&self, network: &[ProtoNetwork], io: &ShaderIO) -> Result; -} - -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct CompileRequest { - pub networks: Vec, - pub io: ShaderIO, -} +// pub trait SpirVCompiler { +// fn compile(&self, network: &[ProtoNetwork], io: &ShaderIO) -> Result; +// } #[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] /// GPU constants that can be used as inputs to a shader. @@ -496,9 +488,9 @@ async fn read_output_buffer_node<'a: 'input, E: 'a + GpuExecutor>(buffer: Arc, Io: ApplicationIo>(editor_api: EditorApi<'a, Io>) -> Arc>> { - let canvas = editor_api.application_io.create_surface(); - let executor = editor_api.application_io.gpu_executor().unwrap(); +async fn create_gpu_surface<'a: 'input, E: 'a + GpuExecutor, Io: ApplicationIo + 'input>(editor_api: &'a EditorApi) -> Arc>> { + let canvas = editor_api.application_io.as_ref().unwrap().create_surface(); + let executor = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap(); Arc::new(executor.create_surface(canvas).unwrap()) } diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index 377e91a55..ca89e3c84 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0" default = ["dealloc_nodes"] serde = ["dep:serde", "graphene-core/serde", "glam/serde", "bezier-rs/serde"] dealloc_nodes = [] +wgpu = ["wgpu-executor"] [dependencies] # Local dependencies @@ -27,6 +28,19 @@ bezier-rs = { workspace = true } specta = { workspace = true } bytemuck = { workspace = true } rustc-hash = { workspace = true } +url = { workspace = true } +reqwest = { workspace = true } # Optional workspace dependencies +wgpu-executor = { workspace = true, optional = true } serde = { workspace = true, optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +# Workspace dependencies +web-sys = { workspace = true } +js-sys = { workspace = true } +wasm-bindgen = { workspace = true } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +# Workspace dependencies +winit = { workspace = true } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index eb5ee1dae..95019306f 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -3,7 +3,7 @@ use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; use dyn_any::{DynAny, StaticType}; pub use graphene_core::uuid::generate_uuid; -use graphene_core::{ProtoNodeIdentifier, Type}; +use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use glam::IVec2; use std::collections::hash_map::DefaultHasher; @@ -365,6 +365,7 @@ impl DocumentNode { } NodeInput::Network { import_type, .. } => (ProtoNodeInput::ManualComposition(import_type), ConstructionArgs::Nodes(vec![])), NodeInput::Inline(inline) => (ProtoNodeInput::None, ConstructionArgs::Inline(inline)), + NodeInput::Scope(_) => unreachable!("Scope input was not resolved"), } }; assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network { .. })), "received non resolved parameter"); @@ -452,6 +453,9 @@ pub enum NodeInput { /// Input that is provided by the parent network to this document node, instead of from a hardcoded value or another node within the same network. Network { import_type: Type, import_index: usize }, + /// Input that is extracted from the parent scopes the node resides in. The string argument is the key. + Scope(Cow<'static, str>), + /// A Rust source code string. Allows us to insert literal Rust code. Only used for GPU compilation. /// We can use this whenever we spin up Rustc. Sort of like inline assembly, but because our language is Rust, it acts as inline Rust. Inline(InlineRust), @@ -474,15 +478,23 @@ impl NodeInput { pub const fn node(node_id: NodeId, output_index: usize) -> Self { Self::Node { node_id, output_index, lambda: false } } + pub const fn lambda(node_id: NodeId, output_index: usize) -> Self { Self::Node { node_id, output_index, lambda: true } } + pub const fn value(tagged_value: TaggedValue, exposed: bool) -> Self { Self::Value { tagged_value, exposed } } + pub const fn network(import_type: Type, import_index: usize) -> Self { Self::Network { import_type, import_index } } + + pub fn scope(key: impl Into>) -> Self { + Self::Scope(key.into()) + } + fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) { if let &mut NodeInput::Node { node_id, output_index, lambda } = self { *self = NodeInput::Node { @@ -492,22 +504,27 @@ impl NodeInput { } } } + pub fn is_exposed(&self) -> bool { match self { NodeInput::Node { .. } => true, NodeInput::Value { exposed, .. } => *exposed, NodeInput::Network { .. } => true, NodeInput::Inline(_) => false, + NodeInput::Scope(_) => false, } } + pub fn ty(&self) -> Type { match self { NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"), NodeInput::Value { tagged_value, .. } => tagged_value.ty(), NodeInput::Network { import_type, .. } => import_type.clone(), NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"), + NodeInput::Scope(_) => unreachable!("ty() called on NodeInput::Scope"), } } + pub fn as_value(&self) -> Option<&TaggedValue> { if let NodeInput::Value { tagged_value, .. } = self { Some(tagged_value) @@ -515,6 +532,7 @@ impl NodeInput { None } } + pub fn as_node(&self) -> Option { if let NodeInput::Node { node_id, .. } = self { Some(*node_id) @@ -666,6 +684,10 @@ pub struct NodeNetwork { pub imports_metadata: (NodeId, IVec2), #[serde(default = "default_export_metadata")] pub exports_metadata: (NodeId, IVec2), + + /// A network may expose nodes as constants which can by used by other nodes using a `NodeInput::Scope(key)`. + #[serde(default)] + pub scope_injections: HashMap, } impl std::hash::Hash for NodeNetwork { @@ -688,6 +710,7 @@ impl Default for NodeNetwork { previewing: Default::default(), imports_metadata: default_import_metadata(), exports_metadata: default_export_metadata(), + scope_injections: Default::default(), } } } @@ -963,7 +986,7 @@ impl<'a> Iterator for FlowIter<'a> { let mut node_id = self.stack.pop()?; // Special handling for iterating from ROOT_PARENT in load_structure` - if node_id == NodeId(std::u64::MAX) { + if node_id == NodeId(u64::MAX) { if let Some(root_node) = self.network.get_root_node() { node_id = root_node.id } else { @@ -1000,6 +1023,7 @@ impl NodeNetwork { root_node_to_restore.id = f(root_node_to_restore.id); } } + self.scope_injections.values_mut().for_each(|(id, _ty)| *id = f(*id)); let nodes = std::mem::take(&mut self.nodes); self.nodes = nodes .into_iter() @@ -1110,6 +1134,18 @@ impl NodeNetwork { are_inputs_used } + pub fn resolve_scope_inputs(&mut self) { + for node in self.nodes.values_mut() { + for input in node.inputs.iter_mut() { + if let NodeInput::Scope(key) = input { + let (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect("Tried to import a non existent key from scope"); + // TODO use correct output index + *input = NodeInput::node(*import_id, 0); + } + } + } + } + /// Remove all nodes that contain [`DocumentNodeImplementation::Network`] by moving the nested nodes into the parent network. pub fn flatten(&mut self, node_id: NodeId) { self.flatten_with_fns(node_id, merge_ids, || NodeId(generate_uuid())) @@ -1204,6 +1240,17 @@ impl NodeNetwork { inner_network.map_ids(|inner_id| map_ids(id, inner_id)); let new_nodes = inner_network.nodes.keys().cloned().collect::>(); + for (key, value) in inner_network.scope_injections.into_iter() { + match self.scope_injections.entry(key) { + std::collections::hash_map::Entry::Occupied(o) => { + log::warn!("Found duplicate scope injection for key {}, ignoring", o.key()); + } + std::collections::hash_map::Entry::Vacant(v) => { + v.insert(value); + } + } + } + // Match the document node input and the inputs of the inner network for (nested_node_id, mut nested_node) in inner_network.nodes.into_iter() { if nested_node.name == "To Artboard" { @@ -1232,6 +1279,12 @@ impl NodeNetwork { } NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"), NodeInput::Inline(_) => (), + NodeInput::Scope(ref key) => { + log::debug!("flattening scope: {}", key); + let (import_id, _ty) = self.scope_injections.get(key.as_ref()).expect("Tried to import a non existent key from scope"); + // TODO use correct output index + nested_node.inputs[nested_input_index] = NodeInput::node(*import_id, 0); + } } } } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index ccfb6a7f8..5cf92cf93 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -2,6 +2,7 @@ use super::DocumentNode; use crate::graphene_compiler::Any; pub use crate::imaginate_input::{ImaginateCache, ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use crate::proto::{Any as DAny, FutureAny}; +use crate::wasm_application_io::WasmEditorApi; use graphene_core::raster::brush_cache::BrushCache; use graphene_core::raster::{BlendMode, LuminanceCalculation}; @@ -12,6 +13,7 @@ pub use dyn_any::StaticType; pub use glam::{DAffine2, DVec2, IVec2, UVec2}; use std::fmt::Display; use std::hash::Hash; +use std::marker::PhantomData; pub use std::sync::Arc; /// Macro to generate the tagged value enum. @@ -25,6 +27,8 @@ macro_rules! tagged_value { $( $(#[$meta] ) *$identifier( $ty ), )* RenderOutput(RenderOutput), SurfaceFrame(graphene_core::SurfaceFrame), + #[serde(skip)] + EditorApi(Arc) } // We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below) @@ -37,6 +41,7 @@ macro_rules! tagged_value { $( Self::$identifier(x) => {x.hash(state)}),* Self::RenderOutput(x) => x.hash(state), Self::SurfaceFrame(x) => x.hash(state), + Self::EditorApi(x) => x.hash(state), } } } @@ -48,6 +53,7 @@ macro_rules! tagged_value { $( Self::$identifier(x) => Box::new(x), )* Self::RenderOutput(x) => Box::new(x), Self::SurfaceFrame(x) => Box::new(x), + Self::EditorApi(x) => Box::new(x), } } /// Creates a graphene_core::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value @@ -57,6 +63,7 @@ macro_rules! tagged_value { $( Self::$identifier(_) => concrete!($ty), )* Self::RenderOutput(_) => concrete!(RenderOutput), Self::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame), + Self::EditorApi(_) => concrete!(&WasmEditorApi) } } /// Attempts to downcast the dynamic type to a tagged value @@ -172,6 +179,7 @@ tagged_value! { VectorModification(graphene_core::vector::VectorModification), CentroidType(graphene_core::vector::misc::CentroidType), BooleanOperation(graphene_core::vector::misc::BooleanOperation), + FontCache(Arc), } impl TaggedValue { @@ -218,6 +226,22 @@ impl UpcastNode { Self { value } } } +#[derive(Default, Debug, Clone, Copy)] +pub struct UpcastAsRefNode, U>(pub T, PhantomData); + +impl<'i, T: 'i + AsRef, U: 'i + StaticType> Node<'i, DAny<'i>> for UpcastAsRefNode { + type Output = FutureAny<'i>; + #[inline(always)] + fn eval(&'i self, _: DAny<'i>) -> Self::Output { + Box::pin(async move { Box::new(self.0.as_ref()) as DAny<'i> }) + } +} + +impl, U> UpcastAsRefNode { + pub const fn new(value: T) -> UpcastAsRefNode { + UpcastAsRefNode(value, PhantomData) + } +} #[derive(Debug, Clone, PartialEq, dyn_any::DynAny, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] diff --git a/node-graph/graph-craft/src/graphene_compiler.rs b/node-graph/graph-craft/src/graphene_compiler.rs index 43d9b3621..ee99180b6 100644 --- a/node-graph/graph-craft/src/graphene_compiler.rs +++ b/node-graph/graph-craft/src/graphene_compiler.rs @@ -13,6 +13,7 @@ impl Compiler { for id in node_ids { network.flatten(id); } + network.resolve_scope_inputs(); network.remove_redundant_id_nodes(); network.remove_dead_nodes(0); let proto_networks = network.into_proto_networks(); @@ -40,3 +41,9 @@ pub type Any<'a> = Box + 'a>; pub trait Executor { fn execute(&self, input: I) -> LocalFuture>>; } +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[cfg(feature = "wgpu")] +pub struct CompileRequest { + pub networks: Vec, + pub io: wgpu_executor::ShaderIO, +} diff --git a/node-graph/graph-craft/src/lib.rs b/node-graph/graph-craft/src/lib.rs index 19c9d831b..bd4e16d45 100644 --- a/node-graph/graph-craft/src/lib.rs +++ b/node-graph/graph-craft/src/lib.rs @@ -10,3 +10,5 @@ pub mod proto; pub mod graphene_compiler; pub mod imaginate_input; + +pub mod wasm_application_io; diff --git a/node-graph/graph-craft/src/null.png b/node-graph/graph-craft/src/null.png new file mode 100644 index 0000000000000000000000000000000000000000..1aca6ba9bed5b84a035ea69e60628c123bb2a821 GIT binary patch literal 94 zcmeAS@N?(olHy`uVBq!ia0vp^Od!kwBL7~QRScv!3p^r$G`BDaGcwGYBLNf?@N{tu k;h346kdW|$zmzopr04*gH+5i9m literal 0 HcmV?d00001 diff --git a/node-graph/graph-craft/src/wasm_application_io.rs b/node-graph/graph-craft/src/wasm_application_io.rs new file mode 100644 index 000000000..e15ea3f5a --- /dev/null +++ b/node-graph/graph-craft/src/wasm_application_io.rs @@ -0,0 +1,228 @@ +use dyn_any::StaticType; +#[cfg(target_arch = "wasm32")] +use graphene_core::application_io::SurfaceHandleFrame; +use graphene_core::application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId}; +#[cfg(feature = "wgpu")] +use wgpu_executor::WgpuExecutor; + +use core::future::Future; +#[cfg(target_arch = "wasm32")] +use js_sys::{Object, Reflect}; +use std::collections::HashMap; +use std::pin::Pin; +#[cfg(target_arch = "wasm32")] +use std::sync::atomic::AtomicU64; +use std::sync::Arc; +#[cfg(not(target_arch = "wasm32"))] +use std::sync::Mutex; +#[cfg(feature = "tokio")] +use tokio::io::AsyncReadExt; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; +#[cfg(target_arch = "wasm32")] +use web_sys::window; +#[cfg(target_arch = "wasm32")] +use web_sys::HtmlCanvasElement; + +#[derive(Debug, Default)] +pub struct WasmApplicationIo { + #[cfg(target_arch = "wasm32")] + ids: AtomicU64, + #[cfg(feature = "wgpu")] + pub(crate) gpu_executor: Option, + #[cfg(not(target_arch = "wasm32"))] + windows: Mutex>>, + pub resources: HashMap>, +} + +impl WasmApplicationIo { + pub async fn new() -> Self { + #[cfg(all(feature = "wgpu", target_arch = "wasm32"))] + let executor = if let Some(gpu) = web_sys::window().map(|w| w.navigator().gpu()) { + let request_adapter = || { + let request_adapter = js_sys::Reflect::get(&gpu, &wasm_bindgen::JsValue::from_str("requestAdapter")).ok()?; + let function = request_adapter.dyn_ref::()?; + Some(function.call0(&gpu).ok()) + }; + let result = request_adapter(); + match result { + None => None, + Some(_) => WgpuExecutor::new().await, + } + } else { + None + }; + #[cfg(all(feature = "wgpu", not(target_arch = "wasm32")))] + let executor = WgpuExecutor::new().await; + let mut io = Self { + #[cfg(target_arch = "wasm32")] + ids: AtomicU64::new(0), + #[cfg(feature = "wgpu")] + gpu_executor: executor, + #[cfg(not(target_arch = "wasm32"))] + windows: Vec::new().into(), + resources: HashMap::new(), + }; + io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec())); + io + } +} + +unsafe impl StaticType for WasmApplicationIo { + type Static = WasmApplicationIo; +} + +impl<'a> From<&'a WasmEditorApi> for &'a WasmApplicationIo { + fn from(editor_api: &'a WasmEditorApi) -> Self { + editor_api.application_io.as_ref().unwrap() + } +} +#[cfg(feature = "wgpu")] +impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor { + fn from(app_io: &'a WasmApplicationIo) -> Self { + app_io.gpu_executor.as_ref().unwrap() + } +} + +pub type WasmEditorApi = graphene_core::application_io::EditorApi; + +impl ApplicationIo for WasmApplicationIo { + #[cfg(target_arch = "wasm32")] + type Surface = HtmlCanvasElement; + #[cfg(not(target_arch = "wasm32"))] + type Surface = Arc; + #[cfg(feature = "wgpu")] + type Executor = WgpuExecutor; + #[cfg(not(feature = "wgpu"))] + type Executor = (); + + #[cfg(target_arch = "wasm32")] + fn create_surface(&self) -> SurfaceHandle { + let wrapper = || { + let document = window().expect("should have a window in this context").document().expect("window should have a document"); + + let canvas: HtmlCanvasElement = document.create_element("canvas")?.dyn_into::()?; + let id = self.ids.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + // store the canvas in the global scope so it doesn't get garbage collected + let window = window().expect("should have a window in this context"); + let window = Object::from(window); + + let image_canvases_key = JsValue::from_str("imageCanvases"); + + let mut canvases = Reflect::get(&window, &image_canvases_key); + if canvases.is_err() { + Reflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap(); + canvases = Reflect::get(&window, &image_canvases_key); + } + + // Convert key and value to JsValue + let js_key = JsValue::from_str(format!("canvas{}", id).as_str()); + let js_value = JsValue::from(canvas.clone()); + + let canvases = Object::from(canvases.unwrap()); + + // Use Reflect API to set property + Reflect::set(&canvases, &js_key, &js_value)?; + Ok::<_, JsValue>(SurfaceHandle { + surface_id: graphene_core::SurfaceId(id), + surface: canvas, + }) + }; + + wrapper().expect("should be able to set canvas in global scope") + } + #[cfg(not(target_arch = "wasm32"))] + fn create_surface(&self) -> SurfaceHandle { + #[cfg(feature = "wayland")] + use winit::platform::wayland::EventLoopBuilderExtWayland; + + #[cfg(feature = "wayland")] + let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap(); + #[cfg(not(feature = "wayland"))] + let event_loop = winit::event_loop::EventLoop::new().unwrap(); + let window = winit::window::WindowBuilder::new() + .with_title("Graphite") + .with_inner_size(winit::dpi::PhysicalSize::new(800, 600)) + .build(&event_loop) + .unwrap(); + let window = Arc::new(window); + self.windows.lock().as_mut().unwrap().push(window.clone()); + SurfaceHandle { + surface_id: SurfaceId(window.id().into()), + surface: window, + } + } + + #[cfg(target_arch = "wasm32")] + fn destroy_surface(&self, surface_id: SurfaceId) { + let window = window().expect("should have a window in this context"); + let window = Object::from(window); + + let image_canvases_key = JsValue::from_str("imageCanvases"); + + let wrapper = || { + if let Ok(canvases) = Reflect::get(&window, &image_canvases_key) { + // Convert key and value to JsValue + let js_key = JsValue::from_str(format!("canvas{}", surface_id.0).as_str()); + + // Use Reflect API to set property + Reflect::delete_property(&canvases.into(), &js_key)?; + } + Ok::<_, JsValue>(()) + }; + + wrapper().expect("should be able to set canvas in global scope") + } + + #[cfg(not(target_arch = "wasm32"))] + fn destroy_surface(&self, _surface_id: SurfaceId) {} + + #[cfg(feature = "wgpu")] + fn gpu_executor(&self) -> Option<&Self::Executor> { + self.gpu_executor.as_ref() + } + + fn load_resource(&self, url: impl AsRef) -> Result { + let url = url::Url::parse(url.as_ref()).map_err(|_| ApplicationError::InvalidUrl)?; + log::trace!("Loading resource: {url:?}"); + match url.scheme() { + #[cfg(feature = "tokio")] + "file" => { + let path = url.to_file_path().map_err(|_| ApplicationError::NotFound)?; + let path = path.to_str().ok_or(ApplicationError::NotFound)?; + let path = path.to_owned(); + Ok(Box::pin(async move { + let file = tokio::fs::File::open(path).await.map_err(|_| ApplicationError::NotFound)?; + let mut reader = tokio::io::BufReader::new(file); + let mut data = Vec::new(); + reader.read_to_end(&mut data).await.map_err(|_| ApplicationError::NotFound)?; + Ok(Arc::from(data)) + }) as Pin, _>>>>) + } + "http" | "https" => { + let url = url.to_string(); + Ok(Box::pin(async move { + let client = reqwest::Client::new(); + let response = client.get(url).send().await.map_err(|_| ApplicationError::NotFound)?; + let data = response.bytes().await.map_err(|_| ApplicationError::NotFound)?; + Ok(Arc::from(data.to_vec())) + }) as Pin, _>>>>) + } + "graphite" => { + let path = url.path(); + let path = path.to_owned(); + log::trace!("Loading local resource: {path}"); + let data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone(); + Ok(Box::pin(async move { Ok(data.clone()) }) as Pin, _>>>>) + } + _ => Err(ApplicationError::NotFound), + } + } +} + +#[cfg(target_arch = "wasm32")] +pub type WasmSurfaceHandle = SurfaceHandle; +#[cfg(target_arch = "wasm32")] +pub type WasmSurfaceHandleFrame = SurfaceHandleFrame; diff --git a/node-graph/graphene-cli/src/main.rs b/node-graph/graphene-cli/src/main.rs index 8a5f8b309..9e75f0564 100644 --- a/node-graph/graphene-cli/src/main.rs +++ b/node-graph/graphene-cli/src/main.rs @@ -42,17 +42,16 @@ async fn main() -> Result<(), Box> { device.poll(wgpu::Maintain::Poll); }); - let editor_api = WasmEditorApi { - image_frame: None, - font_cache: &FontCache::default(), - application_io: &application_io, - node_graph_message_sender: &UpdateLogger {}, - imaginate_preferences: &ImaginatePreferences::default(), - render_config: graphene_core::application_io::RenderConfig::default(), + let _editor_api = WasmEditorApi { + font_cache: FontCache::default(), + application_io: Some(application_io.into()), + node_graph_message_sender: Box::new(UpdateLogger {}), + imaginate_preferences: Box::new(ImaginatePreferences::default()), }; + let render_config = graphene_core::application_io::RenderConfig::default(); loop { - let _result = (&executor).execute(editor_api.clone()).await?; + let _result = (&executor).execute(render_config).await?; std::thread::sleep(std::time::Duration::from_millis(16)); } } diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 803d7b2d6..7dcacf556 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -15,7 +15,7 @@ gpu = [ "gpu-executor", ] vulkan = ["gpu", "vulkan-executor"] -wgpu = ["gpu", "wgpu-executor", "dep:wgpu"] +wgpu = ["gpu", "wgpu-executor", "dep:wgpu", "graph-craft/wgpu"] quantization = ["autoquant"] wasm = ["wasm-bindgen", "web-sys", "js-sys"] imaginate = ["image/png", "base64", "js-sys", "web-sys", "wasm-bindgen-futures"] diff --git a/node-graph/gstd/src/gpu_nodes.rs b/node-graph/gstd/src/gpu_nodes.rs index 354863e15..16476e502 100644 --- a/node-graph/gstd/src/gpu_nodes.rs +++ b/node-graph/gstd/src/gpu_nodes.rs @@ -5,6 +5,7 @@ use gpu_executor::{GpuExecutor, ShaderIO, ShaderInput}; use graph_craft::document::value::TaggedValue; use graph_craft::document::*; use graph_craft::proto::*; +use graphene_core::application_io::ApplicationIo; use graphene_core::quantization::QuantizationChannels; use graphene_core::raster::*; use graphene_core::*; @@ -67,9 +68,9 @@ impl Clone for ComputePass { } #[node_macro::node_impl(MapGpuNode)] -async fn map_gpu<'a: 'input>(image: ImageFrame, node: DocumentNode, editor_api: graphene_core::application_io::EditorApi<'a, WasmApplicationIo>) -> ImageFrame { +async fn map_gpu<'a: 'input>(image: ImageFrame, node: DocumentNode, editor_api: &'a graphene_core::application_io::EditorApi) -> ImageFrame { log::debug!("Executing gpu node"); - let executor = &editor_api.application_io.gpu_executor.as_ref().unwrap(); + let executor = &editor_api.application_io.as_ref().and_then(|io| io.gpu_executor()).unwrap(); #[cfg(feature = "quantization")] let quantization = crate::quantization::generate_quantization_from_image_frame(&image); diff --git a/node-graph/gstd/src/imaginate.rs b/node-graph/gstd/src/imaginate.rs index c8c42ae81..632642c93 100644 --- a/node-graph/gstd/src/imaginate.rs +++ b/node-graph/gstd/src/imaginate.rs @@ -51,7 +51,10 @@ impl core::fmt::Debug for ImaginatePersistentData { impl Default for ImaginatePersistentData { fn default() -> Self { let mut status = ImaginateServerStatus::default(); + #[cfg(not(miri))] let client = new_client().map_err(|err| status = ImaginateServerStatus::Failed(err.to_string())).ok(); + #[cfg(miri)] + let client = None; let ImaginatePreferences { host_name } = Default::default(); Self { pending_server_check: None, @@ -263,7 +266,7 @@ struct ImaginateCommonImageRequest<'a> { #[allow(clippy::too_many_arguments)] pub async fn imaginate<'a, P: Pixel>( image: Image

, - editor_api: impl Future>, + editor_api: impl Future, controller: ImaginateController, seed: impl Future, res: impl Future>, diff --git a/node-graph/gstd/src/null.png b/node-graph/gstd/src/null.png deleted file mode 100644 index d9e090b3ea8842ba7e58777dab8a4d3249941481..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 241 zcmV8ON#KYym|KE&FA0LGMbT7 zNJd*rO;UuP?f;J}pPo_4XeLg1U1ME&!GFJh{`~!)jaW|-WAwk@pWZyW`0~d451+VI z?QE3TsAn|8pZ8B6y=P!xU|{4T-c$w#hM}7HMvWdddN>+QLV5G`<-Kdancsf-MRlY9 re|>fD`YYmHGDO3A)aX&82h3;yLLxH3_?X7S00000NkvXXu0mjf9^-7f diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 5e199e738..5197ca191 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -479,7 +479,7 @@ macro_rules! generate_imaginate_node { impl<'e, P: Pixel, E, C, $($t,)*> ImaginateNode where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* - E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, WasmEditorApi<'e>>>, + E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, { #[allow(clippy::too_many_arguments)] @@ -490,7 +490,7 @@ macro_rules! generate_imaginate_node { impl<'i, 'e: 'i, P: Pixel + 'i + Hash + Default, E: 'i, C: 'i, $($t: 'i,)*> Node<'i, ImageFrame

> for ImaginateNode where $($t: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, $o>>,)* - E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, WasmEditorApi<'e>>>, + E: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, &'e WasmEditorApi>>, C: for<'any_input> Node<'any_input, (), Output = DynFuture<'any_input, ImaginateController>>, { type Output = DynFuture<'i, ImageFrame

>; diff --git a/node-graph/gstd/src/vector.rs b/node-graph/gstd/src/vector.rs index 1fe5800f7..a024420ac 100644 --- a/node-graph/gstd/src/vector.rs +++ b/node-graph/gstd/src/vector.rs @@ -108,7 +108,7 @@ fn boolean_operation_node(graphic_group: GraphicGroup, boolean_operation: Boolea let transform_of_lower_into_space_of_upper = result.transform.inverse() * lower_vector_data.transform; let upper_path_string = to_svg_string(&result, DAffine2::IDENTITY); - let lower_path_string = to_svg_string(&lower_vector_data, transform_of_lower_into_space_of_upper); + let lower_path_string = to_svg_string(lower_vector_data, transform_of_lower_into_space_of_upper); #[allow(unused_unsafe)] let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) }; diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 7838c8fec..1943b0a49 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -1,5 +1,4 @@ -use dyn_any::StaticType; -use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportFormat, RenderConfig, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId}; +use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig, SurfaceHandle, SurfaceHandleFrame}; use graphene_core::raster::bbox::Bbox; use graphene_core::raster::Image; use graphene_core::raster::{color::SRGBA8, ImageFrame}; @@ -7,226 +6,17 @@ use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, I use graphene_core::transform::{Footprint, TransformMut}; use graphene_core::Color; use graphene_core::Node; -#[cfg(feature = "wgpu")] -use wgpu_executor::WgpuExecutor; use base64::Engine; use glam::DAffine2; use core::future::Future; -#[cfg(target_arch = "wasm32")] -use js_sys::{Object, Reflect}; -use std::cell::RefCell; -use std::collections::HashMap; use std::marker::PhantomData; -use std::pin::Pin; use std::sync::Arc; -#[cfg(feature = "tokio")] -use tokio::io::AsyncReadExt; -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::JsValue; use wasm_bindgen::{Clamped, JsCast}; -#[cfg(target_arch = "wasm32")] -use web_sys::window; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; -#[cfg(any(feature = "resvg", feature = "vello"))] -pub struct Canvas(()); - -#[derive(Debug, Default)] -pub struct WasmApplicationIo { - #[cfg(target_arch = "wasm32")] - ids: RefCell, - #[cfg(feature = "wgpu")] - pub(crate) gpu_executor: Option, - #[cfg(not(target_arch = "wasm32"))] - windows: RefCell>>, - pub resources: HashMap>, -} - -impl WasmApplicationIo { - pub async fn new() -> Self { - #[cfg(all(feature = "wgpu", target_arch = "wasm32"))] - let executor = if let Some(gpu) = web_sys::window().map(|w| w.navigator().gpu()) { - let request_adapter = || { - let request_adapter = js_sys::Reflect::get(&gpu, &wasm_bindgen::JsValue::from_str("requestAdapter")).ok()?; - let function = request_adapter.dyn_ref::()?; - Some(function.call0(&gpu).ok()) - }; - let result = request_adapter(); - match result { - None => None, - Some(_) => WgpuExecutor::new().await, - } - } else { - None - }; - #[cfg(all(feature = "wgpu", not(target_arch = "wasm32")))] - let executor = WgpuExecutor::new().await; - let mut io = Self { - #[cfg(target_arch = "wasm32")] - ids: RefCell::new(0), - #[cfg(feature = "wgpu")] - gpu_executor: executor, - #[cfg(not(target_arch = "wasm32"))] - windows: RefCell::new(Vec::new()), - resources: HashMap::new(), - }; - io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec())); - io - } -} - -unsafe impl StaticType for WasmApplicationIo { - type Static = WasmApplicationIo; -} - -impl<'a> From> for &'a WasmApplicationIo { - fn from(editor_api: WasmEditorApi<'a>) -> Self { - editor_api.application_io - } -} -#[cfg(feature = "wgpu")] -impl<'a> From<&'a WasmApplicationIo> for &'a WgpuExecutor { - fn from(app_io: &'a WasmApplicationIo) -> Self { - app_io.gpu_executor.as_ref().unwrap() - } -} - -pub type WasmEditorApi<'a> = graphene_core::application_io::EditorApi<'a, WasmApplicationIo>; - -impl ApplicationIo for WasmApplicationIo { - #[cfg(target_arch = "wasm32")] - type Surface = HtmlCanvasElement; - #[cfg(not(target_arch = "wasm32"))] - type Surface = Arc; - #[cfg(feature = "wgpu")] - type Executor = WgpuExecutor; - #[cfg(not(feature = "wgpu"))] - type Executor = (); - - #[cfg(target_arch = "wasm32")] - fn create_surface(&self) -> SurfaceHandle { - let wrapper = || { - let document = window().expect("should have a window in this context").document().expect("window should have a document"); - - let canvas: HtmlCanvasElement = document.create_element("canvas")?.dyn_into::()?; - let mut guard = self.ids.borrow_mut(); - let id = SurfaceId(*guard); - *guard += 1; - // store the canvas in the global scope so it doesn't get garbage collected - let window = window().expect("should have a window in this context"); - let window = Object::from(window); - - let image_canvases_key = JsValue::from_str("imageCanvases"); - - let mut canvases = Reflect::get(&window, &image_canvases_key); - if canvases.is_err() { - Reflect::set(&JsValue::from(web_sys::window().unwrap()), &image_canvases_key, &Object::new()).unwrap(); - canvases = Reflect::get(&window, &image_canvases_key); - } - - // Convert key and value to JsValue - let js_key = JsValue::from_str(format!("canvas{}", id.0).as_str()); - let js_value = JsValue::from(canvas.clone()); - - let canvases = Object::from(canvases.unwrap()); - - // Use Reflect API to set property - Reflect::set(&canvases, &js_key, &js_value)?; - Ok::<_, JsValue>(SurfaceHandle { surface_id: id, surface: canvas }) - }; - - wrapper().expect("should be able to set canvas in global scope") - } - #[cfg(not(target_arch = "wasm32"))] - fn create_surface(&self) -> SurfaceHandle { - #[cfg(feature = "wayland")] - use winit::platform::wayland::EventLoopBuilderExtWayland; - - #[cfg(feature = "wayland")] - let event_loop = winit::event_loop::EventLoopBuilder::new().with_any_thread(true).build().unwrap(); - #[cfg(not(feature = "wayland"))] - let event_loop = winit::event_loop::EventLoop::new().unwrap(); - let window = winit::window::WindowBuilder::new() - .with_title("Graphite") - .with_inner_size(winit::dpi::PhysicalSize::new(800, 600)) - .build(&event_loop) - .unwrap(); - let window = Arc::new(window); - self.windows.borrow_mut().push(window.clone()); - SurfaceHandle { - surface_id: SurfaceId(window.id().into()), - surface: window, - } - } - - #[cfg(target_arch = "wasm32")] - fn destroy_surface(&self, surface_id: SurfaceId) { - let window = window().expect("should have a window in this context"); - let window = Object::from(window); - - let image_canvases_key = JsValue::from_str("imageCanvases"); - - let wrapper = || { - if let Ok(canvases) = Reflect::get(&window, &image_canvases_key) { - // Convert key and value to JsValue - let js_key = JsValue::from_str(format!("canvas{}", surface_id.0).as_str()); - - // Use Reflect API to set property - Reflect::delete_property(&canvases.into(), &js_key)?; - } - Ok::<_, JsValue>(()) - }; - - wrapper().expect("should be able to set canvas in global scope") - } - - #[cfg(not(target_arch = "wasm32"))] - fn destroy_surface(&self, _surface_id: SurfaceId) {} - - #[cfg(feature = "wgpu")] - fn gpu_executor(&self) -> Option<&Self::Executor> { - self.gpu_executor.as_ref() - } - - fn load_resource(&self, url: impl AsRef) -> Result { - let url = url::Url::parse(url.as_ref()).map_err(|_| ApplicationError::InvalidUrl)?; - log::trace!("Loading resource: {url:?}"); - match url.scheme() { - #[cfg(feature = "tokio")] - "file" => { - let path = url.to_file_path().map_err(|_| ApplicationError::NotFound)?; - let path = path.to_str().ok_or(ApplicationError::NotFound)?; - let path = path.to_owned(); - Ok(Box::pin(async move { - let file = tokio::fs::File::open(path).await.map_err(|_| ApplicationError::NotFound)?; - let mut reader = tokio::io::BufReader::new(file); - let mut data = Vec::new(); - reader.read_to_end(&mut data).await.map_err(|_| ApplicationError::NotFound)?; - Ok(Arc::from(data)) - }) as Pin, _>>>>) - } - "http" | "https" => { - let url = url.to_string(); - Ok(Box::pin(async move { - let client = reqwest::Client::new(); - let response = client.get(url).send().await.map_err(|_| ApplicationError::NotFound)?; - let data = response.bytes().await.map_err(|_| ApplicationError::NotFound)?; - Ok(Arc::from(data.to_vec())) - }) as Pin, _>>>>) - } - "graphite" => { - let path = url.path(); - let path = path.to_owned(); - log::trace!("Loading local resource: {path}"); - let data = self.resources.get(&path).ok_or(ApplicationError::NotFound)?.clone(); - Ok(Box::pin(async move { Ok(data.clone()) }) as Pin, _>>>>) - } - _ => Err(ApplicationError::NotFound), - } - } -} +pub use graph_craft::wasm_application_io::*; pub type WasmSurfaceHandle = SurfaceHandle; pub type WasmSurfaceHandleFrame = SurfaceHandleFrame; @@ -234,8 +24,8 @@ pub type WasmSurfaceHandleFrame = SurfaceHandleFrame; pub struct CreateSurfaceNode {} #[node_macro::node_fn(CreateSurfaceNode)] -async fn create_surface_node<'a: 'input>(editor: WasmEditorApi<'a>) -> Arc::Surface>> { - editor.application_io.create_surface().into() +async fn create_surface_node<'a: 'input>(editor: &'a WasmEditorApi) -> Arc::Surface>> { + editor.application_io.as_ref().unwrap().create_surface().into() } pub struct DrawImageFrameNode { @@ -266,8 +56,8 @@ pub struct LoadResourceNode { } #[node_macro::node_fn(LoadResourceNode)] -async fn load_resource_node<'a: 'input>(editor: WasmEditorApi<'a>, url: String) -> Arc<[u8]> { - editor.application_io.load_resource(url).unwrap().await.unwrap() +async fn load_resource_node<'a: 'input>(editor: &'a WasmEditorApi, url: String) -> Arc<[u8]> { + editor.application_io.as_ref().unwrap().load_resource(url).unwrap().await.unwrap() } pub struct DecodeImageNode; @@ -321,7 +111,7 @@ fn _render_canvas( mut render: SvgRender, render_params: RenderParams, footprint: Footprint, - editor: WasmEditorApi<'_>, + editor: &'_ WasmEditorApi, surface_handle: Arc>, ) -> RenderOutput { let resolution = footprint.resolution; @@ -337,7 +127,7 @@ fn _render_canvas( canvas.set_height(resolution.y); let usvg_tree = data.to_usvg_tree(resolution, [min, max]); - if let Some(_exec) = editor.application_io.gpu_executor() { + if let Some(_exec) = editor.application_io.as_ref().unwrap().gpu_executor() { todo!() } else { let pixmap_size = usvg_tree.size.to_int_size(); @@ -421,7 +211,7 @@ async fn rasterize<_T: GraphicElementRendered + TransformMut>(mut data: _T, foot } // Render with the data node taking in Footprint. -impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, WasmEditorApi<'a>> +impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, RenderConfig> for RenderNode where Data: Node<'input, Footprint, Output = F>, @@ -431,14 +221,14 @@ where type Output = core::pin::Pin + 'input>>; #[inline] - fn eval(&'input self, editor: WasmEditorApi<'a>) -> Self::Output { + fn eval(&'input self, render_config: RenderConfig) -> Self::Output { Box::pin(async move { - let footprint = editor.render_config.viewport; + let footprint = render_config.viewport; - let RenderConfig { hide_artboards, for_export, .. } = editor.render_config; - let render_params = RenderParams::new(editor.render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); + let RenderConfig { hide_artboards, for_export, .. } = render_config; + let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); - let output_format = editor.render_config.export_format; + let output_format = render_config.export_format; match output_format { ExportFormat::Svg => render_svg(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint), #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] @@ -450,7 +240,7 @@ where } // Render with the data node taking in (). -impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, WasmEditorApi<'a>> +impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future, Data: 'input, Surface: 'input, SurfaceFuture: 'input> Node<'input, RenderConfig> for RenderNode where Data: Node<'input, (), Output = F>, @@ -459,14 +249,14 @@ where { type Output = core::pin::Pin + 'input>>; #[inline] - fn eval(&'input self, editor: WasmEditorApi<'a>) -> Self::Output { + fn eval(&'input self, render_config: RenderConfig) -> Self::Output { Box::pin(async move { - let footprint = editor.render_config.viewport; + let footprint = render_config.viewport; - let RenderConfig { hide_artboards, for_export, .. } = editor.render_config; - let render_params = RenderParams::new(editor.render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); + let RenderConfig { hide_artboards, for_export, .. } = render_config; + let render_params = RenderParams::new(render_config.view_mode, ImageRenderMode::Base64, None, false, hide_artboards, for_export); - let output_format = editor.render_config.export_format; + let output_format = render_config.export_format; match output_format { ExportFormat::Svg => render_svg(self.data.eval(()).await, SvgRender::new(), render_params, footprint), #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] @@ -478,11 +268,11 @@ where } #[automatically_derived] impl RenderNode { - pub fn new(data: Data, surface_handle: Surface) -> Self { + pub fn new(data: Data, _surface_handle: Surface) -> Self { Self { data, #[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))] - surface_handle, + surface_handle: _surface_handle, #[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))] surface_handle: PhantomData, parameter: PhantomData, diff --git a/node-graph/interpreted-executor/src/dynamic_executor.rs b/node-graph/interpreted-executor/src/dynamic_executor.rs index 9872f3b48..274d76f03 100644 --- a/node-graph/interpreted-executor/src/dynamic_executor.rs +++ b/node-graph/interpreted-executor/src/dynamic_executor.rs @@ -1,7 +1,7 @@ use crate::node_registry; use dyn_any::StaticType; -use graph_craft::document::value::{TaggedValue, UpcastNode}; +use graph_craft::document::value::{TaggedValue, UpcastAsRefNode, UpcastNode}; use graph_craft::document::{NodeId, Source}; use graph_craft::graphene_compiler::Executor; use graph_craft::proto::{ConstructionArgs, GraphError, LocalFuture, NodeContainer, ProtoNetwork, ProtoNode, SharedNodeContainer, TypeErasedBox, TypingContext}; @@ -102,7 +102,7 @@ impl DynamicExecutor { } } -impl<'a, I: StaticType + 'a> Executor for &'a DynamicExecutor { +impl<'a, I: StaticType + 'static> Executor for &'a DynamicExecutor { fn execute(&self, input: I) -> LocalFuture>> { Box::pin(async move { self.tree.eval_tagged_value(self.output, input).await.map_err(|e| e.into()) }) } @@ -176,7 +176,7 @@ impl BorrowTree { } /// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value. /// This ensures that no borrowed data can escape the node graph. - pub async fn eval_tagged_value<'i, I: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Result { + pub async fn eval_tagged_value(&self, id: NodeId, input: I) -> Result { 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) @@ -207,9 +207,15 @@ impl BorrowTree { match &proto_node.construction_args { ConstructionArgs::Value(value) => { - let upcasted = UpcastNode::new(value.to_owned()); - let node = Box::new(upcasted) as TypeErasedBox<'_>; - let node: std::rc::Rc = NodeContainer::new(node); + let node: std::rc::Rc = if let TaggedValue::EditorApi(api) = value { + let editor_api = UpcastAsRefNode::new(api.clone()); + let node = Box::new(editor_api) as TypeErasedBox<'_>; + NodeContainer::new(node) + } else { + let upcasted = UpcastNode::new(value.to_owned()); + let node = Box::new(upcasted) as TypeErasedBox<'_>; + NodeContainer::new(node) + }; self.store_node(node, id); } ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index e0b8e30ac..c5e3206b9 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -16,6 +16,7 @@ use graphene_core::{fn_type, raster::*}; use graphene_core::{Cow, ProtoNodeIdentifier, Type}; use graphene_core::{Node, NodeIO, NodeIOTypes}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; +use graphene_std::application_io::RenderConfig; use graphene_std::wasm_application_io::*; #[cfg(feature = "gpu")] @@ -182,7 +183,6 @@ macro_rules! raster_node { // TODO: turn into hashmap fn node_registry() -> HashMap> { let node_types: Vec> = vec![ - // register_node!(graphene_core::ops::IdentityNode, input: Any<'_>, params: []), vec![( ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }), @@ -196,7 +196,6 @@ fn node_registry() -> HashMap, input: &ImageFrame, params: []), - register_node!(graphene_core::ops::CloneNode<_>, input: &WasmEditorApi, params: []), register_node!(graphene_core::ops::AddNode<_>, input: u32, params: [u32]), register_node!(graphene_core::ops::AddNode<_>, input: &u32, params: [u32]), register_node!(graphene_core::ops::AddNode<_>, input: u32, params: [&u32]), @@ -271,7 +270,7 @@ fn node_registry() -> HashMap, input: f64, params: [&f64]), register_node!(graphene_core::ops::ModuloNode<_>, input: &f64, params: [&f64]), register_node!(graphene_core::ops::ConstructVector2<_, _>, input: (), params: [f64, f64]), - register_node!(graphene_core::ops::SomeNode, input: WasmEditorApi, params: []), + register_node!(graphene_core::ops::SomeNode, input: &WasmEditorApi, params: []), register_node!(graphene_core::logic::LogToConsoleNode, input: bool, params: []), register_node!(graphene_core::logic::LogToConsoleNode, input: f64, params: []), register_node!(graphene_core::logic::LogToConsoleNode, input: f64, params: []), @@ -292,7 +291,7 @@ fn node_registry() -> HashMap, input: GraphicGroup, output: GraphicGroup, params: []), async_node!(graphene_core::ops::IntoNode<_, GraphicGroup>, input: Artboard, output: GraphicGroup, params: []), #[cfg(feature = "gpu")] - async_node!(graphene_core::ops::IntoNode<_, &WgpuExecutor>, input: WasmEditorApi, output: &WgpuExecutor, params: []), + async_node!(graphene_core::ops::IntoNode<_, &WgpuExecutor>, input: &WasmEditorApi, output: &WgpuExecutor, params: []), register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame, params: [ImageFrame]), register_node!(graphene_std::raster::MaskImageNode<_, _, _>, input: ImageFrame, params: [ImageFrame]), register_node!(graphene_std::raster::InsertChannelNode<_, _, _, _>, input: ImageFrame, params: [ImageFrame, RedGreenBlue]), @@ -348,9 +347,9 @@ fn node_registry() -> HashMap, input: Footprint, output: graphene_core::GraphicGroup, fn_params: [Footprint => graphene_core::GraphicGroup]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicElement, fn_params: [Footprint => graphene_core::GraphicElement]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => Artboard]), - async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]), + async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: &WasmEditorApi, output: Arc<[u8]>, params: [String]), register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []), - async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc::Surface>>, params: []), + async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: &WasmEditorApi, output: Arc::Surface>>, params: []), async_node!( graphene_std::wasm_application_io::DrawImageFrameNode<_>, input: ImageFrame, @@ -384,7 +383,7 @@ fn node_registry() -> HashMap, input: Arc>, output: Vec, params: [&WgpuExecutor, ()]), #[cfg(feature = "gpu")] - async_node!(gpu_executor::CreateGpuSurfaceNode, input: WasmEditorApi, output: Arc::Surface<'_>>>, params: []), + async_node!(gpu_executor::CreateGpuSurfaceNode, input: &WasmEditorApi, output: Arc::Surface<'_>>>, params: []), // todo!(gpu) get this to compie without saying that one type is more general than the other // #[cfg(feature = "gpu")] // async_node!(gpu_executor::RenderTextureNode<_, _>, input: ShaderInputFrame, output: SurfaceFrame, params: [Arc::Surface<'_>>>, &WgpuExecutor]), @@ -401,7 +400,7 @@ fn node_registry() -> HashMap = DowncastBothNode::new(args[0].clone()); - let editor_api: DowncastBothNode<(), WasmEditorApi> = DowncastBothNode::new(args[1].clone()); + let editor_api: DowncastBothNode<(), &WasmEditorApi> = DowncastBothNode::new(args[1].clone()); // let document_node = ClonedNode::new(document_node.eval(())); let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api); let any: DynAnyNode, _, _> = graphene_std::any::DynAnyNode::new(node); @@ -569,99 +568,42 @@ fn node_registry() -> HashMap, input: ImageFrame, params: [BlendMode]), raster_node!(graphene_core::raster::PosterizeNode<_>, params: [f64]), raster_node!(graphene_core::raster::ExposureNode<_, _, _>, params: [f64, f64, f64]), - register_node!(graphene_core::memo::LetNode<_>, input: Option>, params: []), - register_node!(graphene_core::memo::LetNode<_>, input: Option, params: []), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: ImageFrame, params: [ImageFrame]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: VectorData, params: [VectorData]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [RenderOutput]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [f32]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [f64]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [bool]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [String]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [Option]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, params: [DVec2]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => f32]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => f64]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => bool]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => String]), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => DVec2]), - async_node!( - graphene_core::memo::EndLetNode<_, _>, - input: WasmEditorApi, - output: GraphicGroup, - params: [GraphicGroup] - ), - async_node!( - graphene_core::memo::EndLetNode<_, _>, - input: WasmEditorApi, - output: Artboard, - params: [Artboard] - ), - async_node!( - graphene_core::memo::EndLetNode<_, _>, - input: WasmEditorApi, - output: WasmSurfaceHandleFrame, - params: [WasmSurfaceHandleFrame] - ), - async_node!(graphene_core::memo::EndLetNode<_, _>, input: WasmEditorApi, output: SurfaceFrame, params: [SurfaceFrame]), - vec![ - ( - ProtoNodeIdentifier::new("graphene_core::memo::RefNode<_, _>"), - |args| { - Box::pin(async move { - let node: DowncastBothNode, WasmEditorApi> = graphene_std::any::DowncastBothNode::new(args[0].clone()); - let node = >::new(node); - let any: DynAnyNode<(), _, _> = graphene_std::any::DynAnyNode::new(node); - - any.into_type_erased() - }) - }, - NodeIOTypes::new(concrete!(()), concrete!(WasmEditorApi), vec![fn_type!(Option, WasmEditorApi)]), - ), - ( - ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>"), - |args: Vec| { - Box::pin(async move { - use graphene_std::raster::ImaginateNode; - macro_rules! instantiate_imaginate_node { + vec![( + ProtoNodeIdentifier::new("graphene_std::raster::ImaginateNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>"), + |args: Vec| { + Box::pin(async move { + use graphene_std::raster::ImaginateNode; + macro_rules! instantiate_imaginate_node { ($($i:expr,)*) => { ImaginateNode::new($(graphene_std::any::input_node(args[$i].clone()),)* ) }; } - let node: ImaginateNode = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,); - let any = graphene_std::any::DynAnyNode::new(node); - any.into_type_erased() - }) - }, - NodeIOTypes::new( - concrete!(ImageFrame), - concrete!(ImageFrame), - vec![ - fn_type!(WasmEditorApi), - fn_type!(ImaginateController), - fn_type!(u64), - fn_type!(Option), - fn_type!(u32), - fn_type!(ImaginateSamplingMethod), - fn_type!(f64), - fn_type!(String), - fn_type!(String), - fn_type!(bool), - fn_type!(f64), - fn_type!(bool), - fn_type!(f64), - fn_type!(ImaginateMaskStartingFill), - fn_type!(bool), - fn_type!(bool), - ], - ), + let node: ImaginateNode = instantiate_imaginate_node!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,); + let any = graphene_std::any::DynAnyNode::new(node); + any.into_type_erased() + }) + }, + NodeIOTypes::new( + concrete!(ImageFrame), + concrete!(ImageFrame), + vec![ + fn_type!(WasmEditorApi), + fn_type!(ImaginateController), + fn_type!(u64), + fn_type!(Option), + fn_type!(u32), + fn_type!(ImaginateSamplingMethod), + fn_type!(f64), + fn_type!(String), + fn_type!(String), + fn_type!(bool), + fn_type!(f64), + fn_type!(bool), + fn_type!(f64), + fn_type!(ImaginateMaskStartingFill), + fn_type!(bool), + fn_type!(bool), + ], ), - ], + )], async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Image, params: [Image]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ImageFrame, params: [ImageFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: QuantizationChannels, params: [QuantizationChannels]), @@ -683,23 +625,23 @@ fn node_registry() -> HashMap, input: Color, params: [QuantizationChannels]), register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]), register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec, () => Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [ImageFrame, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [VectorData, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [GraphicGroup, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Artboard, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [bool, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f32, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f64, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [String, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Option, Arc::Surface>>]), - async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ImageFrame, () => Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Option, () => Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, fn_params: [Footprint => Vec, () => Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [ImageFrame, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [VectorData, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [GraphicGroup, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Artboard, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [bool, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f32, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [f64, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [String, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Option, Arc::Surface>>]), + async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: RenderConfig, output: RenderOutput, params: [Vec, Arc::Surface>>]), async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame, params: [Footprint, Arc]), async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: GraphicGroup, output: ImageFrame, params: [Footprint, Arc]), async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]), @@ -805,9 +747,8 @@ fn node_registry() -> HashMap] ), register_node!(graphene_core::vector::PathModify<_>, input: VectorData, params: [graphene_core::vector::VectorModification]), - register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]), + register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: &WasmEditorApi, params: [String, graphene_core::text::Font, f64]), register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []), - register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []), async_node!(graphene_core::ConstructLayerNode<_, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup, Footprint => graphene_core::GraphicElement]), register_node!(graphene_core::ToGraphicElementNode, input: graphene_core::vector::VectorData, params: []), register_node!(graphene_core::ToGraphicElementNode, input: ImageFrame, params: []), diff --git a/node-graph/wgpu-executor/Cargo.toml b/node-graph/wgpu-executor/Cargo.toml index 793a20eec..d61bfe387 100644 --- a/node-graph/wgpu-executor/Cargo.toml +++ b/node-graph/wgpu-executor/Cargo.toml @@ -15,7 +15,6 @@ gpu-executor = { path = "../gpu-executor" } # Workspace dependencies graphene-core = { workspace = true, features = ["std", "alloc", "gpu"] } -graph-craft = { workspace = true } dyn-any = { workspace = true, features = ["log-bad-types", "rc", "glam"] } num-traits = { workspace = true } log = { workspace = true } diff --git a/node-graph/wgpu-executor/src/executor.rs b/node-graph/wgpu-executor/src/executor.rs index 098f33acc..96f8bf3fc 100644 --- a/node-graph/wgpu-executor/src/executor.rs +++ b/node-graph/wgpu-executor/src/executor.rs @@ -1,11 +1,15 @@ +use super::context::Context; + +use dyn_any::StaticTypeSized; + +use bytemuck::Pod; +use std::borrow::Cow; +use std::error::Error; +use std::pin::Pin; use std::sync::Arc; -use std::{borrow::Cow, error::Error}; use wgpu::util::DeviceExt; -use super::context::Context; -use bytemuck::Pod; -use dyn_any::StaticTypeSized; -use graph_craft::{graphene_compiler::Executor, proto::LocalFuture}; +pub type LocalFuture<'n, T> = Pin + 'n>>; #[derive(Debug)] pub struct GpuExecutor<'a, I: StaticTypeSized, O> { @@ -15,7 +19,7 @@ pub struct GpuExecutor<'a, I: StaticTypeSized, O> { _phantom: std::marker::PhantomData<(I, O)>, } -impl<'a, I: StaticTypeSized, O> GpuExecutor<'a, I, O> { +impl<'a, I: StaticTypeSized + Sync + Pod + Send, O: StaticTypeSized + Send + Sync + Pod> GpuExecutor<'a, I, O> { pub fn new(context: Context, shader: Cow<'a, [u32]>, entry_point: String) -> anyhow::Result { Ok(Self { context, @@ -24,10 +28,8 @@ impl<'a, I: StaticTypeSized, O> GpuExecutor<'a, I, O> { _phantom: std::marker::PhantomData, }) } -} -impl<'a, I: StaticTypeSized + Sync + Pod + Send, O: StaticTypeSized + Send + Sync + Pod> Executor, Vec> for GpuExecutor<'a, I, O> { - fn execute(&self, input: Vec) -> LocalFuture, Box>> { + pub fn execute(&self, input: Vec) -> LocalFuture, Box>> { let context = &self.context; let future = execute_shader(context.device.clone(), context.queue.clone(), self.shader.to_vec(), input, self.entry_point.clone()); Box::pin(async move { diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index e4908b387..9e6b34d71 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -4,19 +4,19 @@ mod executor; pub use context::Context; use dyn_any::{DynAny, StaticType}; pub use executor::GpuExecutor; +pub use gpu_executor::ShaderIO; use gpu_executor::{ComputePassDimensions, Shader, ShaderInput, StorageBufferOptions, TextureBufferOptions, TextureBufferType, ToStorageBuffer, ToUniformBuffer}; -use graph_craft::Type; +use graphene_core::Type; use anyhow::{bail, Result}; use futures::Future; use graphene_core::application_io::{ApplicationIo, EditorApi, SurfaceHandle}; -use std::cell::Cell; use std::pin::Pin; use std::sync::Arc; use wgpu::util::DeviceExt; -use wgpu::{Buffer, BufferDescriptor, CommandBuffer, ShaderModule, SurfaceConfiguration, SurfaceError, Texture, TextureView}; +use wgpu::{Buffer, BufferDescriptor, CommandBuffer, ShaderModule, SurfaceError, Texture, TextureView}; #[cfg(target_arch = "wasm32")] use web_sys::HtmlCanvasElement; @@ -25,7 +25,6 @@ use web_sys::HtmlCanvasElement; pub struct WgpuExecutor { pub context: Context, render_configuration: RenderConfiguration, - surface_config: Cell>, } impl std::fmt::Debug for WgpuExecutor { @@ -37,9 +36,9 @@ impl std::fmt::Debug for WgpuExecutor { } } -impl<'a, T: ApplicationIo> From> for &'a WgpuExecutor { - fn from(editor_api: EditorApi<'a, T>) -> Self { - editor_api.application_io.gpu_executor().unwrap() +impl<'a, T: ApplicationIo> From<&'a EditorApi> for &'a WgpuExecutor { + fn from(editor_api: &'a EditorApi) -> Self { + editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap() } } @@ -296,9 +295,8 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { log::warn!("No surface formats available"); // return Ok(()); } - let Some(config) = self.surface_config.take() else { return Ok(()) }; - let new_config = config.clone(); - self.surface_config.replace(Some(config)); + // let new_config = config.clone(); + // self.surface_config.replace(Some(config)); let output = match result { Err(SurfaceError::Timeout) => { log::warn!("Timeout when getting current texture"); @@ -307,7 +305,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { Err(SurfaceError::Lost) => { log::warn!("Surface lost"); - surface.configure(&self.context.device, &new_config); + // surface.configure(&self.context.device, &new_config); return Ok(()); } Err(SurfaceError::OutOfMemory) => { @@ -316,7 +314,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { } Err(SurfaceError::Outdated) => { log::warn!("Surface outdated"); - surface.configure(&self.context.device, &new_config); + // surface.configure(&self.context.device, &new_config); return Ok(()); } Ok(surface) => surface, @@ -472,7 +470,6 @@ impl gpu_executor::GpuExecutor for WgpuExecutor { desired_maximum_frame_latency: 2, }; surface.configure(&self.context.device, &config); - self.surface_config.set(Some(config)); let surface_id = window.surface_id; Ok(SurfaceHandle { surface_id, surface }) @@ -591,11 +588,7 @@ impl WgpuExecutor { sampler, }; - Some(Self { - context, - render_configuration, - surface_config: Cell::new(None), - }) + Some(Self { context, render_configuration }) } } diff --git a/shell.nix b/shell.nix index 07241e687..59ed310de 100644 --- a/shell.nix +++ b/shell.nix @@ -31,7 +31,7 @@ let rustc-wasm = pkgs.rust-bin.stable.latest.default.override { targets = [ "wasm32-unknown-unknown" ]; # wasm-pack needs this - extensions = [ "rust-src" "rust-analyzer" "clippy" ]; + extensions = [ "rust-src" "rust-analyzer" "clippy"]; }; in # Make a shell with the dependencies we need @@ -50,11 +50,14 @@ in pkgs.webkitgtk pkgs.pkg-config + pkgs.openssl # Use Mold as a Linke pkgs.mold ]; + + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [pkgs.openssl]; # Hacky way to run cago through Mold shellHook = '' alias cargo='mold --run cargo' diff --git a/website/other/bezier-rs-demos/wasm/src/lib.rs b/website/other/bezier-rs-demos/wasm/src/lib.rs index 16a3cb7be..8ce593544 100644 --- a/website/other/bezier-rs-demos/wasm/src/lib.rs +++ b/website/other/bezier-rs-demos/wasm/src/lib.rs @@ -15,7 +15,7 @@ pub fn init() { log::set_logger(&LOGGER).expect("Failed to set logger"); log::set_max_level(log::LevelFilter::Trace); - fn panic_hook(info: &core::panic::PanicInfo) { + fn panic_hook(info: &std::panic::PanicInfo<'_>) { // Skip if we have already panicked if HAS_CRASHED.with(|cell| cell.replace(true)) { return;