#![allow(non_upper_case_globals)]
use gloo::timers::future::TimeoutFuture;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use gloo::utils::format::JsValueSerdeExt;
pub mod simple2d;
pub mod utils {
use super::*;
pub fn get_by_id_elem(id: &str) -> web_sys::HtmlElement {
gloo::utils::document()
.get_element_by_id(id)
.unwrap_throw()
.dyn_into()
.unwrap_throw()
}
pub fn get_by_id_canvas(id: &str) -> web_sys::HtmlCanvasElement {
gloo::utils::document()
.get_element_by_id(id)
.unwrap_throw()
.dyn_into()
.unwrap_throw()
}
pub fn get_context_webgl2_offscreen(
canvas: &web_sys::OffscreenCanvas,
) -> web_sys::WebGl2RenderingContext {
canvas
.get_context("webgl2")
.unwrap_throw()
.unwrap_throw()
.dyn_into()
.unwrap_throw()
}
pub fn get_worker_global_context() -> web_sys::DedicatedWorkerGlobalScope {
js_sys::global().dyn_into().unwrap_throw()
}
}
#[wasm_bindgen]
extern "C" {
#[no_mangle]
#[used]
static performance: web_sys::Performance;
}
struct Timer {
last: f64,
frame_rate: usize,
}
impl Timer {
fn new(frame_rate: usize) -> Timer {
let frame_rate = ((1.0 / frame_rate as f64) * 1000.0).round() as usize;
assert!(frame_rate > 0);
Timer {
last: performance.now(),
frame_rate,
}
}
async fn next(&mut self) {
let tt = performance.now();
let diff = performance.now() - self.last;
if self.frame_rate as f64 - diff > 0.0 {
let d = (self.frame_rate as f64 - diff) as usize;
TimeoutFuture::new(d.try_into().unwrap_throw()).await;
}
self.last = tt;
}
}
use futures::FutureExt;
use futures::Stream;
use futures::StreamExt;
pub struct FrameTimer<T, K> {
timer: Timer,
buffer: Vec<T>,
stream: K,
}
impl<T, K: Stream<Item = T> + std::marker::Unpin> FrameTimer<T, K> {
pub fn new(frame_rate: usize, stream: K) -> Self {
FrameTimer {
timer: Timer::new(frame_rate),
buffer: vec![],
stream,
}
}
pub async fn next(&mut self) -> &[T] {
self.buffer.clear();
loop {
futures::select_biased!(
_ = self.timer.next().fuse() =>{
break;
},
val = self.stream.next().fuse()=>{
self.buffer.push(val.unwrap_throw());
}
)
}
&self.buffer
}
}
pub use main::EngineMain;
use std::marker::PhantomData;
mod main {
use super::*;
pub struct EngineMain<MW, WM> {
worker: std::rc::Rc<std::cell::RefCell<web_sys::Worker>>,
_handle: gloo::events::EventListener,
_p: PhantomData<(MW, WM)>,
}
impl<MW: 'static + Serialize, WM: for<'a> Deserialize<'a> + 'static> EngineMain<MW, WM> {
pub async fn new(
web_worker_url: &str,
canvas: web_sys::OffscreenCanvas,
) -> (Self, futures::channel::mpsc::UnboundedReceiver<WM>) {
let mut options = web_sys::WorkerOptions::new();
options.type_(web_sys::WorkerType::Module);
let worker = Rc::new(RefCell::new(
web_sys::Worker::new_with_options(web_worker_url, &options).unwrap_throw(),
));
let (fs, fr) = futures::channel::oneshot::channel();
let mut fs = Some(fs);
let (ks, kr) = futures::channel::mpsc::unbounded();
let _handle =
gloo::events::EventListener::new(&worker.borrow(), "message", move |event| {
let event = event.dyn_ref::<web_sys::MessageEvent>().unwrap_throw();
let data = event.data();
let data: js_sys::Array = data.dyn_into().unwrap_throw();
let m = data.get(0);
let k = data.get(1);
if !m.is_null() {
if let Some(s) = m.as_string() {
if s == "ready" {
if let Some(f) = fs.take() {
f.send(()).unwrap_throw();
}
}
}
} else {
let a = k.into_serde().unwrap_throw();
ks.unbounded_send(a).unwrap_throw();
}
});
let _ = fr.await.unwrap_throw();
let arr = js_sys::Array::new_with_length(1);
arr.set(0, canvas.clone().into());
let data = js_sys::Array::new();
data.set(0, canvas.into());
data.set(1, JsValue::null());
worker
.borrow()
.post_message_with_transfer(&data, &arr)
.unwrap_throw();
(
EngineMain {
worker,
_handle,
_p: PhantomData,
},
kr,
)
}
pub fn post_message(&mut self, val: MW) {
let a = JsValue::from_serde(&val).unwrap_throw();
let data = js_sys::Array::new();
data.set(0, JsValue::null());
data.set(1, a);
self.worker.borrow().post_message(&data).unwrap_throw();
}
pub fn register_event(
&mut self,
elem: &web_sys::HtmlElement,
event_type: &'static str,
mut func: impl FnMut(EventData) -> MW + 'static,
) -> gloo::events::EventListener {
let w = self.worker.clone();
let e = elem.clone();
gloo::events::EventListener::new(elem, event_type, move |event| {
let e = EventData {
elem: &e,
event,
event_type,
};
let val = func(e);
let a = JsValue::from_serde(&val).unwrap_throw();
let data = js_sys::Array::new();
data.set(0, JsValue::null());
data.set(1, a);
w.borrow().post_message(&data).unwrap_throw();
})
}
}
}
pub struct EventData<'a> {
pub elem: &'a web_sys::HtmlElement,
pub event: &'a web_sys::Event,
pub event_type: &'static str,
}
pub use worker::EngineWorker;
mod worker {
use super::*;
pub struct EngineWorker<MW, WM> {
_handle: gloo::events::EventListener,
canvas: web_sys::OffscreenCanvas,
_p: PhantomData<(MW, WM)>,
}
impl<MW: 'static + for<'a> Deserialize<'a>, WM: Serialize> EngineWorker<MW, WM> {
pub fn canvas(&self) -> web_sys::OffscreenCanvas {
self.canvas.clone()
}
pub async fn new() -> (
EngineWorker<MW, WM>,
futures::channel::mpsc::UnboundedReceiver<MW>,
) {
let scope = utils::get_worker_global_context();
let (fs, fr) = futures::channel::oneshot::channel();
let mut fs = Some(fs);
let (bags, bagf) = futures::channel::mpsc::unbounded();
let _handle = gloo::events::EventListener::new(&scope, "message", move |event| {
let event = event.dyn_ref::<web_sys::MessageEvent>().unwrap_throw();
let data = event.data();
let data: js_sys::Array = data.dyn_into().unwrap_throw();
let offscreen = data.get(0);
let payload = data.get(1);
if !offscreen.is_null() {
let offscreen: web_sys::OffscreenCanvas = offscreen.dyn_into().unwrap_throw();
if let Some(fs) = fs.take() {
fs.send(offscreen).unwrap_throw();
}
}
if !payload.is_null() {
let e = payload.into_serde().unwrap_throw();
bags.unbounded_send(e).unwrap_throw();
}
});
let data = js_sys::Array::new();
data.set(0, JsValue::from_str("ready"));
data.set(1, JsValue::null());
scope.post_message(&data).unwrap_throw();
let canvas = fr.await.unwrap_throw();
(
EngineWorker {
_handle,
canvas,
_p: PhantomData,
},
bagf,
)
}
pub fn post_message(&mut self, a: WM) {
let scope = utils::get_worker_global_context();
let data = js_sys::Array::new();
data.set(0, JsValue::null());
data.set(1, JsValue::from_serde(&a).unwrap_throw());
scope.post_message(&data).unwrap_throw();
}
}
}