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;
impl Context for DummyContext {
fn send<T: IntoMessage>(&self, _message: T) -> Result<(), SendError> {
fn send_back<T: IntoMessage>(&self, _message: T) -> Result<(), SendError> {
Ok(())
}
}

View File

@ -162,7 +162,7 @@ pub struct 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());
Ok(())
}

View File

@ -88,7 +88,7 @@ pub enum SendError {
/// Allows sending messages from workers to back to the caller.
pub trait Context: 'static {
/// 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)]
@ -190,7 +190,7 @@ pub struct 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
.send(message.into())
.map_err(|_e| SendError::Transmission)
@ -282,7 +282,7 @@ pub mod tests {
pub struct 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(())
}
}

View File

@ -55,7 +55,7 @@ impl<T: RasterTransferables, C: Context> ProcessRasterContext<T, C> {
image_data: RgbaImage,
) -> Result<(), ProcessRasterError> {
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)))
}
}

View File

@ -142,7 +142,7 @@ pub fn fetch_raster_apc<K: OffscreenKernel, T: RasterTransferables, C: Context +
log::error!("{e:?}");
context
.send(<T as RasterTransferables>::LayerRasterMissing::build_from(
.send_back(<T as RasterTransferables>::LayerRasterMissing::build_from(
coords,
))
.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> {
self.context
.send(T::TileTessellated::build_from(*coords))
.send_back(T::TileTessellated::build_from(*coords))
.map_err(|e| ProcessVectorError::SendError(e))
}
@ -135,7 +135,7 @@ impl<T: VectorTransferables, C: Context> ProcessVectorContext<T, C> {
layer_name: &str,
) -> Result<(), ProcessVectorError> {
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))
}
@ -147,7 +147,7 @@ impl<T: VectorTransferables, C: Context> ProcessVectorContext<T, C> {
layer_data: tile::Layer,
) -> Result<(), ProcessVectorError> {
self.context
.send(T::LayerTessellated::build_from(
.send_back(T::LayerTessellated::build_from(
*coords,
buffer,
feature_indices,
@ -162,7 +162,7 @@ impl<T: VectorTransferables, C: Context> ProcessVectorContext<T, C> {
geometries: Vec<IndexedGeometry<f64>>,
) -> Result<(), ProcessVectorError> {
self.context
.send(T::LayerIndexed::build_from(
.send_back(T::LayerIndexed::build_from(
*coords,
TileIndex::Linear { list: geometries },
))

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import * as maplibre from "../wasm/maplibre"
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
@ -17,7 +17,7 @@ onmessage = async (message: MessageEvent<MessageData>) => {
const data = message.data;
const module = data.module;
const memory = data.memory;
const initialised = maplibre.default(module, memory).catch(err => {
initialised = maplibre.default(module, memory).catch(err => {
// Propagate to main `onerror`:
setTimeout(() => {
throw err;
@ -25,10 +25,8 @@ onmessage = async (message: MessageEvent<MessageData>) => {
// Rethrow to keep promise rejected and prevent execution of further commands:
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
// 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"]

View File

@ -4,7 +4,6 @@
use std::{cell::RefCell, rc::Rc};
use js_sys::Promise;
use rand::prelude::*;
use wasm_bindgen::prelude::*;
use web_sys::Worker;
@ -17,8 +16,10 @@ extern "C" {
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 Execute = Box<dyn (FnOnce() -> Promise) + Send>;
type Execute = Box<dyn (FnOnce() -> PinnedFuture) + Send>;
pub struct WorkerPool {
new_worker: NewWorker,
@ -44,7 +45,7 @@ pub struct Work {
}
impl Work {
pub fn execute(self) -> Promise {
pub fn execute(self) -> PinnedFuture {
(self.func)()
}
}
@ -139,13 +140,16 @@ impl WorkerPool {
///
/// Returns any error that may happen while a JS web worker is created and a
/// 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 work = Work { func: Box::new(f) };
let work_ptr = Box::into_raw(Box::new(work));
match worker.post_message(
&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)),
))
.expect("can not fail"),

View File

@ -36,12 +36,7 @@ impl Scheduler for WebWorkerPoolScheduler {
T: Future<Output = ()> + 'static,
{
self.pool
.execute(move || {
wasm_bindgen_futures::future_to_promise(async move {
future_factory().await;
Ok(JsValue::undefined())
})
})
.execute(move || Box::pin(future_factory()))
.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_futures::JsFuture;
use crate::{platform::multithreaded::pool::Work, JSError};
/// Entry point invoked by the worker.
#[wasm_bindgen]
pub async fn multithreaded_process_data(work_ptr: *mut Work) -> Result<(), JSError> {
let work = unsafe { Box::from_raw(work_ptr) };
JsFuture::from(work.execute())
.await
.map_err(|_e| CallError::Schedule)?;
let work: Box<Work> = unsafe { Box::from_raw(work_ptr) };
work.execute().await;
Ok(())
}

View File

@ -84,7 +84,7 @@ pub struct 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 tag = if WebMessageTag::LayerRaster.dyn_clone().as_ref() == message.tag() {
&WebMessageTag::LayerRaster