pteropus/src/wasm/mod.rs

161 lines
4.5 KiB
Rust

use {
log::info,
std::{rc::Rc, sync::Mutex},
wasm_bindgen::prelude::*,
web_sys::HtmlCanvasElement,
wgpu::SurfaceTarget,
};
use crate::{
app::{App, Model},
mvu,
mvu::{Event, MvuApp, Size2i},
};
struct FrameTimer {
performance: web_sys::Performance,
last_frame_start_milliseconds: f64,
current_frame_start_milliseconds: f64,
}
impl FrameTimer {
fn new() -> Self {
let performance = web_sys::window()
.expect("get window")
.performance()
.expect("window has performance object");
let last_frame_start_milliseconds = performance.now();
let current_frame_start_milliseconds = performance.now();
Self {
performance,
last_frame_start_milliseconds,
current_frame_start_milliseconds,
}
}
}
impl mvu::FrameTimer for FrameTimer {
fn mark_frame_start(&mut self) {
self.last_frame_start_milliseconds = self.current_frame_start_milliseconds;
self.current_frame_start_milliseconds = self.performance.now();
}
fn get_frame_time_seconds(&self) -> f64 {
(self.current_frame_start_milliseconds - self.last_frame_start_milliseconds) * 0.001
}
}
#[wasm_bindgen]
#[derive(Clone)]
pub struct PteropusCanvas {
app: Rc<Mutex<App>>,
model: Rc<Model>,
}
#[wasm_bindgen(getter_with_clone)]
#[derive(Clone)]
pub struct PteropusInitError {
pub message: String,
}
impl PteropusInitError {
fn new<M: Into<String>>(message: M) -> Self {
Self {
message: message.into(),
}
}
}
#[wasm_bindgen]
impl PteropusCanvas {
async fn init(&self) -> Result<(), PteropusInitError> {
let html_canvas = self.get_canvas();
let size = Size2i {
width: html_canvas.client_width() as u32,
height: html_canvas.client_height() as u32,
};
let instance = wgpu::Instance::default();
let surface_target = SurfaceTarget::Canvas(html_canvas);
let surface = instance.create_surface(surface_target).map_err(|_| {
PteropusInitError::new(
"Could not initialize canvas. This browser may not support WebGPU.",
)
})?;
self.app
.lock()
.expect("get app mutex")
.init(&instance, surface, size, Box::new(FrameTimer::new()))
.await;
Ok(())
}
#[wasm_bindgen]
pub async fn render(&self) {
self.app
.lock()
.expect("get app mutex")
.view(Rc::clone(&self.model))
.await
.expect("view succeeds");
}
#[wasm_bindgen]
pub async fn on_resize(&self) {
let html_canvas = self.get_canvas();
self.app
.lock()
.expect("get app mutex")
.resize(Size2i {
width: html_canvas.client_width() as u32,
height: html_canvas.client_height() as u32,
})
.await;
}
#[wasm_bindgen]
pub async fn load_file(&mut self, file: web_sys::File) {
let data = gloo::file::futures::read_as_bytes(&file.into())
.await
.expect("read data from dragged file");
self.model = self
.app
.lock()
.expect("get app mutex")
.update(
Rc::clone(&self.model),
Event::OpenTestFile(Box::new(std::io::Cursor::new(data))),
)
.await;
}
#[wasm_bindgen]
pub async fn clear_scene(&mut self) {
self.model = self
.app
.lock()
.expect("get app mutex")
.update(Rc::clone(&self.model), Event::ClearScene)
.await;
}
pub fn get_canvas(&self) -> HtmlCanvasElement {
let window = web_sys::window().expect("get window");
let document = window.document().expect("get HTML document");
let canvas_element = document
.get_element_by_id("pteropus-canvas")
.expect("document contains element with id \"pteropus-canvas\"");
canvas_element
.dyn_into()
.expect("pteropus-canvas element is a canvas")
}
}
pub async fn run(app: App, model: Rc<Model>) -> Result<PteropusCanvas, PteropusInitError> {
let app = Rc::new(Mutex::new(app));
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Info).expect("Couldn't initialize logger");
let pteropus_canvas = PteropusCanvas { app, model };
pteropus_canvas.init().await?;
Ok(pteropus_canvas)
}