use std::cell::Cell;
use std::future::Future;
use std::hash::{Hash, Hasher};
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use async_trait::async_trait;
use bytes::Bytes;
use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{
Blob, HtmlImageElement, Request, RequestInit, RequestMode, Response, WorkerGlobalScope,
};
use crate::decoded_image::{DecodedImage, DecodedImageType};
use crate::error::GalileoError;
use crate::platform::PlatformService;
pub mod vt_processor;
pub mod web_workers;
pub struct WebPlatformService {}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl PlatformService for WebPlatformService {
fn new() -> Self {
Self {}
}
async fn load_image_url(&self, url: &str) -> Result<DecodedImage, GalileoError> {
let image = ImageFuture::new(url).await?;
let window = web_sys::window().expect("no global `window` exists");
let image_bitmap_promise = window.create_image_bitmap_with_html_image_element(&image)?;
let image_bitmap = JsFuture::from(image_bitmap_promise).await?.dyn_into()?;
Ok(DecodedImage(DecodedImageType::JsImageBitmap {
js_image: image_bitmap,
hash: get_hash(url),
}))
}
async fn decode_image(&self, image_data: Bytes) -> Result<DecodedImage, GalileoError> {
let js_array = js_sys::Uint8Array::from(&image_data[..]);
let bytes = js_sys::Array::new();
bytes.push(&js_array);
let blob = Blob::new_with_u8_array_sequence(&bytes)?;
let window = web_sys::window().expect("no global `window` exists");
let image_bitmap_promise = window.create_image_bitmap_with_blob(&blob)?;
let image_bitmap = JsFuture::from(image_bitmap_promise).await?.dyn_into()?;
Ok(DecodedImage(DecodedImageType::JsImageBitmap {
js_image: image_bitmap,
hash: get_hash(&image_data),
}))
}
async fn load_bytes_from_url(&self, url: &str) -> Result<bytes::Bytes, GalileoError> {
let opts = RequestInit::new();
opts.set_method("GET");
opts.set_mode(RequestMode::Cors);
let request =
Request::new_with_str_and_init(url, &opts).expect("failed to create a request object");
request
.headers()
.set("Accept", "application/vnd.mapbox-vector-tile")?;
use wasm_bindgen::JsCast;
let resp_value = {
if let Some(window) = web_sys::window() {
JsFuture::from(window.fetch_with_request(&request)).await?
} else if let Ok(global) = js_sys::global().dyn_into::<WorkerGlobalScope>() {
JsFuture::from(global.fetch_with_request(&request)).await?
} else {
return Err(GalileoError::Wasm(Some(
"Global object is not available".into(),
)));
}
};
assert!(resp_value.is_instance_of::<Response>());
let resp: Response = resp_value.dyn_into()?;
let bytes_val = JsFuture::from(resp.array_buffer()?).await?;
let array = Uint8Array::new(&bytes_val);
Ok(array.to_vec().into())
}
}
pub struct ImageFuture {
image: Option<HtmlImageElement>,
load_failed: Rc<Cell<bool>>,
}
impl ImageFuture {
pub fn new(path: &str) -> Self {
let image = HtmlImageElement::new().expect("Cannot create HTMLImage Element");
image.set_cross_origin(Some("anonymous"));
image.set_src(path);
ImageFuture {
image: Some(image),
load_failed: Rc::new(Cell::new(false)),
}
}
}
impl Future for ImageFuture {
type Output = Result<HtmlImageElement, GalileoError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match &self.image {
Some(image) if image.complete() => {
let image = self.image.take().expect("Image future is in invalid state");
let failed = self.load_failed.get();
if failed {
Poll::Ready(Err(GalileoError::IO))
} else {
Poll::Ready(Ok(image))
}
}
Some(image) => {
let waker = cx.waker().clone();
let on_load_closure = Closure::wrap(Box::new(move || {
waker.wake_by_ref();
}) as Box<dyn FnMut()>);
image.set_onload(Some(on_load_closure.as_ref().unchecked_ref()));
on_load_closure.forget();
let waker = cx.waker().clone();
let failed_flag = self.load_failed.clone();
let on_error_closure = Closure::wrap(Box::new(move || {
failed_flag.set(true);
waker.wake_by_ref();
}) as Box<dyn FnMut()>);
image.set_onerror(Some(on_error_closure.as_ref().unchecked_ref()));
on_error_closure.forget();
Poll::Pending
}
_ => Poll::Ready(Err(GalileoError::IO)),
}
}
}
fn get_hash(object: &(impl Hash + ?Sized)) -> u64 {
let mut hasher = ahash::AHasher::default();
object.hash(&mut hasher);
hasher.finish()
}