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 <me@timon.zip>
This commit is contained in:
parent
07802204f2
commit
08ec1d08f6
|
|
@ -1838,11 +1838,16 @@ dependencies = [
|
||||||
"cef",
|
"cef",
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures",
|
"futures",
|
||||||
|
"glam",
|
||||||
|
"graph-craft",
|
||||||
|
"graphite-editor",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
|
"ron",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
|
"wgpu-executor",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1904,6 +1909,7 @@ dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"math-parser",
|
"math-parser",
|
||||||
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-wasm-bindgen",
|
"serde-wasm-bindgen",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,19 @@ edition = "2024"
|
||||||
rust-version = "1.87"
|
rust-version = "1.87"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# default = ["gpu"]
|
default = ["gpu"]
|
||||||
# gpu = ["graphite-editor/gpu"]
|
gpu = ["graphite-editor/gpu"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# # Local dependencies
|
||||||
# graphite-editor = { path = "../editor", features = [
|
graphite-editor = { path = "../editor", features = [
|
||||||
# "gpu",
|
"gpu",
|
||||||
# "ron",
|
"ron",
|
||||||
# "vello",
|
"vello",
|
||||||
# "decouple-execution",
|
] }
|
||||||
# ] }
|
graph-craft = { workspace = true }
|
||||||
|
wgpu-executor = { workspace = true }
|
||||||
|
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
winit = { workspace = true, features = ["serde"] }
|
winit = { workspace = true, features = ["serde"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
@ -29,4 +31,6 @@ include_dir = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
|
ron = { workspace = true}
|
||||||
bytemuck = { workspace = true }
|
bytemuck = { workspace = true }
|
||||||
|
glam = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ use crate::CustomEvent;
|
||||||
use crate::WindowSize;
|
use crate::WindowSize;
|
||||||
use crate::render::GraphicsState;
|
use crate::render::GraphicsState;
|
||||||
use crate::render::WgpuContext;
|
use crate::render::WgpuContext;
|
||||||
|
use graph_craft::wasm_application_io::WasmApplicationIo;
|
||||||
|
use graphite_editor::application::Editor;
|
||||||
|
use graphite_editor::messages::prelude::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
@ -21,11 +24,10 @@ pub(crate) struct WinitApp {
|
||||||
pub(crate) cef_context: cef::Context<cef::Initialized>,
|
pub(crate) cef_context: cef::Context<cef::Initialized>,
|
||||||
pub(crate) window: Option<Arc<Window>>,
|
pub(crate) window: Option<Arc<Window>>,
|
||||||
cef_schedule: Option<Instant>,
|
cef_schedule: Option<Instant>,
|
||||||
_ui_frame_buffer: Option<wgpu::Texture>,
|
|
||||||
window_size_sender: Sender<WindowSize>,
|
window_size_sender: Sender<WindowSize>,
|
||||||
_viewport_frame_buffer: Option<wgpu::Texture>,
|
|
||||||
graphics_state: Option<GraphicsState>,
|
graphics_state: Option<GraphicsState>,
|
||||||
wgpu_context: WgpuContext,
|
wgpu_context: WgpuContext,
|
||||||
|
pub(crate) editor: Editor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WinitApp {
|
impl WinitApp {
|
||||||
|
|
@ -34,13 +36,28 @@ impl WinitApp {
|
||||||
cef_context,
|
cef_context,
|
||||||
window: None,
|
window: None,
|
||||||
cef_schedule: Some(Instant::now()),
|
cef_schedule: Some(Instant::now()),
|
||||||
_viewport_frame_buffer: None,
|
|
||||||
_ui_frame_buffer: None,
|
|
||||||
graphics_state: None,
|
graphics_state: None,
|
||||||
window_size_sender,
|
window_size_sender,
|
||||||
wgpu_context,
|
wgpu_context,
|
||||||
|
editor: Editor::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dispatch_message(&mut self, message: Message) {
|
||||||
|
let responses = self.editor.handle_message(message);
|
||||||
|
self.send_messages_to_editor(responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_messages_to_editor(&mut self, responses: Vec<FrontendMessage>) {
|
||||||
|
if responses.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(message) = ron::to_string(&responses) else {
|
||||||
|
tracing::error!("Failed to serialize Messages");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.cef_context.send_web_message(message.as_bytes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationHandler<CustomEvent> for WinitApp {
|
impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
|
|
@ -49,16 +66,22 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
let timeout = Instant::now() + Duration::from_millis(10);
|
let timeout = Instant::now() + Duration::from_millis(10);
|
||||||
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
|
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
|
||||||
self.cef_context.work();
|
self.cef_context.work();
|
||||||
|
|
||||||
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
|
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_events(&mut self, _event_loop: &ActiveEventLoop, _cause: StartCause) {
|
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
|
||||||
if let Some(schedule) = self.cef_schedule
|
if let Some(schedule) = self.cef_schedule
|
||||||
&& schedule < Instant::now()
|
&& schedule < Instant::now()
|
||||||
{
|
{
|
||||||
self.cef_schedule = None;
|
self.cef_schedule = None;
|
||||||
self.cef_context.work();
|
self.cef_context.work();
|
||||||
}
|
}
|
||||||
|
if let StartCause::ResumeTimeReached { .. } = cause {
|
||||||
|
if let Some(window) = &self.window {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
|
@ -77,6 +100,10 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
self.graphics_state = Some(graphics_state);
|
self.graphics_state = Some(graphics_state);
|
||||||
|
|
||||||
tracing::info!("Winit window created and ready");
|
tracing::info!("Winit window created and ready");
|
||||||
|
|
||||||
|
let application_io = WasmApplicationIo::new_with_context(self.wgpu_context.clone());
|
||||||
|
|
||||||
|
futures::executor::block_on(graphite_editor::node_graph_executor::replace_application_io(application_io));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
|
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
|
||||||
|
|
@ -97,6 +124,46 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
self.cef_schedule = Some(instant);
|
self.cef_schedule = Some(instant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CustomEvent::MessageReceived { message } => {
|
||||||
|
if let Message::InputPreprocessor(ipp_message) = &message {
|
||||||
|
if let Some(window) = &self.window {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Message::InputPreprocessor(InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports }) = &message {
|
||||||
|
if let Some(graphic_state) = &mut self.graphics_state {
|
||||||
|
let window_size = self.window.as_ref().unwrap().inner_size();
|
||||||
|
let window_size = glam::Vec2::new(window_size.width as f32, window_size.height as f32);
|
||||||
|
let top_left = bounds_of_viewports[0].top_left.as_vec2() / window_size;
|
||||||
|
let bottom_right = bounds_of_viewports[0].bottom_right.as_vec2() / window_size;
|
||||||
|
let offset = top_left.to_array();
|
||||||
|
let scale = (bottom_right - top_left).recip();
|
||||||
|
graphic_state.set_viewport_offset(offset);
|
||||||
|
graphic_state.set_viewport_scale(scale.to_array());
|
||||||
|
} else {
|
||||||
|
panic!("graphics state not intialized, viewport offset might be lost");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.dispatch_message(message);
|
||||||
|
}
|
||||||
|
CustomEvent::NodeGraphRan { texture } => {
|
||||||
|
if let Some(texture) = texture
|
||||||
|
&& let Some(graphics_state) = &mut self.graphics_state
|
||||||
|
{
|
||||||
|
graphics_state.bind_viewport_texture(&texture);
|
||||||
|
}
|
||||||
|
let mut responses = VecDeque::new();
|
||||||
|
let err = self.editor.poll_node_graph_evaluation(&mut responses);
|
||||||
|
if let Err(e) = err {
|
||||||
|
if e != "No active document" {
|
||||||
|
tracing::error!("Error poling node graph: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for message in responses {
|
||||||
|
self.dispatch_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,5 +120,15 @@ impl CefEventHandler for CefHandler {
|
||||||
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
|
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn receive_web_message(&self, message: &[u8]) {}
|
fn receive_web_message(&self, message: &[u8]) {
|
||||||
|
let str = std::str::from_utf8(message).unwrap();
|
||||||
|
match ron::from_str(str) {
|
||||||
|
Ok(message) => {
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived { message });
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to deserialize message {:?}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
use cef::rc::{ConvertReturnValue, Rc, RcImpl};
|
use cef::rc::{ConvertReturnValue, Rc, RcImpl};
|
||||||
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};
|
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};
|
||||||
use cef::{
|
use cef::{CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_value_create_function};
|
||||||
CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_context_get_entered_context,
|
|
||||||
v8_value_create_function,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
|
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
|
||||||
|
|
||||||
|
|
@ -61,7 +58,9 @@ impl ImplRenderProcessHandler for RenderProcessHandlerImpl {
|
||||||
cef_v8_propertyattribute_t::V8_PROPERTY_ATTRIBUTE_READONLY.wrap_result(),
|
cef_v8_propertyattribute_t::V8_PROPERTY_ATTRIBUTE_READONLY.wrap_result(),
|
||||||
);
|
);
|
||||||
|
|
||||||
frame.execute_java_script(Some(&CefString::from(function_call.as_str())), None, 0);
|
if global.value_bykey(Some(&CefString::from(function_name))).is_some() {
|
||||||
|
frame.execute_java_script(Some(&CefString::from(function_call.as_str())), None, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if context.exit() == 0 {
|
if context.exit() == 0 {
|
||||||
tracing::error!("Failed to exit V8 context");
|
tracing::error!("Failed to exit V8 context");
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use cef::{CefString, Frame, ImplBinaryValue, ImplBrowser, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, rc::ConvertParam, sys::cef_process_id_t};
|
use cef::{CefString, Frame, ImplBinaryValue, ImplBrowser, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
|
||||||
|
|
||||||
use super::{Context, Initialized};
|
use super::{Context, Initialized};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use std::{fmt::Debug, time::Duration};
|
||||||
|
|
||||||
|
use graphite_editor::messages::prelude::Message;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
|
|
@ -20,6 +21,8 @@ mod dirs;
|
||||||
pub(crate) enum CustomEvent {
|
pub(crate) enum CustomEvent {
|
||||||
UiUpdate(wgpu::Texture),
|
UiUpdate(wgpu::Texture),
|
||||||
ScheduleBrowserWork(Instant),
|
ScheduleBrowserWork(Instant),
|
||||||
|
MessageReceived { message: Message },
|
||||||
|
NodeGraphRan { texture: Option<wgpu::Texture> },
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -38,7 +41,7 @@ fn main() {
|
||||||
|
|
||||||
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
|
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
let wgpu_context = futures::executor::block_on(WgpuContext::new());
|
let wgpu_context = futures::executor::block_on(WgpuContext::new()).unwrap();
|
||||||
let cef_context = match cef_context.init(cef::CefHandler::new(window_size_receiver, event_loop.create_proxy(), wgpu_context.clone())) {
|
let cef_context = match cef_context.init(cef::CefHandler::new(window_size_receiver, event_loop.create_proxy(), wgpu_context.clone())) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(cef::InitError::AlreadyRunning) => {
|
Err(cef::InitError::AlreadyRunning) => {
|
||||||
|
|
@ -51,6 +54,25 @@ fn main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tracing::info!("Cef initialized successfully");
|
||||||
|
|
||||||
|
let rendering_loop_proxy = event_loop.create_proxy();
|
||||||
|
let target_fps = 60;
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
let last_render = Instant::now();
|
||||||
|
let (has_run, texture) = futures::executor::block_on(graphite_editor::node_graph_executor::run_node_graph());
|
||||||
|
if has_run {
|
||||||
|
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan {
|
||||||
|
texture: texture.map(|t| (*t.texture).clone()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let frame_time = Duration::from_secs_f32((target_fps as f32).recip());
|
||||||
|
let sleep = last_render + frame_time - Instant::now();
|
||||||
|
std::thread::sleep(sleep);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context);
|
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context);
|
||||||
|
|
||||||
event_loop.run_app(&mut winit_app).unwrap();
|
event_loop.run_app(&mut winit_app).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -56,46 +56,7 @@ pub(crate) enum FrameBufferError {
|
||||||
InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },
|
InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub use wgpu_executor::Context as WgpuContext;
|
||||||
pub(crate) struct WgpuContext {
|
|
||||||
pub(crate) device: wgpu::Device,
|
|
||||||
pub(crate) queue: wgpu::Queue,
|
|
||||||
adapter: wgpu::Adapter,
|
|
||||||
instance: wgpu::Instance,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WgpuContext {
|
|
||||||
pub(crate) async fn new() -> Self {
|
|
||||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
|
|
||||||
backends: wgpu::Backends::PRIMARY,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let adapter = instance
|
|
||||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
|
||||||
power_preference: wgpu::PowerPreference::default(),
|
|
||||||
compatible_surface: None,
|
|
||||||
force_fallback_adapter: false,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let required_limits = adapter.limits();
|
|
||||||
|
|
||||||
let (device, queue) = adapter
|
|
||||||
.request_device(&wgpu::DeviceDescriptor {
|
|
||||||
label: None,
|
|
||||||
required_features: wgpu::Features::PUSH_CONSTANTS,
|
|
||||||
required_limits,
|
|
||||||
memory_hints: Default::default(),
|
|
||||||
trace: wgpu::Trace::Off,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Self { device, queue, adapter, instance }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct GraphicsState {
|
pub(crate) struct GraphicsState {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ license = "Apache-2.0"
|
||||||
default = ["wasm"]
|
default = ["wasm"]
|
||||||
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
|
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
|
||||||
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
||||||
decouple-execution = []
|
|
||||||
resvg = ["graphene-std/resvg"]
|
resvg = ["graphene-std/resvg"]
|
||||||
vello = ["graphene-std/vello", "resvg"]
|
vello = ["graphene-std/vello", "resvg"]
|
||||||
ron = ["dep:ron"]
|
ron = ["dep:ron"]
|
||||||
|
|
|
||||||
|
|
@ -206,11 +206,14 @@ impl Dispatcher {
|
||||||
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
Message::Tool(message) => {
|
Message::Tool(message) => {
|
||||||
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
|
let Some(document_id) = self.message_handlers.portfolio_message_handler.active_document_id() else {
|
||||||
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
|
|
||||||
warn!("Called ToolMessage without an active document.\nGot {message:?}");
|
warn!("Called ToolMessage without an active document.\nGot {message:?}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
|
||||||
|
warn!("Called ToolMessage with an invalid active document.\nGot {message:?}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let context = ToolMessageContext {
|
let context = ToolMessageContext {
|
||||||
document_id,
|
document_id,
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,10 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
|
||||||
.into(),
|
.into(),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
responses.add(DeferMessage::AfterNavigationReady {
|
||||||
|
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into(), DocumentMessage::DeselectAllLayers.into()],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(DeferMessage::AfterNavigationReady {
|
|
||||||
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into(), DocumentMessage::DeselectAllLayers.into()],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -702,12 +702,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||||
|
|
||||||
if create_document {
|
if create_document {
|
||||||
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
|
// Wait for the document to be rendered so the click targets can be calculated in order to determine the artboard size that will encompass the pasted image
|
||||||
responses.add(DeferMessage::AfterNavigationReady {
|
|
||||||
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into()],
|
|
||||||
});
|
|
||||||
responses.add(DeferMessage::AfterGraphRun {
|
responses.add(DeferMessage::AfterGraphRun {
|
||||||
messages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()],
|
messages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()],
|
||||||
});
|
});
|
||||||
|
responses.add(DeferMessage::AfterNavigationReady {
|
||||||
|
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into()],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PortfolioMessage::PasteSvg {
|
PortfolioMessage::PasteSvg {
|
||||||
|
|
@ -883,6 +883,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||||
responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents });
|
responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents });
|
||||||
}
|
}
|
||||||
PortfolioMessage::UpdateVelloPreference => {
|
PortfolioMessage::UpdateVelloPreference => {
|
||||||
|
responses.add(FrontendMessage::UpdateViewportHolePunch { active: preferences.use_vello });
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
self.persistent_data.use_vello = preferences.use_vello;
|
self.persistent_data.use_vello = preferences.use_vello;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ pub struct ExecutionRequest {
|
||||||
render_config: RenderConfig,
|
render_config: RenderConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct ExecutionResponse {
|
pub struct ExecutionResponse {
|
||||||
execution_id: u64,
|
execution_id: u64,
|
||||||
result: Result<TaggedValue, String>,
|
result: Result<TaggedValue, String>,
|
||||||
|
|
@ -46,7 +45,6 @@ pub struct CompilationResponse {
|
||||||
node_graph_errors: GraphErrors,
|
node_graph_errors: GraphErrors,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub enum NodeGraphUpdate {
|
pub enum NodeGraphUpdate {
|
||||||
ExecutionResponse(ExecutionResponse),
|
ExecutionResponse(ExecutionResponse),
|
||||||
CompilationResponse(CompilationResponse),
|
CompilationResponse(CompilationResponse),
|
||||||
|
|
|
||||||
|
|
@ -427,22 +427,14 @@ struct InspectState {
|
||||||
}
|
}
|
||||||
/// The resulting value from the temporary inspected during execution
|
/// The resulting value from the temporary inspected during execution
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct InspectResult {
|
pub struct InspectResult {
|
||||||
#[cfg(not(feature = "decouple-execution"))]
|
|
||||||
introspected_data: Option<Arc<dyn std::any::Any + Send + Sync + 'static>>,
|
introspected_data: Option<Arc<dyn std::any::Any + Send + Sync + 'static>>,
|
||||||
#[cfg(feature = "decouple-execution")]
|
|
||||||
introspected_data: Option<TaggedValue>,
|
|
||||||
pub inspect_node: NodeId,
|
pub inspect_node: NodeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InspectResult {
|
impl InspectResult {
|
||||||
pub fn take_data(&mut self) -> Option<Arc<dyn std::any::Any + Send + Sync + 'static>> {
|
pub fn take_data(&mut self) -> Option<Arc<dyn std::any::Any + Send + Sync + 'static>> {
|
||||||
#[cfg(not(feature = "decouple-execution"))]
|
|
||||||
return self.introspected_data.clone();
|
return self.introspected_data.clone();
|
||||||
|
|
||||||
#[cfg(feature = "decouple-execution")]
|
|
||||||
return self.introspected_data.take().map(|value| value.to_any());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -487,8 +479,6 @@ impl InspectState {
|
||||||
fn access(&self, executor: &DynamicExecutor) -> Option<InspectResult> {
|
fn access(&self, executor: &DynamicExecutor) -> Option<InspectResult> {
|
||||||
let introspected_data = executor.introspect(&[self.monitor_node]).inspect_err(|e| warn!("Failed to introspect monitor node {e}")).ok();
|
let introspected_data = executor.introspect(&[self.monitor_node]).inspect_err(|e| warn!("Failed to introspect monitor node {e}")).ok();
|
||||||
// TODO: Consider displaying the error instead of ignoring it
|
// TODO: Consider displaying the error instead of ignoring it
|
||||||
#[cfg(feature = "decouple-execution")]
|
|
||||||
let introspected_data = introspected_data.as_ref().and_then(|data| TaggedValue::try_from_std_any_ref(data).ok());
|
|
||||||
|
|
||||||
Some(InspectResult {
|
Some(InspectResult {
|
||||||
inspect_node: self.inspect_node,
|
inspect_node: self.inspect_node,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"production": "npm run setup && npm run wasm:build-production && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-production\"",
|
"production": "npm run setup && npm run wasm:build-production && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-production\"",
|
||||||
"---------- BUILDS ----------": "",
|
"---------- BUILDS ----------": "",
|
||||||
"build-dev": "npm run wasm:build-dev && vite build",
|
"build-dev": "npm run wasm:build-dev && vite build",
|
||||||
|
"build-native": "npm run native:build-dev && vite build",
|
||||||
"build-profiling": "npm run wasm:build-profiling && vite build",
|
"build-profiling": "npm run wasm:build-profiling && vite build",
|
||||||
"build": "npm run wasm:build-production && vite build",
|
"build": "npm run wasm:build-production && vite build",
|
||||||
"---------- UTILITIES ----------": "",
|
"---------- UTILITIES ----------": "",
|
||||||
|
|
@ -19,6 +20,7 @@
|
||||||
"lint-fix": "eslint . --fix && tsc --noEmit",
|
"lint-fix": "eslint . --fix && tsc --noEmit",
|
||||||
"---------- INTERNAL ----------": "",
|
"---------- INTERNAL ----------": "",
|
||||||
"setup": "node package-installer.js",
|
"setup": "node package-installer.js",
|
||||||
|
"native:build-dev": "wasm-pack build ./wasm --dev --target=web --features native",
|
||||||
"wasm:build-dev": "wasm-pack build ./wasm --dev --target=web",
|
"wasm:build-dev": "wasm-pack build ./wasm --dev --target=web",
|
||||||
"wasm:build-profiling": "wasm-pack build ./wasm --profiling --target=web",
|
"wasm:build-profiling": "wasm-pack build ./wasm --profiling --target=web",
|
||||||
"wasm:build-production": "wasm-pack build ./wasm --release --target=web",
|
"wasm:build-production": "wasm-pack build ./wasm --release --target=web",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// import { panicProxy } from "@graphite/utility-functions/panic-proxy";
|
// import { panicProxy } from "@graphite/utility-functions/panic-proxy";
|
||||||
import { type JsMessageType } from "@graphite/messages";
|
import { type JsMessageType } from "@graphite/messages";
|
||||||
import { createSubscriptionRouter, type SubscriptionRouter } from "@graphite/subscription-router";
|
import { createSubscriptionRouter, type SubscriptionRouter } from "@graphite/subscription-router";
|
||||||
import init, { setRandomSeed, wasmMemory, EditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
|
import init, { setRandomSeed, wasmMemory, EditorHandle, receiveNativeMessage } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
|
||||||
|
|
||||||
export type Editor = {
|
export type Editor = {
|
||||||
raw: WebAssembly.Memory;
|
raw: WebAssembly.Memory;
|
||||||
|
|
@ -27,6 +27,8 @@ export async function initWasm() {
|
||||||
wasmImport = await wasmMemory();
|
wasmImport = await wasmMemory();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(window as any).imageCanvases = {};
|
(window as any).imageCanvases = {};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(window as any).receiveNativeMessage = receiveNativeMessage;
|
||||||
|
|
||||||
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
|
// Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers
|
||||||
const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ license = "Apache-2.0"
|
||||||
[features]
|
[features]
|
||||||
default = ["gpu"]
|
default = ["gpu"]
|
||||||
gpu = ["editor/gpu"]
|
gpu = ["editor/gpu"]
|
||||||
|
native = []
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "rlib"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
@ -37,6 +38,7 @@ wasm-bindgen-futures = { workspace = true }
|
||||||
math-parser = { workspace = true }
|
math-parser = { workspace = true }
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
web-sys = { workspace = true }
|
web-sys = { workspace = true }
|
||||||
|
ron = { workspace = true }
|
||||||
|
|
||||||
[package.metadata.wasm-pack.profile.dev]
|
[package.metadata.wasm-pack.profile.dev]
|
||||||
wasm-opt = false
|
wasm-opt = false
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,7 @@ impl EditorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends a message to the dispatcher in the Editor Backend
|
// Sends a message to the dispatcher in the Editor Backend
|
||||||
|
#[cfg(not(feature = "native"))]
|
||||||
fn dispatch<T: Into<Message>>(&self, message: T) {
|
fn dispatch<T: Into<Message>>(&self, message: T) {
|
||||||
// Process no further messages after a crash to avoid spamming the console
|
// Process no further messages after a crash to avoid spamming the console
|
||||||
if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
|
if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
|
||||||
|
|
@ -169,6 +170,16 @@ impl EditorHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native")]
|
||||||
|
fn dispatch<T: Into<Message>>(&self, message: T) {
|
||||||
|
let message: Message = message.into();
|
||||||
|
let Ok(serialized_message) = ron::to_string(&message) else {
|
||||||
|
log::error!("Failed to serialize message");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
crate::native_communcation::send_message_to_cef(serialized_message)
|
||||||
|
}
|
||||||
|
|
||||||
// Sends a FrontendMessage to JavaScript
|
// Sends a FrontendMessage to JavaScript
|
||||||
fn send_frontend_message_to_js(&self, mut message: FrontendMessage) {
|
fn send_frontend_message_to_js(&self, mut message: FrontendMessage) {
|
||||||
if let FrontendMessage::UpdateImageData { ref image_data } = message {
|
if let FrontendMessage::UpdateImageData { ref image_data } = message {
|
||||||
|
|
@ -228,22 +239,15 @@ impl EditorHandle {
|
||||||
wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation());
|
wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation());
|
||||||
|
|
||||||
if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
|
if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
|
||||||
editor_and_handle(|editor, handle| {
|
editor_and_handle(|_, handle| {
|
||||||
for message in editor.handle_message(InputPreprocessorMessage::CurrentTime {
|
handle.dispatch(InputPreprocessorMessage::CurrentTime {
|
||||||
timestamp: js_sys::Date::now() as u64,
|
timestamp: js_sys::Date::now() as u64,
|
||||||
}) {
|
});
|
||||||
handle.send_frontend_message_to_js(message);
|
handle.dispatch(AnimationMessage::IncrementFrameCounter);
|
||||||
}
|
|
||||||
|
|
||||||
for message in editor.handle_message(AnimationMessage::IncrementFrameCounter) {
|
|
||||||
handle.send_frontend_message_to_js(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used by auto-panning, but this could possibly be refactored in the future, see:
|
// Used by auto-panning, but this could possibly be refactored in the future, see:
|
||||||
// <https://github.com/GraphiteEditor/Graphite/pull/2562#discussion_r2041102786>
|
// <https://github.com/GraphiteEditor/Graphite/pull/2562#discussion_r2041102786>
|
||||||
for message in editor.handle_message(BroadcastMessage::TriggerEvent(BroadcastEvent::AnimationFrame)) {
|
handle.dispatch(BroadcastMessage::TriggerEvent(BroadcastEvent::AnimationFrame));
|
||||||
handle.send_frontend_message_to_js(message);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -910,7 +914,7 @@ fn editor<T: Default>(callback: impl FnOnce(&mut editor::application::Editor) ->
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides access to the `Editor` and its `EditorHandle` by calling the given closure with them as arguments.
|
/// Provides access to the `Editor` and its `EditorHandle` by calling the given closure with them as arguments.
|
||||||
pub(crate) fn editor_and_handle(mut callback: impl FnMut(&mut Editor, &mut EditorHandle)) {
|
pub(crate) fn editor_and_handle(callback: impl FnOnce(&mut Editor, &mut EditorHandle)) {
|
||||||
EDITOR_HANDLE.with(|editor_handle| {
|
EDITOR_HANDLE.with(|editor_handle| {
|
||||||
editor(|editor| {
|
editor(|editor| {
|
||||||
let mut guard = editor_handle.try_lock();
|
let mut guard = editor_handle.try_lock();
|
||||||
|
|
@ -964,9 +968,7 @@ fn auto_save_all_documents() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor_and_handle(|editor, handle| {
|
editor_and_handle(|_, handle| {
|
||||||
for message in editor.handle_message(PortfolioMessage::AutoSaveAllDocuments) {
|
handle.dispatch(PortfolioMessage::AutoSaveAllDocuments);
|
||||||
handle.send_frontend_message_to_js(message);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ extern crate log;
|
||||||
|
|
||||||
pub mod editor_api;
|
pub mod editor_api;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
|
pub mod native_communcation;
|
||||||
|
|
||||||
use editor::messages::prelude::*;
|
use editor::messages::prelude::*;
|
||||||
use std::panic;
|
use std::panic;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
use editor::{application::Editor, messages::prelude::FrontendMessage};
|
||||||
|
use js_sys::{ArrayBuffer, Uint8Array};
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use crate::editor_api::{self, EditorHandle};
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "receiveNativeMessage")]
|
||||||
|
pub fn receive_native_message(buffer: ArrayBuffer) {
|
||||||
|
let buffer = Uint8Array::new(buffer.as_ref()).to_vec();
|
||||||
|
match ron::from_str::<Vec<FrontendMessage>>(str::from_utf8(buffer.as_slice()).unwrap()) {
|
||||||
|
Ok(messages) => {
|
||||||
|
let callback = move |_: &mut Editor, handle: &mut EditorHandle| {
|
||||||
|
for message in messages {
|
||||||
|
handle.send_frontend_message_to_js_rust_proxy(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
editor_api::editor_and_handle(callback);
|
||||||
|
}
|
||||||
|
Err(e) => log::error!("Failed to deserialize frontend messages: {e:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_message_to_cef(message: String) {
|
||||||
|
let global = js_sys::global();
|
||||||
|
|
||||||
|
// Get the function by name
|
||||||
|
let func = js_sys::Reflect::get(&global, &JsValue::from_str("sendNativeMessage")).expect("Function not found");
|
||||||
|
|
||||||
|
let func = func.dyn_into::<js_sys::Function>().expect("Not a function");
|
||||||
|
let array = Uint8Array::from(message.as_bytes());
|
||||||
|
let buffer = array.buffer();
|
||||||
|
|
||||||
|
// Call it with argument
|
||||||
|
func.call1(&JsValue::NULL, &JsValue::from(buffer)).expect("Function call failed");
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"---------- DEV SERVER ----------": "",
|
"---------- DEV SERVER ----------": "",
|
||||||
"start": "cd frontend && npm start",
|
"start": "cd frontend && npm start",
|
||||||
"start-desktop": "cd frontend && npm run build-dev && cargo run -p graphite-desktop",
|
"start-desktop": "cd frontend && npm run build-native && cargo run -p graphite-desktop",
|
||||||
"profiling": "cd frontend && npm run profiling",
|
"profiling": "cd frontend && npm run profiling",
|
||||||
"production": "cd frontend && npm run production",
|
"production": "cd frontend && npm run production",
|
||||||
"---------- BUILDS ----------": "",
|
"---------- BUILDS ----------": "",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue