diff --git a/Cargo.lock b/Cargo.lock index e7e6f13868..961b67c1ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1838,11 +1838,16 @@ dependencies = [ "cef", "dirs", "futures", + "glam", + "graph-craft", + "graphite-editor", "include_dir", + "ron", "thiserror 2.0.12", "tracing", "tracing-subscriber", "wgpu", + "wgpu-executor", "winit", ] @@ -1904,6 +1909,7 @@ dependencies = [ "js-sys", "log", "math-parser", + "ron", "serde", "serde-wasm-bindgen", "wasm-bindgen", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 30723b7848..97f6bca65a 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -9,17 +9,19 @@ edition = "2024" rust-version = "1.87" [features] -# default = ["gpu"] -# gpu = ["graphite-editor/gpu"] +default = ["gpu"] +gpu = ["graphite-editor/gpu"] [dependencies] -# Local dependencies -# graphite-editor = { path = "../editor", features = [ -# "gpu", -# "ron", -# "vello", -# "decouple-execution", -# ] } +# # Local dependencies +graphite-editor = { path = "../editor", features = [ + "gpu", + "ron", + "vello", +] } +graph-craft = { workspace = true } +wgpu-executor = { workspace = true } + wgpu = { workspace = true } winit = { workspace = true, features = ["serde"] } thiserror = { workspace = true } @@ -29,4 +31,6 @@ include_dir = { workspace = true } tracing-subscriber = { workspace = true } tracing = { workspace = true } dirs = { workspace = true } +ron = { workspace = true} bytemuck = { workspace = true } +glam = { workspace = true } diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 8a904662db..ae1fdc2979 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -2,6 +2,9 @@ use crate::CustomEvent; use crate::WindowSize; use crate::render::GraphicsState; 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::mpsc::Sender; use std::time::Duration; @@ -21,11 +24,10 @@ pub(crate) struct WinitApp { pub(crate) cef_context: cef::Context, pub(crate) window: Option>, cef_schedule: Option, - _ui_frame_buffer: Option, window_size_sender: Sender, - _viewport_frame_buffer: Option, graphics_state: Option, wgpu_context: WgpuContext, + pub(crate) editor: Editor, } impl WinitApp { @@ -34,13 +36,28 @@ impl WinitApp { cef_context, window: None, cef_schedule: Some(Instant::now()), - _viewport_frame_buffer: None, - _ui_frame_buffer: None, graphics_state: None, window_size_sender, 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) { + 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 for WinitApp { @@ -49,16 +66,22 @@ impl ApplicationHandler for WinitApp { let timeout = Instant::now() + Duration::from_millis(10); let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout)); self.cef_context.work(); + 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 && schedule < Instant::now() { self.cef_schedule = None; 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) { @@ -77,6 +100,10 @@ impl ApplicationHandler for WinitApp { self.graphics_state = Some(graphics_state); 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) { @@ -97,6 +124,46 @@ impl ApplicationHandler for WinitApp { 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); + } + } } } diff --git a/desktop/src/cef.rs b/desktop/src/cef.rs index 8096ba9e1f..7d3e68e328 100644 --- a/desktop/src/cef.rs +++ b/desktop/src/cef.rs @@ -120,5 +120,15 @@ impl CefEventHandler for CefHandler { 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) + } + } + } } diff --git a/desktop/src/cef/internal/render_process_handler.rs b/desktop/src/cef/internal/render_process_handler.rs index 2d125ae897..62917a8b87 100644 --- a/desktop/src/cef/internal/render_process_handler.rs +++ b/desktop/src/cef/internal/render_process_handler.rs @@ -1,9 +1,6 @@ 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::{ - CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_context_get_entered_context, - v8_value_create_function, -}; +use cef::{CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_value_create_function}; 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(), ); - 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 { tracing::error!("Failed to exit V8 context"); diff --git a/desktop/src/cef/ipc.rs b/desktop/src/cef/ipc.rs index 2c37c2c3af..69b344a464 100644 --- a/desktop/src/cef/ipc.rs +++ b/desktop/src/cef/ipc.rs @@ -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}; diff --git a/desktop/src/main.rs b/desktop/src/main.rs index edaafcd272..fcaab73d9c 100644 --- a/desktop/src/main.rs +++ b/desktop/src/main.rs @@ -1,7 +1,8 @@ -use std::fmt::Debug; use std::process::exit; use std::time::Instant; +use std::{fmt::Debug, time::Duration}; +use graphite_editor::messages::prelude::Message; use tracing_subscriber::EnvFilter; use winit::event_loop::EventLoop; @@ -20,6 +21,8 @@ mod dirs; pub(crate) enum CustomEvent { UiUpdate(wgpu::Texture), ScheduleBrowserWork(Instant), + MessageReceived { message: Message }, + NodeGraphRan { texture: Option }, } fn main() { @@ -38,7 +41,7 @@ fn main() { 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())) { Ok(c) => c, 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); event_loop.run_app(&mut winit_app).unwrap(); diff --git a/desktop/src/render.rs b/desktop/src/render.rs index 5aaf422ee1..73f54e8d81 100644 --- a/desktop/src/render.rs +++ b/desktop/src/render.rs @@ -56,46 +56,7 @@ pub(crate) enum FrameBufferError { InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize }, } -#[derive(Debug, Clone)] -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 } - } -} +pub use wgpu_executor::Context as WgpuContext; #[derive(Debug)] pub(crate) struct GraphicsState { diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 2d4a12d7cc..6871b98312 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -14,7 +14,6 @@ license = "Apache-2.0" default = ["wasm"] wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"] gpu = ["interpreted-executor/gpu", "wgpu-executor"] -decouple-execution = [] resvg = ["graphene-std/resvg"] vello = ["graphene-std/vello", "resvg"] ron = ["dep:ron"] diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 4be3e53621..e311c05f9e 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -206,11 +206,14 @@ impl Dispatcher { self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ()); } Message::Tool(message) => { - let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap(); - let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else { + let Some(document_id) = self.message_handlers.portfolio_message_handler.active_document_id() else { warn!("Called ToolMessage without an active document.\nGot {message:?}"); 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 { document_id, 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 23fb98dea6..3b29475cc9 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 @@ -34,11 +34,10 @@ impl MessageHandler for NewDocumentDialogMessageHa .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()], - }); } } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 96028179ed..37eabd96de 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -702,12 +702,12 @@ impl MessageHandler> for Portfolio 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 - responses.add(DeferMessage::AfterNavigationReady { - messages: vec![DocumentMessage::ZoomCanvasToFitAll.into()], - }); responses.add(DeferMessage::AfterGraphRun { messages: vec![DocumentMessage::WrapContentInArtboard { place_artboard_at_origin: true }.into()], }); + responses.add(DeferMessage::AfterNavigationReady { + messages: vec![DocumentMessage::ZoomCanvasToFitAll.into()], + }); } } PortfolioMessage::PasteSvg { @@ -883,6 +883,7 @@ impl MessageHandler> for Portfolio responses.add(FrontendMessage::UpdateOpenDocumentsList { open_documents }); } PortfolioMessage::UpdateVelloPreference => { + responses.add(FrontendMessage::UpdateViewportHolePunch { active: preferences.use_vello }); responses.add(NodeGraphMessage::RunDocumentGraph); self.persistent_data.use_vello = preferences.use_vello; } diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 0b4ceb38b7..aa439960dd 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -29,7 +29,6 @@ pub struct ExecutionRequest { render_config: RenderConfig, } -#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] pub struct ExecutionResponse { execution_id: u64, result: Result, @@ -46,7 +45,6 @@ pub struct CompilationResponse { node_graph_errors: GraphErrors, } -#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] pub enum NodeGraphUpdate { ExecutionResponse(ExecutionResponse), CompilationResponse(CompilationResponse), diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index b4362ce833..efc9bffe38 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -427,22 +427,14 @@ struct InspectState { } /// The resulting value from the temporary inspected during execution #[derive(Clone, Debug, Default)] -#[cfg_attr(feature = "decouple-execution", derive(serde::Serialize, serde::Deserialize))] pub struct InspectResult { - #[cfg(not(feature = "decouple-execution"))] introspected_data: Option>, - #[cfg(feature = "decouple-execution")] - introspected_data: Option, pub inspect_node: NodeId, } impl InspectResult { pub fn take_data(&mut self) -> Option> { - #[cfg(not(feature = "decouple-execution"))] 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 { 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 - #[cfg(feature = "decouple-execution")] - let introspected_data = introspected_data.as_ref().and_then(|data| TaggedValue::try_from_std_any_ref(data).ok()); Some(InspectResult { inspect_node: self.inspect_node, diff --git a/frontend/package.json b/frontend/package.json index 07fc0460b0..40d36d4c88 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "production": "npm run setup && npm run wasm:build-production && concurrently -k -n \"VITE,RUST\" \"vite\" \"npm run wasm:watch-production\"", "---------- BUILDS ----------": "", "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": "npm run wasm:build-production && vite build", "---------- UTILITIES ----------": "", @@ -19,6 +20,7 @@ "lint-fix": "eslint . --fix && tsc --noEmit", "---------- INTERNAL ----------": "", "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-profiling": "wasm-pack build ./wasm --profiling --target=web", "wasm:build-production": "wasm-pack build ./wasm --release --target=web", diff --git a/frontend/src/editor.ts b/frontend/src/editor.ts index 7dd6a98951..0eacb4f76d 100644 --- a/frontend/src/editor.ts +++ b/frontend/src/editor.ts @@ -1,7 +1,7 @@ // import { panicProxy } from "@graphite/utility-functions/panic-proxy"; import { type JsMessageType } from "@graphite/messages"; 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 = { raw: WebAssembly.Memory; @@ -27,6 +27,8 @@ export async function initWasm() { wasmImport = await wasmMemory(); // eslint-disable-next-line @typescript-eslint/no-explicit-any (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 const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml index b335c5efbf..90b8ebe6ba 100644 --- a/frontend/wasm/Cargo.toml +++ b/frontend/wasm/Cargo.toml @@ -13,6 +13,7 @@ license = "Apache-2.0" [features] default = ["gpu"] gpu = ["editor/gpu"] +native = [] [lib] crate-type = ["cdylib", "rlib"] @@ -37,6 +38,7 @@ wasm-bindgen-futures = { workspace = true } math-parser = { workspace = true } wgpu = { workspace = true } web-sys = { workspace = true } +ron = { workspace = true } [package.metadata.wasm-pack.profile.dev] wasm-opt = false diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index a4ded1c024..21dc1176fa 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -154,6 +154,7 @@ impl EditorHandle { } // Sends a message to the dispatcher in the Editor Backend + #[cfg(not(feature = "native"))] fn dispatch>(&self, message: T) { // Process no further messages after a crash to avoid spamming the console if EDITOR_HAS_CRASHED.load(Ordering::SeqCst) { @@ -169,6 +170,16 @@ impl EditorHandle { } } + #[cfg(feature = "native")] + fn dispatch>(&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 fn send_frontend_message_to_js(&self, mut message: FrontendMessage) { if let FrontendMessage::UpdateImageData { ref image_data } = message { @@ -228,22 +239,15 @@ impl EditorHandle { wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation()); if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) { - editor_and_handle(|editor, handle| { - for message in editor.handle_message(InputPreprocessorMessage::CurrentTime { + editor_and_handle(|_, handle| { + handle.dispatch(InputPreprocessorMessage::CurrentTime { timestamp: js_sys::Date::now() as u64, - }) { - handle.send_frontend_message_to_js(message); - } - - for message in editor.handle_message(AnimationMessage::IncrementFrameCounter) { - handle.send_frontend_message_to_js(message); - } + }); + handle.dispatch(AnimationMessage::IncrementFrameCounter); // Used by auto-panning, but this could possibly be refactored in the future, see: // - for message in editor.handle_message(BroadcastMessage::TriggerEvent(BroadcastEvent::AnimationFrame)) { - handle.send_frontend_message_to_js(message); - } + handle.dispatch(BroadcastMessage::TriggerEvent(BroadcastEvent::AnimationFrame)); }); } @@ -910,7 +914,7 @@ fn editor(callback: impl FnOnce(&mut editor::application::Editor) -> } /// 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(|editor| { let mut guard = editor_handle.try_lock(); @@ -964,9 +968,7 @@ fn auto_save_all_documents() { return; } - editor_and_handle(|editor, handle| { - for message in editor.handle_message(PortfolioMessage::AutoSaveAllDocuments) { - handle.send_frontend_message_to_js(message); - } + editor_and_handle(|_, handle| { + handle.dispatch(PortfolioMessage::AutoSaveAllDocuments); }); } diff --git a/frontend/wasm/src/lib.rs b/frontend/wasm/src/lib.rs index 730fb4215c..01415fc29f 100644 --- a/frontend/wasm/src/lib.rs +++ b/frontend/wasm/src/lib.rs @@ -6,6 +6,7 @@ extern crate log; pub mod editor_api; pub mod helpers; +pub mod native_communcation; use editor::messages::prelude::*; use std::panic; diff --git a/frontend/wasm/src/native_communcation.rs b/frontend/wasm/src/native_communcation.rs new file mode 100644 index 0000000000..bdf90b9524 --- /dev/null +++ b/frontend/wasm/src/native_communcation.rs @@ -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::>(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::().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"); +} diff --git a/package.json b/package.json index 7197418fb0..9bdfdae2e2 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "scripts": { "---------- DEV SERVER ----------": "", "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", "production": "cd frontend && npm run production", "---------- BUILDS ----------": "",