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",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<cef::Initialized>,
|
||||
pub(crate) window: Option<Arc<Window>>,
|
||||
cef_schedule: Option<Instant>,
|
||||
_ui_frame_buffer: Option<wgpu::Texture>,
|
||||
window_size_sender: Sender<WindowSize>,
|
||||
_viewport_frame_buffer: Option<wgpu::Texture>,
|
||||
graphics_state: Option<GraphicsState>,
|
||||
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<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 {
|
||||
|
|
@ -49,16 +66,22 @@ impl ApplicationHandler<CustomEvent> 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<CustomEvent> 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<CustomEvent> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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<wgpu::Texture> },
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -34,11 +34,10 @@ impl MessageHandler<NewDocumentDialogMessage, ()> 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()],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -702,12 +702,12 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> 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<PortfolioMessage, PortfolioMessageContext<'_>> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TaggedValue, String>,
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<Arc<dyn std::any::Any + Send + Sync + 'static>>,
|
||||
#[cfg(feature = "decouple-execution")]
|
||||
introspected_data: Option<TaggedValue>,
|
||||
pub inspect_node: NodeId,
|
||||
}
|
||||
|
||||
impl InspectResult {
|
||||
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();
|
||||
|
||||
#[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> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ impl EditorHandle {
|
|||
}
|
||||
|
||||
// Sends a message to the dispatcher in the Editor Backend
|
||||
#[cfg(not(feature = "native"))]
|
||||
fn dispatch<T: Into<Message>>(&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<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
|
||||
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:
|
||||
// <https://github.com/GraphiteEditor/Graphite/pull/2562#discussion_r2041102786>
|
||||
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<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.
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
"---------- 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 ----------": "",
|
||||
|
|
|
|||
Loading…
Reference in New Issue