Replace globals with editor environment (#3656)

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Timon
2026-01-19 18:06:02 +01:00
committed by GitHub
parent 07fbcd489c
commit 95d3556204
29 changed files with 193 additions and 259 deletions

View File

@@ -16,7 +16,6 @@
import { createNodeGraphState } from "@graphite/state-providers/node-graph";
import { createPortfolioState } from "@graphite/state-providers/portfolio";
import { createTooltipState } from "@graphite/state-providers/tooltip";
import { operatingSystem } from "@graphite/utility-functions/platform";
import MainWindow from "@graphite/components/window/MainWindow.svelte";
@@ -51,7 +50,7 @@
onMount(() => {
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready
editor.handle.initAfterFrontendReady(operatingSystem());
editor.handle.initAfterFrontendReady();
});
onDestroy(() => {

View File

@@ -1,8 +1,12 @@
// import { panicProxy } from "@graphite/utility-functions/panic-proxy";
import init, { setRandomSeed, wasmMemory, EditorHandle, receiveNativeMessage } from "@graphite/../wasm/pkg/graphite_wasm";
import { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
import init, { wasmMemory, receiveNativeMessage } from "@graphite/../wasm/pkg/graphite_wasm";
import { type JsMessageType } from "@graphite/messages";
import { createSubscriptionRouter, type SubscriptionRouter } from "@graphite/subscription-router";
import { operatingSystem } from "@graphite/utility-functions/platform";
// TODO: Remove `raw`, split out `subscriptions`, and unwrap the remaining `handle` so `EditorHandle` can replace `Editor` and then it can also be renamed to `Editor` to fully remove `EditorHandle`.
export type Editor = {
raw: WebAssembly.Memory;
handle: EditorHandle;
@@ -14,10 +18,10 @@ let wasmImport: WebAssembly.Memory | undefined;
// Should be called asynchronously before `createEditor()`.
export async function initWasm() {
// Skip if the WASM module is already initialized
// Skip if the Wasm module is already initialized
if (wasmImport !== undefined) return;
// Import the WASM module JS bindings and wrap them in the panic proxy
// Import the Wasm module JS bindings and wrap them in the panic proxy
const wasm = await init();
for (const [name, f] of Object.entries(wasm)) {
if (name.startsWith("__node_registry")) f();
@@ -28,28 +32,27 @@ export async function initWasm() {
(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);
const randomSeed = BigInt(randomSeedFloat);
setRandomSeed(randomSeed);
}
// Should be called after running `initWasm()` and its promise resolving.
export function createEditor(): Editor {
// Raw: object containing several callable functions from `editor_api.rs` defined directly on the WASM module, not the `EditorHandle` struct (generated by wasm-bindgen)
if (!wasmImport) throw new Error("Editor WASM backend was not initialized at application startup");
// Raw: object containing several callable functions from `editor_api.rs` defined directly on the Wasm module, not the `EditorHandle` struct (generated by wasm-bindgen)
if (!wasmImport) throw new Error("Editor Wasm backend was not initialized at application startup");
const raw: WebAssembly.Memory = wasmImport;
// 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 randomSeed = BigInt(randomSeedFloat);
// Handle: object containing many functions from `editor_api.rs` that are part of the `EditorHandle` struct (generated by wasm-bindgen)
const handle: EditorHandle = new EditorHandle((messageType: JsMessageType, messageData: Record<string, unknown>) => {
// This callback is called by WASM when a FrontendMessage is received from the WASM wrapper `EditorHandle`
const handle = EditorHandle.create(operatingSystem(), randomSeed, (messageType: JsMessageType, messageData: Record<string, unknown>) => {
// This callback is called by Wasm when a FrontendMessage is received from the Wasm wrapper `EditorHandle`
// We pass along the first two arguments then add our own `raw` and `handle` context for the last two arguments
subscriptions.handleJsMessage(messageType, messageData, raw, handle);
});
// Subscriptions: allows subscribing to messages in JS that are sent from the WASM backend
const subscriptions: SubscriptionRouter = createSubscriptionRouter();
// Subscriptions: allows subscribing to messages in JS that are sent from the Wasm backend
const subscriptions = createSubscriptionRouter();
// Check if the URL hash fragment has any demo artwork to be loaded
(async () => {

View File

@@ -40,7 +40,7 @@ export function githubUrl(panicDetails: string): string {
Provide any further information or context that you think would be helpful in fixing the issue. Screenshots or video can be linked or attached to this issue.
**Browser and OS**
${browserVersion()}, ${operatingSystem().replace("Unknown", "YOUR OPERATING SYSTEM")}
${browserVersion()}, ${operatingSystem()}
**Stack Trace**
Copied from the crash dialog in the Graphite editor:

View File

@@ -25,18 +25,17 @@ export function browserVersion(): string {
return `${match[0]} ${match[1]}`;
}
export type OperatingSystem = "Windows" | "Mac" | "Linux" | "Unknown";
export type OperatingSystem = "Windows" | "Mac" | "Linux";
export function operatingSystem(): OperatingSystem {
const osTable: Record<string, OperatingSystem> = {
Windows: "Windows",
Mac: "Mac",
Linux: "Linux",
Unknown: "Unknown",
};
const userAgentOS = Object.keys(osTable).find((key) => window.navigator.userAgent.includes(key));
return osTable[userAgentOS || "Unknown"];
return osTable[userAgentOS || "Windows"];
}
export function isDesktop(): boolean {

View File

@@ -12,7 +12,7 @@ use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta};
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use editor::messages::portfolio::document::utility_types::network_interface::ImportOrExport;
use editor::messages::portfolio::utility_types::{FontCatalog, FontCatalogFamily, Platform};
use editor::messages::portfolio::utility_types::{FontCatalog, FontCatalogFamily};
use editor::messages::prelude::*;
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
use graph_craft::document::NodeId;
@@ -31,7 +31,7 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, window};
#[cfg(not(feature = "native"))]
use crate::EDITOR;
#[cfg(not(feature = "native"))]
use editor::application::Editor;
use editor::application::{Editor, Environment, Host, Platform};
static IMAGE_DATA_HASH: AtomicU64 = AtomicU64::new(0);
@@ -43,14 +43,7 @@ fn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {
hasher.finish()
}
/// Set the random seed used by the editor by calling this from JS upon initialization.
/// This is necessary because WASM doesn't have a random number generator.
#[wasm_bindgen(js_name = setRandomSeed)]
pub fn set_random_seed(seed: u64) {
editor::application::set_uuid_seed(seed);
}
/// Provides a handle to access the raw WASM memory.
/// Provides a handle to access the raw Wasm memory.
#[wasm_bindgen(js_name = wasmMemory)]
pub fn wasm_memory() -> JsValue {
wasm_bindgen::memory()
@@ -89,9 +82,20 @@ impl EditorHandle {
#[wasm_bindgen]
impl EditorHandle {
#[cfg(not(feature = "native"))]
#[wasm_bindgen(constructor)]
pub fn new(frontend_message_handler_callback: js_sys::Function) -> Self {
let editor = Editor::new();
pub fn create(platform: String, uuid_random_seed: u64, frontend_message_handler_callback: js_sys::Function) -> EditorHandle {
let editor = Editor::new(
Environment {
platform: Platform::Web,
host: match platform.as_str() {
"Linux" => Host::Linux,
"Mac" => Host::Mac,
"Windows" => Host::Windows,
_ => unreachable!(),
},
},
uuid_random_seed,
);
let editor_handle = EditorHandle { frontend_message_handler_callback };
if EDITOR.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor))).is_none() {
log::error!("Attempted to initialize the editor more than once");
@@ -103,8 +107,7 @@ impl EditorHandle {
}
#[cfg(feature = "native")]
#[wasm_bindgen(constructor)]
pub fn new(frontend_message_handler_callback: js_sys::Function) -> Self {
pub fn create(_platform: String, _uuid_random_seed: u64, frontend_message_handler_callback: js_sys::Function) -> EditorHandle {
let editor_handle = EditorHandle { frontend_message_handler_callback };
if EDITOR_HANDLE.with(|handle| handle.lock().ok().map(|mut guard| *guard = Some(editor_handle.clone()))).is_none() {
log::error!("Attempted to initialize the editor handle more than once");
@@ -184,18 +187,10 @@ impl EditorHandle {
// ========================================================================
#[wasm_bindgen(js_name = initAfterFrontendReady)]
pub fn init_after_frontend_ready(&self, platform: String) {
pub fn init_after_frontend_ready(&self) {
#[cfg(feature = "native")]
crate::native_communcation::initialize_native_communication();
// Send initialization messages
let platform = match platform.as_str() {
"Windows" => Platform::Windows,
"Mac" => Platform::Mac,
"Linux" => Platform::Linux,
_ => Platform::Unknown,
};
self.dispatch(GlobalsMessage::SetPlatform { platform });
self.dispatch(PortfolioMessage::Init);
// Poll node graph evaluation on `requestAnimationFrame`