Skip to content

Commit 08ec1d0

Browse files
Desktop: Execute editor and node graph natively (#2955)
* Desktop: Execute editor and node graph natively * Remove decouple execution feature * Disable feature gate for native communication functions * Avoid ininite message loop on an infinite canvas * Add any lint exception * Build evaluation loop * Fix texture passing message * Cleanup * More cleanup --------- Co-authored-by: Timon Schelling <[email protected]>
1 parent 0780220 commit 08ec1d0

File tree

21 files changed

+207
-104
lines changed

21 files changed

+207
-104
lines changed

Cargo.lock

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

desktop/Cargo.toml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ edition = "2024"
99
rust-version = "1.87"
1010

1111
[features]
12-
# default = ["gpu"]
13-
# gpu = ["graphite-editor/gpu"]
12+
default = ["gpu"]
13+
gpu = ["graphite-editor/gpu"]
1414

1515
[dependencies]
16-
# Local dependencies
17-
# graphite-editor = { path = "../editor", features = [
18-
# "gpu",
19-
# "ron",
20-
# "vello",
21-
# "decouple-execution",
22-
# ] }
16+
# # Local dependencies
17+
graphite-editor = { path = "../editor", features = [
18+
"gpu",
19+
"ron",
20+
"vello",
21+
] }
22+
graph-craft = { workspace = true }
23+
wgpu-executor = { workspace = true }
24+
2325
wgpu = { workspace = true }
2426
winit = { workspace = true, features = ["serde"] }
2527
thiserror = { workspace = true }
@@ -29,4 +31,6 @@ include_dir = { workspace = true }
2931
tracing-subscriber = { workspace = true }
3032
tracing = { workspace = true }
3133
dirs = { workspace = true }
34+
ron = { workspace = true}
3235
bytemuck = { workspace = true }
36+
glam = { workspace = true }

desktop/src/app.rs

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ use crate::CustomEvent;
22
use crate::WindowSize;
33
use crate::render::GraphicsState;
44
use crate::render::WgpuContext;
5+
use graph_craft::wasm_application_io::WasmApplicationIo;
6+
use graphite_editor::application::Editor;
7+
use graphite_editor::messages::prelude::*;
58
use std::sync::Arc;
69
use std::sync::mpsc::Sender;
710
use std::time::Duration;
@@ -21,11 +24,10 @@ pub(crate) struct WinitApp {
2124
pub(crate) cef_context: cef::Context<cef::Initialized>,
2225
pub(crate) window: Option<Arc<Window>>,
2326
cef_schedule: Option<Instant>,
24-
_ui_frame_buffer: Option<wgpu::Texture>,
2527
window_size_sender: Sender<WindowSize>,
26-
_viewport_frame_buffer: Option<wgpu::Texture>,
2728
graphics_state: Option<GraphicsState>,
2829
wgpu_context: WgpuContext,
30+
pub(crate) editor: Editor,
2931
}
3032

3133
impl WinitApp {
@@ -34,13 +36,28 @@ impl WinitApp {
3436
cef_context,
3537
window: None,
3638
cef_schedule: Some(Instant::now()),
37-
_viewport_frame_buffer: None,
38-
_ui_frame_buffer: None,
3939
graphics_state: None,
4040
window_size_sender,
4141
wgpu_context,
42+
editor: Editor::new(),
4243
}
4344
}
45+
46+
fn dispatch_message(&mut self, message: Message) {
47+
let responses = self.editor.handle_message(message);
48+
self.send_messages_to_editor(responses);
49+
}
50+
51+
fn send_messages_to_editor(&mut self, responses: Vec<FrontendMessage>) {
52+
if responses.is_empty() {
53+
return;
54+
}
55+
let Ok(message) = ron::to_string(&responses) else {
56+
tracing::error!("Failed to serialize Messages");
57+
return;
58+
};
59+
self.cef_context.send_web_message(message.as_bytes());
60+
}
4461
}
4562

4663
impl ApplicationHandler<CustomEvent> for WinitApp {
@@ -49,16 +66,22 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
4966
let timeout = Instant::now() + Duration::from_millis(10);
5067
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
5168
self.cef_context.work();
69+
5270
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
5371
}
5472

55-
fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) {
73+
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
5674
if let Some(schedule) = self.cef_schedule
5775
&& schedule < Instant::now()
5876
{
5977
self.cef_schedule = None;
6078
self.cef_context.work();
6179
}
80+
if let StartCause::ResumeTimeReached { .. } = cause {
81+
if let Some(window) = &self.window {
82+
window.request_redraw();
83+
}
84+
}
6285
}
6386

6487
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
@@ -77,6 +100,10 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
77100
self.graphics_state = Some(graphics_state);
78101

79102
tracing::info!("Winit window created and ready");
103+
104+
let application_io = WasmApplicationIo::new_with_context(self.wgpu_context.clone());
105+
106+
futures::executor::block_on(graphite_editor::node_graph_executor::replace_application_io(application_io));
80107
}
81108

82109
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
@@ -97,6 +124,46 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
97124
self.cef_schedule = Some(instant);
98125
}
99126
}
127+
CustomEvent::MessageReceived { message } => {
128+
if let Message::InputPreprocessor(ipp_message) = &message {
129+
if let Some(window) = &self.window {
130+
window.request_redraw();
131+
}
132+
}
133+
if let Message::InputPreprocessor(InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports }) = &message {
134+
if let Some(graphic_state) = &mut self.graphics_state {
135+
let window_size = self.window.as_ref().unwrap().inner_size();
136+
let window_size = glam::Vec2::new(window_size.width as f32, window_size.height as f32);
137+
let top_left = bounds_of_viewports[0].top_left.as_vec2() / window_size;
138+
let bottom_right = bounds_of_viewports[0].bottom_right.as_vec2() / window_size;
139+
let offset = top_left.to_array();
140+
let scale = (bottom_right - top_left).recip();
141+
graphic_state.set_viewport_offset(offset);
142+
graphic_state.set_viewport_scale(scale.to_array());
143+
} else {
144+
panic!("graphics state not intialized, viewport offset might be lost");
145+
}
146+
}
147+
self.dispatch_message(message);
148+
}
149+
CustomEvent::NodeGraphRan { texture } => {
150+
if let Some(texture) = texture
151+
&& let Some(graphics_state) = &mut self.graphics_state
152+
{
153+
graphics_state.bind_viewport_texture(&texture);
154+
}
155+
let mut responses = VecDeque::new();
156+
let err = self.editor.poll_node_graph_evaluation(&mut responses);
157+
if let Err(e) = err {
158+
if e != "No active document" {
159+
tracing::error!("Error poling node graph: {}", e);
160+
}
161+
}
162+
163+
for message in responses {
164+
self.dispatch_message(message);
165+
}
166+
}
100167
}
101168
}
102169

desktop/src/cef.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,15 @@ impl CefEventHandler for CefHandler {
120120
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
121121
}
122122

123-
fn receive_web_message(&self, message: &[u8]) {}
123+
fn receive_web_message(&self, message: &[u8]) {
124+
let str = std::str::from_utf8(message).unwrap();
125+
match ron::from_str(str) {
126+
Ok(message) => {
127+
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived { message });
128+
}
129+
Err(e) => {
130+
tracing::error!("Failed to deserialize message {:?}", e)
131+
}
132+
}
133+
}
124134
}

desktop/src/cef/internal/render_process_handler.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
use cef::rc::{ConvertReturnValue, Rc, RcImpl};
22
use cef::sys::{_cef_render_process_handler_t, cef_base_ref_counted_t, cef_render_process_handler_t, cef_v8_propertyattribute_t, cef_v8_value_create_array_buffer_with_copy};
3-
use cef::{
4-
CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_context_get_entered_context,
5-
v8_value_create_function,
6-
};
3+
use cef::{CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_value_create_function};
74

85
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
96

@@ -61,7 +58,9 @@ impl ImplRenderProcessHandler for RenderProcessHandlerImpl {
6158
cef_v8_propertyattribute_t::V8_PROPERTY_ATTRIBUTE_READONLY.wrap_result(),
6259
);
6360

64-
frame.execute_java_script(Some(&CefString::from(function_call.as_str())), None, 0);
61+
if global.value_bykey(Some(&CefString::from(function_name))).is_some() {
62+
frame.execute_java_script(Some(&CefString::from(function_call.as_str())), None, 0);
63+
}
6564

6665
if context.exit() == 0 {
6766
tracing::error!("Failed to exit V8 context");

desktop/src/cef/ipc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use cef::{CefString, Frame, ImplBinaryValue, ImplBrowser, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, rc::ConvertParam, sys::cef_process_id_t};
1+
use cef::{CefString, Frame, ImplBinaryValue, ImplBrowser, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
22

33
use super::{Context, Initialized};
44

desktop/src/main.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use std::fmt::Debug;
21
use std::process::exit;
32
use std::time::Instant;
3+
use std::{fmt::Debug, time::Duration};
44

5+
use graphite_editor::messages::prelude::Message;
56
use tracing_subscriber::EnvFilter;
67
use winit::event_loop::EventLoop;
78

@@ -20,6 +21,8 @@ mod dirs;
2021
pub(crate) enum CustomEvent {
2122
UiUpdate(wgpu::Texture),
2223
ScheduleBrowserWork(Instant),
24+
MessageReceived { message: Message },
25+
NodeGraphRan { texture: Option<wgpu::Texture> },
2326
}
2427

2528
fn main() {
@@ -38,7 +41,7 @@ fn main() {
3841

3942
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
4043

41-
let wgpu_context = futures::executor::block_on(WgpuContext::new());
44+
let wgpu_context = futures::executor::block_on(WgpuContext::new()).unwrap();
4245
let cef_context = match cef_context.init(cef::CefHandler::new(window_size_receiver, event_loop.create_proxy(), wgpu_context.clone())) {
4346
Ok(c) => c,
4447
Err(cef::InitError::AlreadyRunning) => {
@@ -51,6 +54,25 @@ fn main() {
5154
}
5255
};
5356

57+
tracing::info!("Cef initialized successfully");
58+
59+
let rendering_loop_proxy = event_loop.create_proxy();
60+
let target_fps = 60;
61+
std::thread::spawn(move || {
62+
loop {
63+
let last_render = Instant::now();
64+
let (has_run, texture) = futures::executor::block_on(graphite_editor::node_graph_executor::run_node_graph());
65+
if has_run {
66+
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan {
67+
texture: texture.map(|t| (*t.texture).clone()),
68+
});
69+
}
70+
let frame_time = Duration::from_secs_f32((target_fps as f32).recip());
71+
let sleep = last_render + frame_time - Instant::now();
72+
std::thread::sleep(sleep);
73+
}
74+
});
75+
5476
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context);
5577

5678
event_loop.run_app(&mut winit_app).unwrap();

desktop/src/render.rs

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -56,46 +56,7 @@ pub(crate) enum FrameBufferError {
5656
InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },
5757
}
5858

59-
#[derive(Debug, Clone)]
60-
pub(crate) struct WgpuContext {
61-
pub(crate) device: wgpu::Device,
62-
pub(crate) queue: wgpu::Queue,
63-
adapter: wgpu::Adapter,
64-
instance: wgpu::Instance,
65-
}
66-
67-
impl WgpuContext {
68-
pub(crate) async fn new() -> Self {
69-
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
70-
backends: wgpu::Backends::PRIMARY,
71-
..Default::default()
72-
});
73-
74-
let adapter = instance
75-
.request_adapter(&wgpu::RequestAdapterOptions {
76-
power_preference: wgpu::PowerPreference::default(),
77-
compatible_surface: None,
78-
force_fallback_adapter: false,
79-
})
80-
.await
81-
.unwrap();
82-
83-
let required_limits = adapter.limits();
84-
85-
let (device, queue) = adapter
86-
.request_device(&wgpu::DeviceDescriptor {
87-
label: None,
88-
required_features: wgpu::Features::PUSH_CONSTANTS,
89-
required_limits,
90-
memory_hints: Default::default(),
91-
trace: wgpu::Trace::Off,
92-
})
93-
.await
94-
.unwrap();
95-
96-
Self { device, queue, adapter, instance }
97-
}
98-
}
59+
pub use wgpu_executor::Context as WgpuContext;
9960

10061
#[derive(Debug)]
10162
pub(crate) struct GraphicsState {

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-
decouple-execution = []
1817
resvg = ["graphene-std/resvg"]
1918
vello = ["graphene-std/vello", "resvg"]
2019
ron = ["dep:ron"]

editor/src/dispatcher.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,14 @@ impl Dispatcher {
206206
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
207207
}
208208
Message::Tool(message) => {
209-
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
210-
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
209+
let Some(document_id) = self.message_handlers.portfolio_message_handler.active_document_id() else {
211210
warn!("Called ToolMessage without an active document.\nGot {message:?}");
212211
return;
213212
};
213+
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
214+
warn!("Called ToolMessage with an invalid active document.\nGot {message:?}");
215+
return;
216+
};
214217

215218
let context = ToolMessageContext {
216219
document_id,

0 commit comments

Comments
 (0)