Fix WASM loading (#320)

* Rename send to send_back and remove use of Promise

* Fix bug where wasm loading fails

* Bump memory for multi threading
This commit is contained in:
Max Ammann 2024-11-07 00:56:45 +01:00 committed by GitHub
parent a9dc4a066b
commit 5146e6a36d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 30 additions and 36 deletions

View File

@ -21,7 +21,7 @@ const MUNICH_COORDS: TileCoords = TileCoords {
pub struct DummyContext; pub struct DummyContext;
impl Context for DummyContext { impl Context for DummyContext {
fn send<T: IntoMessage>(&self, _message: T) -> Result<(), SendError> { fn send_back<T: IntoMessage>(&self, _message: T) -> Result<(), SendError> {
Ok(()) Ok(())
} }
} }

View File

@ -162,7 +162,7 @@ pub struct HeadlessContext {
} }
impl Context for HeadlessContext { impl Context for HeadlessContext {
fn send<T: IntoMessage>(&self, message: T) -> Result<(), SendError> { fn send_back<T: IntoMessage>(&self, message: T) -> Result<(), SendError> {
self.messages.deref().borrow_mut().push(message.into()); self.messages.deref().borrow_mut().push(message.into());
Ok(()) Ok(())
} }

View File

@ -88,7 +88,7 @@ pub enum SendError {
/// Allows sending messages from workers to back to the caller. /// Allows sending messages from workers to back to the caller.
pub trait Context: 'static { pub trait Context: 'static {
/// Send a message back to the caller. /// Send a message back to the caller.
fn send<T: IntoMessage>(&self, message: T) -> Result<(), SendError>; fn send_back<T: IntoMessage>(&self, message: T) -> Result<(), SendError>;
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -190,7 +190,7 @@ pub struct SchedulerContext {
} }
impl Context for SchedulerContext { impl Context for SchedulerContext {
fn send<T: IntoMessage>(&self, message: T) -> Result<(), SendError> { fn send_back<T: IntoMessage>(&self, message: T) -> Result<(), SendError> {
self.sender self.sender
.send(message.into()) .send(message.into())
.map_err(|_e| SendError::Transmission) .map_err(|_e| SendError::Transmission)
@ -282,7 +282,7 @@ pub mod tests {
pub struct DummyContext; pub struct DummyContext;
impl Context for DummyContext { impl Context for DummyContext {
fn send<T: IntoMessage>(&self, _message: T) -> Result<(), SendError> { fn send_back<T: IntoMessage>(&self, _message: T) -> Result<(), SendError> {
Ok(()) Ok(())
} }
} }

View File

@ -55,7 +55,7 @@ impl<T: RasterTransferables, C: Context> ProcessRasterContext<T, C> {
image_data: RgbaImage, image_data: RgbaImage,
) -> Result<(), ProcessRasterError> { ) -> Result<(), ProcessRasterError> {
self.context self.context
.send(T::LayerRaster::build_from(*coords, layer_name, image_data)) .send_back(T::LayerRaster::build_from(*coords, layer_name, image_data))
.map_err(|e| ProcessRasterError::Processing(Box::new(e))) .map_err(|e| ProcessRasterError::Processing(Box::new(e)))
} }
} }

View File

@ -142,7 +142,7 @@ pub fn fetch_raster_apc<K: OffscreenKernel, T: RasterTransferables, C: Context +
log::error!("{e:?}"); log::error!("{e:?}");
context context
.send(<T as RasterTransferables>::LayerRasterMissing::build_from( .send_back(<T as RasterTransferables>::LayerRasterMissing::build_from(
coords, coords,
)) ))
.map_err(ProcedureError::Send)?; .map_err(ProcedureError::Send)?;

View File

@ -125,7 +125,7 @@ impl<T: VectorTransferables, C: Context> ProcessVectorContext<T, C> {
fn tile_finished(&mut self, coords: &WorldTileCoords) -> Result<(), ProcessVectorError> { fn tile_finished(&mut self, coords: &WorldTileCoords) -> Result<(), ProcessVectorError> {
self.context self.context
.send(T::TileTessellated::build_from(*coords)) .send_back(T::TileTessellated::build_from(*coords))
.map_err(|e| ProcessVectorError::SendError(e)) .map_err(|e| ProcessVectorError::SendError(e))
} }
@ -135,7 +135,7 @@ impl<T: VectorTransferables, C: Context> ProcessVectorContext<T, C> {
layer_name: &str, layer_name: &str,
) -> Result<(), ProcessVectorError> { ) -> Result<(), ProcessVectorError> {
self.context self.context
.send(T::LayerMissing::build_from(*coords, layer_name.to_owned())) .send_back(T::LayerMissing::build_from(*coords, layer_name.to_owned()))
.map_err(|e| ProcessVectorError::SendError(e)) .map_err(|e| ProcessVectorError::SendError(e))
} }
@ -147,7 +147,7 @@ impl<T: VectorTransferables, C: Context> ProcessVectorContext<T, C> {
layer_data: tile::Layer, layer_data: tile::Layer,
) -> Result<(), ProcessVectorError> { ) -> Result<(), ProcessVectorError> {
self.context self.context
.send(T::LayerTessellated::build_from( .send_back(T::LayerTessellated::build_from(
*coords, *coords,
buffer, buffer,
feature_indices, feature_indices,
@ -162,7 +162,7 @@ impl<T: VectorTransferables, C: Context> ProcessVectorContext<T, C> {
geometries: Vec<IndexedGeometry<f64>>, geometries: Vec<IndexedGeometry<f64>>,
) -> Result<(), ProcessVectorError> { ) -> Result<(), ProcessVectorError> {
self.context self.context
.send(T::LayerIndexed::build_from( .send_back(T::LayerIndexed::build_from(
*coords, *coords,
TileIndex::Linear { list: geometries }, TileIndex::Linear { list: geometries },
)) ))

View File

@ -151,7 +151,7 @@ pub fn fetch_vector_apc<K: OffscreenKernel, T: VectorTransferables, C: Context +
log::error!("{e:?}"); log::error!("{e:?}");
for to_load in &fill_layers { for to_load in &fill_layers {
context context
.send(<T as VectorTransferables>::LayerMissing::build_from( .send_back(<T as VectorTransferables>::LayerMissing::build_from(
coords, coords,
to_load.to_string(), to_load.to_string(),
)) ))

View File

@ -59,6 +59,7 @@ if (multithreaded) {
let baseConfig = { let baseConfig = {
platform: "browser", platform: "browser",
bundle: true, bundle: true,
minify: release,
assetNames: "assets/[name]", assetNames: "assets/[name]",
define: { define: {
WEBGL: `${webgl}`, WEBGL: `${webgl}`,

View File

@ -25,7 +25,7 @@ export const startMapLibre = async (wasmPath: string | undefined, workerPath: st
preventDefaultTouchActions(); preventDefaultTouchActions();
if (MULTITHREADED) { if (MULTITHREADED) {
const MEMORY = 209715200; // 200MB const MEMORY = 900 * 1024 * 1024; // 900 MB
const PAGES = 64 * 1024; const PAGES = 64 * 1024;
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY / PAGES, shared: true}) const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY / PAGES, shared: true})

View File

@ -1,7 +1,7 @@
import * as maplibre from "../wasm/maplibre" import * as maplibre from "../wasm/maplibre"
type MessageData = {type: 'wasm_init', module: WebAssembly.Module, memory: WebAssembly.Memory} type MessageData = {type: 'wasm_init', module: WebAssembly.Module, memory: WebAssembly.Memory}
| {type: 'call', work_ptr: number} | {type: 'pool_call', work_ptr: number}
let initialised: Promise<maplibre.InitOutput> = null let initialised: Promise<maplibre.InitOutput> = null
@ -17,7 +17,7 @@ onmessage = async (message: MessageEvent<MessageData>) => {
const data = message.data; const data = message.data;
const module = data.module; const module = data.module;
const memory = data.memory; const memory = data.memory;
const initialised = maplibre.default(module, memory).catch(err => { initialised = maplibre.default(module, memory).catch(err => {
// Propagate to main `onerror`: // Propagate to main `onerror`:
setTimeout(() => { setTimeout(() => {
throw err; throw err;
@ -25,10 +25,8 @@ onmessage = async (message: MessageEvent<MessageData>) => {
// Rethrow to keep promise rejected and prevent execution of further commands: // Rethrow to keep promise rejected and prevent execution of further commands:
throw err; throw err;
}); });
} else if (type === 'call') { } else if (type === 'pool_call') {
const work_ptr = message.data.work_ptr; // because memory is shared, this pointer is valid in the memory of the main thread and this worker thread const work_ptr = message.data.work_ptr; // because memory is shared, this pointer is valid in the memory of the main thread and this worker thread
// This will queue further commands up until the module is fully initialised:
await initialised;
const process_data: (msg: any) => Promise<void> = maplibre["multithreaded_process_data"] const process_data: (msg: any) => Promise<void> = maplibre["multithreaded_process_data"]

View File

@ -4,7 +4,6 @@
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use js_sys::Promise;
use rand::prelude::*; use rand::prelude::*;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use web_sys::Worker; use web_sys::Worker;
@ -17,8 +16,10 @@ extern "C" {
fn new_worker() -> JsValue; fn new_worker() -> JsValue;
} }
pub type PinnedFuture = std::pin::Pin<Box<(dyn std::future::Future<Output = ()> + 'static)>>;
type NewWorker = Box<dyn Fn() -> Result<Worker, WebError>>; type NewWorker = Box<dyn Fn() -> Result<Worker, WebError>>;
type Execute = Box<dyn (FnOnce() -> Promise) + Send>; type Execute = Box<dyn (FnOnce() -> PinnedFuture) + Send>;
pub struct WorkerPool { pub struct WorkerPool {
new_worker: NewWorker, new_worker: NewWorker,
@ -44,7 +45,7 @@ pub struct Work {
} }
impl Work { impl Work {
pub fn execute(self) -> Promise { pub fn execute(self) -> PinnedFuture {
(self.func)() (self.func)()
} }
} }
@ -139,13 +140,16 @@ impl WorkerPool {
/// ///
/// Returns any error that may happen while a JS web worker is created and a /// Returns any error that may happen while a JS web worker is created and a
/// message is sent to it. /// message is sent to it.
pub fn execute(&self, f: impl (FnOnce() -> Promise) + Send + 'static) -> Result<(), WebError> { pub fn execute(
&self,
f: impl (FnOnce() -> PinnedFuture) + Send + 'static,
) -> Result<(), WebError> {
let worker = self.worker()?; let worker = self.worker()?;
let work = Work { func: Box::new(f) }; let work = Work { func: Box::new(f) };
let work_ptr = Box::into_raw(Box::new(work)); let work_ptr = Box::into_raw(Box::new(work));
match worker.post_message( match worker.post_message(
&js_sys::Object::from_entries(&js_sys::Array::of2( &js_sys::Object::from_entries(&js_sys::Array::of2(
&js_sys::Array::of2(&JsValue::from("type"), &js_sys::JsString::from("call")), &js_sys::Array::of2(&JsValue::from("type"), &js_sys::JsString::from("pool_call")),
&js_sys::Array::of2(&JsValue::from("work_ptr"), &JsValue::from(work_ptr as u32)), &js_sys::Array::of2(&JsValue::from("work_ptr"), &JsValue::from(work_ptr as u32)),
)) ))
.expect("can not fail"), .expect("can not fail"),

View File

@ -36,12 +36,7 @@ impl Scheduler for WebWorkerPoolScheduler {
T: Future<Output = ()> + 'static, T: Future<Output = ()> + 'static,
{ {
self.pool self.pool
.execute(move || { .execute(move || Box::pin(future_factory()))
wasm_bindgen_futures::future_to_promise(async move {
future_factory().await;
Ok(JsValue::undefined())
})
})
.map_err(|e| ScheduleError::Scheduling(Box::new(e))) .map_err(|e| ScheduleError::Scheduling(Box::new(e)))
} }
} }

View File

@ -1,15 +1,11 @@
use maplibre::io::apc::CallError;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use crate::{platform::multithreaded::pool::Work, JSError}; use crate::{platform::multithreaded::pool::Work, JSError};
/// Entry point invoked by the worker. /// Entry point invoked by the worker.
#[wasm_bindgen] #[wasm_bindgen]
pub async fn multithreaded_process_data(work_ptr: *mut Work) -> Result<(), JSError> { pub async fn multithreaded_process_data(work_ptr: *mut Work) -> Result<(), JSError> {
let work = unsafe { Box::from_raw(work_ptr) }; let work: Box<Work> = unsafe { Box::from_raw(work_ptr) };
JsFuture::from(work.execute()) work.execute().await;
.await
.map_err(|_e| CallError::Schedule)?;
Ok(()) Ok(())
} }

View File

@ -84,7 +84,7 @@ pub struct PassingContext {
} }
impl Context for PassingContext { impl Context for PassingContext {
fn send<T: IntoMessage>(&self, message: T) -> Result<(), SendError> { fn send_back<T: IntoMessage>(&self, message: T) -> Result<(), SendError> {
let message = message.into(); let message = message.into();
let tag = if WebMessageTag::LayerRaster.dyn_clone().as_ref() == message.tag() { let tag = if WebMessageTag::LayerRaster.dyn_clone().as_ref() == message.tag() {
&WebMessageTag::LayerRaster &WebMessageTag::LayerRaster