Skip to content

Commit 2247dd9

Browse files
authored
Desktop: Ready runtime and render node for desktop (#2952)
* Desktop: Ready runtime and render node for desktop * Address review comments
1 parent 6119dea commit 2247dd9

File tree

13 files changed

+179
-104
lines changed

13 files changed

+179
-104
lines changed

editor/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ license = "Apache-2.0"
1414
default = ["wasm"]
1515
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
1616
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
17-
tauri = ["ron", "decouple-execution"]
1817
decouple-execution = []
1918
resvg = ["graphene-std/resvg"]
2019
vello = ["graphene-std/vello", "resvg"]

editor/src/messages/layout/utility_types/widgets/input_widgets.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ impl<'a> serde::Deserialize<'a> for CheckboxId {
7878
where
7979
D: serde::Deserializer<'a>,
8080
{
81-
let id = u64::deserialize(deserializer)?;
81+
let optional_id: Option<u64> = Option::deserialize(deserializer)?;
82+
// TODO: This is potentially weird because after deserialization the two labels will be decoupled if the value not existent
83+
let id = optional_id.unwrap_or(0);
8284
let checkbox_id = CheckboxId(OnceCell::new().into());
8385
checkbox_id.0.set(id).map_err(serde::de::Error::custom)?;
8486
Ok(checkbox_id)

editor/src/node_graph_executor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ impl NodeGraphExecutor {
364364
);
365365
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
366366
}
367+
graphene_std::wasm_application_io::RenderOutputType::Texture { .. } => {}
367368
_ => {
368369
return Err(format!("Invalid node graph output type: {:#?}", render_output.data));
369370
}

editor/src/node_graph_executor/runtime.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ use graph_craft::proto::GraphErrors;
88
use graph_craft::wasm_application_io::EditorPreferences;
99
use graph_craft::{ProtoNodeIdentifier, concrete};
1010
use graphene_std::Context;
11-
use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
11+
use graphene_std::application_io::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
1212
use graphene_std::instances::Instance;
1313
use graphene_std::memo::IORecord;
1414
use graphene_std::renderer::{GraphicElementRendered, RenderParams, SvgRender};
1515
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
1616
use graphene_std::text::FontCache;
1717
use graphene_std::vector::style::ViewMode;
1818
use graphene_std::vector::{VectorData, VectorDataTable};
19-
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
19+
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
2020
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
2121
use interpreted_executor::util::wrap_network_in_scope;
2222
use once_cell::sync::Lazy;
@@ -131,12 +131,12 @@ impl NodeRuntime {
131131
}
132132
}
133133

134-
pub async fn run(&mut self) {
134+
pub async fn run(&mut self) -> Option<ImageTexture> {
135135
if self.editor_api.application_io.is_none() {
136136
self.editor_api = WasmEditorApi {
137-
#[cfg(not(test))]
137+
#[cfg(all(not(test), target_arch = "wasm32"))]
138138
application_io: Some(WasmApplicationIo::new().await.into()),
139-
#[cfg(test)]
139+
#[cfg(any(test, not(target_arch = "wasm32")))]
140140
application_io: Some(WasmApplicationIo::new_offscreen().await.into()),
141141
font_cache: self.editor_api.font_cache.clone(),
142142
node_graph_message_sender: Box::new(self.sender.clone()),
@@ -213,6 +213,16 @@ impl NodeRuntime {
213213
// Resolve the result from the inspection by accessing the monitor node
214214
let inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor));
215215

216+
let texture = if let Ok(TaggedValue::RenderOutput(RenderOutput {
217+
data: RenderOutputType::Texture(texture),
218+
..
219+
})) = &result
220+
{
221+
// We can early return becaus we know that there is at most one execution request and it will always be handled last
222+
Some(texture.clone())
223+
} else {
224+
None
225+
};
216226
self.sender.send_execution_response(ExecutionResponse {
217227
execution_id,
218228
result,
@@ -221,9 +231,11 @@ impl NodeRuntime {
221231
vector_modify: self.vector_modify.clone(),
222232
inspect_result,
223233
});
234+
return texture;
224235
}
225236
}
226237
}
238+
None
227239
}
228240

229241
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
@@ -382,18 +394,30 @@ pub async fn introspect_node(path: &[NodeId]) -> Result<Arc<dyn std::any::Any +
382394
Err(IntrospectError::RuntimeNotReady)
383395
}
384396

385-
pub async fn run_node_graph() -> bool {
386-
let Some(mut runtime) = NODE_RUNTIME.try_lock() else { return false };
397+
pub async fn run_node_graph() -> (bool, Option<ImageTexture>) {
398+
let Some(mut runtime) = NODE_RUNTIME.try_lock() else { return (false, None) };
387399
if let Some(ref mut runtime) = runtime.as_mut() {
388-
runtime.run().await;
400+
return (true, runtime.run().await);
389401
}
390-
true
402+
(false, None)
391403
}
392404

393405
pub async fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {
394406
let mut node_runtime = NODE_RUNTIME.lock();
395407
node_runtime.replace(runtime)
396408
}
409+
pub async fn replace_application_io(application_io: WasmApplicationIo) {
410+
let mut node_runtime = NODE_RUNTIME.lock();
411+
if let Some(node_runtime) = &mut *node_runtime {
412+
node_runtime.editor_api = WasmEditorApi {
413+
font_cache: node_runtime.editor_api.font_cache.clone(),
414+
application_io: Some(application_io.into()),
415+
node_graph_message_sender: Box::new(node_runtime.sender.clone()),
416+
editor_preferences: Box::new(node_runtime.editor_preferences.clone()),
417+
}
418+
.into();
419+
}
420+
}
397421

398422
/// Which node is inspected and which monitor node is used (if any) for the current execution
399423
#[derive(Debug, Clone, Copy)]
Lines changed: 7 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,11 @@
11
use super::*;
22
use std::sync::mpsc::{Receiver, Sender};
3-
use wasm_bindgen::prelude::*;
4-
5-
#[wasm_bindgen]
6-
extern "C" {
7-
// Invoke with arguments (default)
8-
#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])]
9-
async fn invoke(cmd: &str, args: JsValue) -> JsValue;
10-
#[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name="invoke")]
11-
async fn invoke_without_arg(cmd: &str) -> JsValue;
12-
}
133

144
/// Handles communication with the NodeRuntime, either locally or via Tauri
155
#[derive(Debug)]
166
pub struct NodeRuntimeIO {
177
// Send to
18-
#[cfg(any(not(feature = "tauri"), test))]
198
sender: Sender<GraphRuntimeRequest>,
20-
#[cfg(all(feature = "tauri", not(test)))]
21-
sender: Sender<NodeGraphUpdate>,
229
receiver: Receiver<NodeGraphUpdate>,
2310
}
2411

@@ -31,25 +18,13 @@ impl Default for NodeRuntimeIO {
3118
impl NodeRuntimeIO {
3219
/// Creates a new NodeRuntimeIO instance
3320
pub fn new() -> Self {
34-
#[cfg(any(not(feature = "tauri"), test))]
35-
{
36-
let (response_sender, response_receiver) = std::sync::mpsc::channel();
37-
let (request_sender, request_receiver) = std::sync::mpsc::channel();
38-
futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)));
21+
let (response_sender, response_receiver) = std::sync::mpsc::channel();
22+
let (request_sender, request_receiver) = std::sync::mpsc::channel();
23+
futures::executor::block_on(replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)));
3924

40-
Self {
41-
sender: request_sender,
42-
receiver: response_receiver,
43-
}
44-
}
45-
46-
#[cfg(all(feature = "tauri", not(test)))]
47-
{
48-
let (response_sender, response_receiver) = std::sync::mpsc::channel();
49-
Self {
50-
sender: response_sender,
51-
receiver: response_receiver,
52-
}
25+
Self {
26+
sender: request_sender,
27+
receiver: response_receiver,
5328
}
5429
}
5530
#[cfg(test)]
@@ -59,44 +34,11 @@ impl NodeRuntimeIO {
5934

6035
/// Sends a message to the NodeRuntime
6136
pub fn send(&self, message: GraphRuntimeRequest) -> Result<(), String> {
62-
#[cfg(any(not(feature = "tauri"), test))]
63-
{
64-
self.sender.send(message).map_err(|e| e.to_string())
65-
}
66-
67-
#[cfg(all(feature = "tauri", not(test)))]
68-
{
69-
let serialized = ron::to_string(&message).map_err(|e| e.to_string()).unwrap();
70-
wasm_bindgen_futures::spawn_local(async move {
71-
let js_message = create_message_object(&serialized);
72-
invoke("runtime_message", js_message).await;
73-
});
74-
Ok(())
75-
}
37+
self.sender.send(message).map_err(|e| e.to_string())
7638
}
7739

7840
/// Receives any pending updates from the NodeRuntime
7941
pub fn receive(&self) -> impl Iterator<Item = NodeGraphUpdate> + use<'_> {
80-
// TODO: This introduces extra latency
81-
#[cfg(all(feature = "tauri", not(test)))]
82-
{
83-
let sender = self.sender.clone();
84-
// In the Tauri case, responses are handled separately via poll_node_runtime_updates
85-
wasm_bindgen_futures::spawn_local(async move {
86-
let messages = invoke_without_arg("poll_node_graph").await;
87-
let vec: Vec<_> = ron::from_str(&messages.as_string().unwrap()).unwrap();
88-
for message in vec {
89-
sender.send(message).unwrap();
90-
}
91-
});
92-
}
9342
self.receiver.try_iter()
9443
}
9544
}
96-
97-
#[cfg(all(feature = "tauri", not(test)))]
98-
pub fn create_message_object(message: &str) -> JsValue {
99-
let obj = js_sys::Object::new();
100-
js_sys::Reflect::set(&obj, &JsValue::from_str("message"), &JsValue::from_str(message)).unwrap();
101-
obj.into()
102-
}

frontend/wasm/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ license = "Apache-2.0"
1313
[features]
1414
default = ["gpu"]
1515
gpu = ["editor/gpu"]
16-
tauri = ["editor/tauri"]
1716

1817
[lib]
1918
crate-type = ["cdylib", "rlib"]

frontend/wasm/src/editor_api.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ async fn poll_node_graph_evaluation() {
931931
return;
932932
}
933933

934-
if !editor::node_graph_executor::run_node_graph().await {
934+
if !editor::node_graph_executor::run_node_graph().await.0 {
935935
return;
936936
};
937937

node-graph/gapplication-io/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ pub struct ImageTexture {
5757
pub texture: (),
5858
}
5959

60+
impl<'a> serde::Deserialize<'a> for ImageTexture {
61+
fn deserialize<D>(_: D) -> Result<Self, D::Error>
62+
where
63+
D: serde::Deserializer<'a>,
64+
{
65+
unimplemented!("attempted to serialize a texture")
66+
}
67+
}
68+
6069
impl Hash for ImageTexture {
6170
#[cfg(feature = "wgpu")]
6271
fn hash<H: Hasher>(&self, state: &mut H) {

node-graph/graph-craft/src/document/value.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::wasm_application_io::WasmEditorApi;
44
use dyn_any::DynAny;
55
pub use dyn_any::StaticType;
66
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
7-
use graphene_application_io::SurfaceFrame;
7+
use graphene_application_io::{ImageTexture, SurfaceFrame};
88
use graphene_brush::brush_cache::BrushCache;
99
use graphene_brush::brush_stroke::BrushStroke;
1010
use graphene_core::raster::Image;
@@ -429,7 +429,12 @@ pub struct RenderOutput {
429429
#[derive(Debug, Clone, Hash, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
430430
pub enum RenderOutputType {
431431
CanvasFrame(SurfaceFrame),
432-
Svg { svg: String, image_data: Vec<(u64, Image<Color>)> },
432+
#[serde(skip)]
433+
Texture(ImageTexture),
434+
Svg {
435+
svg: String,
436+
image_data: Vec<(u64, Image<Color>)>,
437+
},
433438
Image(Vec<u8>),
434439
}
435440

node-graph/graph-craft/src/wasm_application_io.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ impl WasmApplicationIo {
137137
let wgpu_available = executor.is_some();
138138
WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);
139139

140-
// Always enable wgpu when running with Tauri
141140
let mut io = Self {
142141
#[cfg(target_arch = "wasm32")]
143142
ids: AtomicU64::new(0),
@@ -149,6 +148,27 @@ impl WasmApplicationIo {
149148

150149
io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec()));
151150

151+
io
152+
}
153+
#[cfg(all(not(target_arch = "wasm32"), feature = "wgpu"))]
154+
pub fn new_with_context(context: wgpu_executor::Context) -> Self {
155+
#[cfg(feature = "wgpu")]
156+
let executor = WgpuExecutor::with_context(context);
157+
158+
#[cfg(not(feature = "wgpu"))]
159+
let wgpu_available = false;
160+
#[cfg(feature = "wgpu")]
161+
let wgpu_available = executor.is_some();
162+
WGPU_AVAILABLE.store(wgpu_available as i8, Ordering::SeqCst);
163+
164+
let mut io = Self {
165+
gpu_executor: executor,
166+
windows: Vec::new(),
167+
resources: HashMap::new(),
168+
};
169+
170+
io.resources.insert("null".to_string(), Arc::from(include_bytes!("null.png").to_vec()));
171+
152172
io
153173
}
154174
}

0 commit comments

Comments
 (0)