diff --git a/Cargo.lock b/Cargo.lock index 98318b3..9505f22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2867,6 +2867,7 @@ version = "0.1.0" dependencies = [ "bevy", "console_error_panic_hook", + "parking_lot", "wasm-bindgen", ] diff --git a/package-lock.json b/package-lock.json index c2e0aaa..da41b9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "license": "MIT", "dependencies": { + "lodash": "^4.17.21", "tslib": "^2.3.0" }, "devDependencies": { @@ -18,6 +19,7 @@ "@nx/workspace": "16.9.1", "@nxrs/cargo": "^0.3.3", "@swc/core": "~1.3.85", + "@types/lodash": "^4.14.199", "@types/node": "18.7.1", "@vitest/ui": "~0.32.0", "jsdom": "~22.1.0", @@ -3187,6 +3189,12 @@ "dev": true, "peer": true }, + "node_modules/@types/lodash": { + "version": "4.14.199", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", + "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", + "dev": true + }, "node_modules/@types/node": { "version": "18.7.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.1.tgz", @@ -5608,8 +5616,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.debounce": { "version": "4.0.8", diff --git a/package.json b/package.json index d0dc5fa..b0d0232 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@nx/workspace": "16.9.1", "@nxrs/cargo": "^0.3.3", "@swc/core": "~1.3.85", + "@types/lodash": "^4.14.199", "@types/node": "18.7.1", "@vitest/ui": "~0.32.0", "jsdom": "~22.1.0", @@ -26,6 +27,7 @@ "wasm-pack": "^0.12.1" }, "dependencies": { + "lodash": "^4.17.21", "tslib": "^2.3.0" } } diff --git a/studio-frontend/src/app/app.element.ts b/studio-frontend/src/app/app.element.ts index 5d14227..302bf96 100644 --- a/studio-frontend/src/app/app.element.ts +++ b/studio-frontend/src/app/app.element.ts @@ -1,5 +1,6 @@ import * as studio from "@storyteller/studio"; import { ViewportSize } from "@storyteller/studio"; +import { throttle } from "lodash"; import "./app.element.scss"; @@ -7,11 +8,29 @@ export class AppElement extends HTMLElement { static observedAttributes = []; connectedCallback(): void { - // TODO: This siezes up the whole browser for several seconds, presumably - // while the WASM binary is loading. Is there any way to do this - // asynchronously? - const { width, height } = document.body.getBoundingClientRect(); - studio.init(new ViewportSize(width, height)); + const width = window.innerWidth; + const height = window.innerHeight; + try { + // TODO: This siezes up the whole browser for several seconds, presumably + // while the WASM binary is loading. Is there any way to do this + // asynchronously? + studio.init(new ViewportSize(width, height)); + } catch (err) { + if (!(err as Error).message?.startsWith("Using exceptions for control flow")) + throw err; + } + + window.addEventListener("resize", this.onResize); } + + disconnectedCallback(): void { + window.removeEventListener("resize", this.onResize); + } + + onResize = throttle(() => { + const width = window.innerWidth; + const height = window.innerHeight; + studio.resize(new ViewportSize(width, height)); + }, 16.666); } customElements.define("app-root", AppElement); diff --git a/studio/Cargo.toml b/studio/Cargo.toml index b3eaeb5..ce3eaea 100644 --- a/studio/Cargo.toml +++ b/studio/Cargo.toml @@ -15,6 +15,7 @@ path = "src/main.rs" [dependencies] wasm-bindgen = { version = "0.2.84" } console_error_panic_hook = { version = "0.1.7", optional = true } +parking_lot = "0.12.1" [features] wasm = ["dep:console_error_panic_hook"] diff --git a/studio/src/lib.rs b/studio/src/lib.rs index c67f828..6aafdbc 100644 --- a/studio/src/lib.rs +++ b/studio/src/lib.rs @@ -17,21 +17,25 @@ pub fn init(viewport_size: ViewportSize) { #[cfg(target_arch = "wasm32")] console_error_panic_hook::set_once(); - App::new() - .add_plugins(( - DefaultPlugins.set(WindowPlugin { - primary_window: Some(Window { - resolution: viewport_size.into(), - ..default() - }), + let mut app = App::new(); + + #[cfg(target_arch = "wasm32")] + app.add_plugins(viewport::wasm::ViewportPlugin); + + app.add_plugins(( + DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + resolution: viewport_size.into(), ..default() }), - OrbitCamPlugin, - TestScenePlugin, - UiLayerPlugin, - )) - .add_systems(Update, draw_world_axes.after(orbit_cam::pan_orbit_camera)) - .run(); + ..default() + }), + OrbitCamPlugin, + TestScenePlugin, + UiLayerPlugin, + )) + .add_systems(Update, draw_world_axes.after(orbit_cam::pan_orbit_camera)) + .run(); } fn draw_world_axes(mut gizmos: Gizmos, q: Query<&OrbitCam>) { diff --git a/studio/src/viewport.rs b/studio/src/viewport.rs index 99072c0..cddfe0e 100644 --- a/studio/src/viewport.rs +++ b/studio/src/viewport.rs @@ -1,7 +1,10 @@ +use core::fmt; + use bevy::window::WindowResolution; use wasm_bindgen::prelude::*; #[wasm_bindgen] +#[derive(Clone, Copy)] pub struct ViewportSize { pub width: f32, pub height: f32, @@ -35,3 +38,66 @@ impl From for ViewportSize { } } } + +impl fmt::Debug for ViewportSize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!("({:.2}, {:.2})", self.width, self.height)) + } +} + +#[cfg(target_arch = "wasm32")] +pub mod wasm { + use std::{cell::RefCell, sync::OnceLock}; + + use bevy::prelude::*; + use parking_lot::ReentrantMutex; + use wasm_bindgen::prelude::*; + + use super::ViewportSize; + + static RESIZE_BUF: OnceLock>>> = OnceLock::new(); + + #[wasm_bindgen] + pub fn resize(viewport_size: ViewportSize) { + let buffer = RESIZE_BUF.get_or_init(|| ReentrantMutex::new(RefCell::new(None))); + if let Some(lock) = buffer.try_lock() { + *lock.borrow_mut() = Some(viewport_size); + } + } + + pub struct ViewportPlugin; + + impl Plugin for ViewportPlugin { + fn build(&self, app: &mut App) { + app.add_event::() + .add_systems(PreUpdate, dispatch_viewport_events) + .add_systems(Update, sync_window_to_viewport); + } + } + + #[derive(Event, Clone, Copy)] + struct ViewportResizeEvent(ViewportSize); + + fn sync_window_to_viewport( + mut events: EventReader, + mut window: Query<&mut Window>, + ) { + let mut resize = None; + for event in events.into_iter() { + resize = Some(event.0); + } + + if let Some(ViewportSize { width, height }) = resize { + window.single_mut().resolution.set(width, height); + } + } + + fn dispatch_viewport_events(mut events: EventWriter) { + let buffer = RESIZE_BUF.get_or_init(|| ReentrantMutex::new(RefCell::new(None))); + if let Some(lock) = buffer.try_lock() { + if let Some(resize) = lock.borrow_mut().take() { + events.send(ViewportResizeEvent(resize)); + } + } + } +}