Skip to content

Commit 383e30e

Browse files
committed
Native node graph runtime
1 parent f01c3f2 commit 383e30e

File tree

11 files changed

+274
-3
lines changed

11 files changed

+274
-3
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/src/dispatcher.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ impl Dispatcher {
150150
Message::Frontend(message) => {
151151
// Handle these messages immediately by returning early
152152
if let FrontendMessage::TriggerFontLoad { .. } = message {
153-
self.responses.push(message);
153+
// Deduplicate the render native node graph messages. TODO: Replace responses with hashset
154+
if !(message == FrontendMessage::RequestNativeNodeGraphRender && self.responses.contains(&FrontendMessage::RequestNativeNodeGraphRender)) {
155+
self.responses.push(message);
156+
}
154157
self.cleanup_queues(false);
155158

156159
// Return early to avoid running the code after the match block

editor/src/messages/frontend/frontend_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ pub enum FrontendMessage {
267267
UpdateMouseCursor {
268268
cursor: MouseCursorIcon,
269269
},
270+
RequestNativeNodeGraphRender,
270271
UpdateNodeGraphSvelteRender {
271272
#[serde(rename = "nodesToRender")]
272273
nodes_to_render: Vec<FrontendNodeToRender>,

editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
16261626
responses.add(NodeGraphMessage::UpdateActionButtons);
16271627

16281628
if self.native_node_graph_render {
1629+
responses.add(FrontendMessage::RequestNativeNodeGraphRender);
16291630
} else {
16301631
let nodes_to_render = network_interface.collect_nodes(&self.node_graph_errors, preferences.graph_wire_style, breadcrumb_network_path);
16311632
self.frontend_nodes = nodes_to_render.iter().map(|node| node.metadata.node_id).collect();

frontend/src/components/views/Graph.svelte

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@
445445
{/if}
446446
{/each}
447447
</div>
448+
{:else}
449+
<div class="native-node-graph-ui">{@html $nodeGraph.nativeNodeGraphSVGString}</div>
448450
{/if}
449451

450452
<div class="graph" bind:this={graph}>
@@ -772,6 +774,14 @@
772774
}
773775
}
774776
777+
.native-node-graph-ui {
778+
position: absolute;
779+
top: 0;
780+
left: 0;
781+
width: 100%;
782+
height: 100%;
783+
}
784+
775785
.layers-and-nodes {
776786
position: absolute;
777787
top: 0;

frontend/wasm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ editor = { path = "../../editor", package = "graphite-editor", features = [
2525
"resvg",
2626
"vello",
2727
] }
28+
interpreted-executor = { workspace = true }
2829
graphene-std = { workspace = true }
30+
once_cell = { workspace = true }
2931

3032
# Workspace dependencies
3133
graph-craft = { workspace = true }

frontend/wasm/src/editor_api.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
// on the dispatcher messaging system and more complex Rust data types.
66
//
77
use crate::helpers::translate_key;
8-
use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER};
8+
#[cfg(not(feature = "native"))]
9+
use crate::wasm_node_graph_ui_executor::WasmNodeGraphUIExecutor;
10+
use crate::{EDITOR_HANDLE, EDITOR_HAS_CRASHED, Error, MESSAGE_BUFFER, WASM_NODE_GRAPH_EXECUTOR};
911
use editor::consts::FILE_EXTENSION;
1012
use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
1113
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
@@ -17,6 +19,7 @@ use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
1719
use graph_craft::document::NodeId;
1820
use graphene_std::raster::Image;
1921
use graphene_std::raster::color::Color;
22+
use interpreted_executor::ui_runtime::CompilationRequest;
2023
use js_sys::{Object, Reflect};
2124
use serde::Serialize;
2225
use serde_wasm_bindgen::{self, from_value};
@@ -149,9 +152,13 @@ impl EditorHandle {
149152
pub fn new(frontend_message_handler_callback: js_sys::Function) -> Self {
150153
let editor = Editor::new();
151154
let editor_handle = EditorHandle { frontend_message_handler_callback };
155+
let node_graph_executor = WasmNodeGraphUIExecutor::new();
152156
if EDITOR.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor))).is_none() {
153157
log::error!("Attempted to initialize the editor more than once");
154158
}
159+
if WASM_NODE_GRAPH_EXECUTOR.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(node_graph_executor))).is_none() {
160+
log::error!("Attempted to initialize the editor more than once");
161+
}
155162
if EDITOR_HANDLE.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor_handle.clone()))).is_none() {
156163
log::error!("Attempted to initialize the editor handle more than once");
157164
}
@@ -208,6 +215,17 @@ impl EditorHandle {
208215

209216
// Sends a FrontendMessage to JavaScript
210217
fn send_frontend_message_to_js(&self, mut message: FrontendMessage) {
218+
// Intercept any requests to render the node graph overlay
219+
if message == FrontendMessage::RequestNativeNodeGraphRender {
220+
executor_editor_and_handle(|executor, editor, _handle| {
221+
if let Some(node_graph_overlay_network) = editor.generate_node_graph_overlay_network() {
222+
let compilation_request = CompilationRequest { network: node_graph_overlay_network };
223+
executor.compilation_request(compilation_request);
224+
}
225+
});
226+
return;
227+
}
228+
211229
if let FrontendMessage::UpdateImageData { ref image_data } = message {
212230
let new_hash = calculate_hash(image_data);
213231
let prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);
@@ -264,6 +282,18 @@ impl EditorHandle {
264282
#[cfg(not(feature = "native"))]
265283
wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation());
266284

285+
// Poll the UI node graph
286+
#[cfg(not(feature = "native"))]
287+
executor_editor_and_handle(|executor, editor, handle| {
288+
for frontend_message in executor
289+
.poll_node_graph_ui_evaluation(editor)
290+
.into_iter()
291+
.flat_map(|runtime_response| editor.handle_message(runtime_response))
292+
{
293+
handle.send_frontend_message_to_js(frontend_message);
294+
}
295+
});
296+
267297
if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
268298
handle(|handle| {
269299
// Process all messages that have been queued up
@@ -989,6 +1019,31 @@ fn editor<T: Default>(callback: impl FnOnce(&mut editor::application::Editor) ->
9891019
})
9901020
}
9911021

1022+
#[cfg(not(feature = "native"))]
1023+
fn executor<T: Default>(callback: impl FnOnce(&mut WasmNodeGraphUIExecutor) -> T) -> T {
1024+
WASM_NODE_GRAPH_EXECUTOR.with(|executor| {
1025+
let mut guard = executor.try_lock();
1026+
let Ok(Some(executor)) = guard.as_deref_mut() else {
1027+
log::error!("Failed to borrow editor");
1028+
return T::default();
1029+
};
1030+
1031+
callback(executor)
1032+
})
1033+
}
1034+
1035+
#[cfg(not(feature = "native"))]
1036+
pub(crate) fn executor_editor_and_handle(callback: impl FnOnce(&mut WasmNodeGraphUIExecutor, &mut Editor, &mut EditorHandle)) {
1037+
executor(|executor| {
1038+
handle(|editor_handle| {
1039+
editor(|editor| {
1040+
// Call the closure with the editor and its handle
1041+
callback(executor, editor, editor_handle);
1042+
})
1043+
});
1044+
})
1045+
}
1046+
9921047
/// Provides access to the `Editor` and its `EditorHandle` by calling the given closure with them as arguments.
9931048
#[cfg(not(feature = "native"))]
9941049
pub(crate) fn editor_and_handle(callback: impl FnOnce(&mut Editor, &mut EditorHandle)) {
@@ -1046,7 +1101,6 @@ async fn poll_node_graph_evaluation() {
10461101
// If the editor cannot be borrowed then it has encountered a panic - we should just ignore new dispatches
10471102
});
10481103
}
1049-
10501104
fn auto_save_all_documents() {
10511105
// Process no further messages after a crash to avoid spamming the console
10521106
if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {

frontend/wasm/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ extern crate log;
77
pub mod editor_api;
88
pub mod helpers;
99
pub mod native_communcation;
10+
pub mod wasm_node_graph_ui_executor;
1011

1112
use editor::messages::prelude::*;
1213
use std::panic;
1314
use std::sync::Mutex;
1415
use std::sync::atomic::{AtomicBool, Ordering};
1516
use wasm_bindgen::prelude::*;
1617

18+
use crate::wasm_node_graph_ui_executor::WasmNodeGraphUIExecutor;
19+
1720
// Set up the persistent editor backend state
1821
pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false);
1922
pub static NODE_GRAPH_ERROR_DISPLAYED: AtomicBool = AtomicBool::new(false);
@@ -22,6 +25,8 @@ pub static LOGGER: WasmLog = WasmLog;
2225
thread_local! {
2326
#[cfg(not(feature = "native"))]
2427
pub static EDITOR: Mutex<Option<editor::application::Editor>> = const { Mutex::new(None) };
28+
#[cfg(not(feature = "native"))]
29+
pub static WASM_NODE_GRAPH_EXECUTOR: Mutex<Option<WasmNodeGraphUIExecutor>> = const { Mutex::new(None) };
2530
pub static MESSAGE_BUFFER: std::cell::RefCell<Vec<Message>> = const { std::cell::RefCell::new(Vec::new()) };
2631
pub static EDITOR_HANDLE: Mutex<Option<editor_api::EditorHandle>> = const { Mutex::new(None) };
2732
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use std::sync::{Mutex, mpsc::Receiver};
2+
3+
use editor::{
4+
application::Editor,
5+
messages::prelude::{FrontendMessage, Message},
6+
};
7+
use graph_craft::graphene_compiler::Compiler;
8+
use graphene_std::node_graph_overlay::{types::NodeGraphTransform, ui_context::UIRuntimeResponse};
9+
use interpreted_executor::{
10+
dynamic_executor::DynamicExecutor,
11+
ui_runtime::{CompilationRequest, EvaluationRequest, NodeGraphUIRuntime},
12+
};
13+
use once_cell::sync::Lazy;
14+
15+
pub static NODE_UI_RUNTIME: Lazy<Mutex<Option<NodeGraphUIRuntime>>> = Lazy::new(|| Mutex::new(None));
16+
17+
// Since the runtime is not locked, it is possible to spawn multiple futures concurrently.
18+
// This is why the runtime_busy flag exists
19+
// This struct should never be locked in a future
20+
pub struct WasmNodeGraphUIExecutor {
21+
response_receiver: Receiver<UIRuntimeResponse>,
22+
runtime_busy: bool,
23+
queued_compilation: Option<CompilationRequest>,
24+
}
25+
26+
impl Default for WasmNodeGraphUIExecutor {
27+
fn default() -> Self {
28+
Self::new()
29+
}
30+
}
31+
32+
impl WasmNodeGraphUIExecutor {
33+
pub fn new() -> Self {
34+
let (response_sender, response_receiver) = std::sync::mpsc::channel();
35+
let runtime = NodeGraphUIRuntime {
36+
executor: DynamicExecutor::default(),
37+
compiler: Compiler {},
38+
response_sender,
39+
};
40+
if let Ok(mut node_runtime) = NODE_UI_RUNTIME.lock() {
41+
node_runtime.replace(runtime);
42+
} else {
43+
log::error!("Could not lock runtime when creating new executor");
44+
};
45+
46+
WasmNodeGraphUIExecutor {
47+
response_receiver,
48+
runtime_busy: false,
49+
queued_compilation: None,
50+
}
51+
}
52+
53+
pub fn compilation_request(&mut self, compilation_request: CompilationRequest) {
54+
if !self.runtime_busy {
55+
self.runtime_busy = true;
56+
wasm_bindgen_futures::spawn_local(async move {
57+
let Ok(mut runtime) = NODE_UI_RUNTIME.try_lock() else {
58+
log::error!("Could not get runtime when evaluating");
59+
return;
60+
};
61+
let Some(runtime) = runtime.as_mut() else {
62+
log::error!("Could not lock runtime when evaluating");
63+
return;
64+
};
65+
runtime.compile(compilation_request).await;
66+
})
67+
} else {
68+
self.queued_compilation = Some(compilation_request);
69+
}
70+
}
71+
72+
// Evaluates the node graph in a spawned future, and returns responses with the response_sender
73+
fn evaluation_request(&mut self, editor: &Editor) {
74+
if let Some(active_document) = editor.dispatcher.message_handlers.portfolio_message_handler.active_document() {
75+
let Some(network_metadata) = active_document.network_interface.network_metadata(&active_document.breadcrumb_network_path) else {
76+
return;
77+
};
78+
79+
let transform = active_document.navigation_handler.calculate_offset_transform(
80+
editor.dispatcher.message_handlers.input_preprocessor_message_handler.viewport_bounds.center(),
81+
&network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz,
82+
);
83+
84+
let transform = NodeGraphTransform {
85+
scale: transform.matrix2.x_axis.x,
86+
x: transform.translation.x,
87+
y: transform.translation.y,
88+
};
89+
let resolution = editor.dispatcher.message_handlers.input_preprocessor_message_handler.viewport_bounds.size().as_uvec2();
90+
let evaluation_request = EvaluationRequest { transform, resolution };
91+
self.runtime_busy = true;
92+
93+
wasm_bindgen_futures::spawn_local(async move {
94+
let Ok(mut runtime) = NODE_UI_RUNTIME.try_lock() else {
95+
log::error!("Could not get runtime when evaluating");
96+
return;
97+
};
98+
let Some(runtime) = runtime.as_mut() else {
99+
log::error!("Could not lock runtime when evaluating");
100+
return;
101+
};
102+
runtime.evaluate(evaluation_request).await
103+
})
104+
}
105+
}
106+
107+
// This is run every time a frame is requested to be rendered.
108+
// It returns back Messages for how to update the frontend/editor click targets
109+
// It also checks for any queued evaluation/compilation requests and runs them
110+
pub fn poll_node_graph_ui_evaluation(&mut self, editor: &Editor) -> Vec<Message> {
111+
let mut responses = Vec::new();
112+
for runtime_response in self.response_receiver.try_iter() {
113+
match runtime_response {
114+
UIRuntimeResponse::RuntimeReady => {
115+
self.runtime_busy = false;
116+
}
117+
UIRuntimeResponse::OverlaySVG(svg_string) => {
118+
responses.push(FrontendMessage::UpdateNativeNodeGraphSVG { svg_string }.into());
119+
}
120+
UIRuntimeResponse::OverlayTexture(_texture) => todo!(),
121+
}
122+
}
123+
124+
if !self.runtime_busy {
125+
if let Some(compilation_request) = self.queued_compilation.take() {
126+
self.compilation_request(compilation_request);
127+
} else {
128+
self.evaluation_request(editor);
129+
}
130+
}
131+
132+
responses
133+
}
134+
}

node-graph/interpreted-executor/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod dynamic_executor;
22
pub mod node_registry;
3+
pub mod ui_runtime;
34
pub mod util;
45

56
#[cfg(test)]

0 commit comments

Comments
 (0)