Add first working version which does not need shared memory on web

This commit is contained in:
Maximilian Ammann 2022-09-15 09:46:17 +02:00
parent d0c86492f7
commit 7dc7346597
34 changed files with 819 additions and 300 deletions

View File

@ -1,3 +1,6 @@
use maplibre::environment::DefaultTransferables;
use maplibre::platform::apc::TokioAsyncProcedureCall;
use maplibre::{
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
MapBuilder,
@ -5,9 +8,10 @@ use maplibre::{
use maplibre_winit::winit::{WinitEnvironment, WinitMapWindowConfig};
pub async fn run_headed() {
MapBuilder::<WinitEnvironment<_, _>>::new()
MapBuilder::<WinitEnvironment<_, _, _, TokioAsyncProcedureCall>>::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(ReqwestHttpClient::new(None))
.with_apc(TokioAsyncProcedureCall::new())
.with_scheduler(TokioScheduler::new())
.build()
.initialize()

View File

@ -1,4 +1,5 @@
use maplibre::headless::HeadlessEnvironment;
use maplibre::platform::apc::TokioAsyncProcedureCall;
use maplibre::{
coords::{LatLon, WorldTileCoords},
error::Error,
@ -13,11 +14,12 @@ use maplibre_winit::winit::WinitEnvironment;
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
let mut map = MapBuilder::<HeadlessEnvironment<_, _>>::new()
let mut map = MapBuilder::<HeadlessEnvironment<_, _, _, TokioAsyncProcedureCall>>::new()
.with_map_window_config(HeadlessMapWindowConfig {
size: WindowSize::new(tile_size, tile_size).unwrap(),
})
.with_http_client(ReqwestHttpClient::new(None))
.with_apc(TokioAsyncProcedureCall::new())
.with_scheduler(TokioScheduler::new())
.with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,

View File

@ -1,15 +1,18 @@
use instant::Instant;
use maplibre::environment::{DefaultTransferables, Environment};
use maplibre::io::apc::Transferable;
use maplibre::io::apc::{AsyncProcedureCall, Transferable};
use maplibre::io::scheduler::Scheduler;
use maplibre::platform::apc::TokioAsyncProcedureCall;
use maplibre::io::transferables::Transferables;
use maplibre::{
error::Error,
io::source_client::HttpClient,
map_schedule::InteractiveMapSchedule,
window::{EventLoop, HeadedMapWindow, MapWindowConfig},
};
use std::cell::RefCell;
use std::marker::PhantomData;
use std::ops::Deref;
use std::rc::Rc;
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::ControlFlow,
@ -66,17 +69,26 @@ impl WinitMapWindow {
pub type WinitWindow = winit::window::Window;
pub type WinitEventLoop = winit::event_loop::EventLoop<()>;
pub struct WinitEnvironment<S: Scheduler, HC: HttpClient> {
pub struct WinitEnvironment<
S: Scheduler,
HC: HttpClient,
T: Transferables,
APC: AsyncProcedureCall<T, HC>,
> {
phantom_s: PhantomData<S>,
phantom_hc: PhantomData<HC>,
phantom_t: PhantomData<T>,
phantom_apc: PhantomData<APC>,
}
impl<S: Scheduler, HC: HttpClient> Environment for WinitEnvironment<S, HC> {
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
for WinitEnvironment<S, HC, T, APC>
{
type MapWindowConfig = WinitMapWindowConfig;
type AsyncProcedureCall = TokioAsyncProcedureCall<DefaultTransferables>;
type AsyncProcedureCall = APC;
type Scheduler = S;
type HttpClient = HC;
type Transferables = DefaultTransferables;
type Transferables = T;
}
///Main (platform-specific) main loop which handles:
@ -87,7 +99,11 @@ impl<E: Environment> EventLoop<E> for WinitMapWindow
where
E::MapWindowConfig: MapWindowConfig<MapWindow = WinitMapWindow>,
{
fn run(mut self, mut map_schedule: InteractiveMapSchedule<E>, max_frames: Option<u64>) {
fn run(
mut self,
map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
max_frames: Option<u64>,
) {
let mut last_render_time = Instant::now();
let mut current_frame: u64 = 0;
@ -96,6 +112,8 @@ where
self.take_event_loop()
.unwrap()
.run(move |event, _, control_flow| {
let mut map_schedule = map_schedule.deref().borrow_mut();
#[cfg(target_os = "android")]
if !map_schedule.is_initialized() && event == Event::Resumed {
use tokio::{runtime::Handle, task};

View File

@ -19,6 +19,7 @@ headless = ["png"]
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android", target_os = "windows"))'.dependencies]
tokio = { version = "1.20.1", features = ["macros", "rt", "rt-multi-thread", "sync", "time"] }
tokio-util = { version = "0.7.1", features = ["rt"] }
env_logger = "0.9.0"
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "gzip"] }
reqwest-middleware-cache = "0.1.1"

View File

@ -6,6 +6,7 @@ use std::{
fmt::{Display, Formatter},
};
use bytemuck_derive::{Pod, Zeroable};
use cgmath::{num_traits::Pow, AbsDiffEq, Matrix4, Point3, Vector3};
use crate::{
@ -15,7 +16,7 @@ use crate::{
SignificantlyDifferent,
},
};
use serde::Serialize;
use serde::{Deserialize, Serialize};
pub const EXTENT_UINT: u32 = 4096;
pub const EXTENT_SINT: i32 = EXTENT_UINT as i32;
@ -68,7 +69,23 @@ impl fmt::Debug for Quadkey {
}
}
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone, Debug, Default, Serialize)]
// FIXME: does Pod and Zeroable make sense?
#[derive(
Ord,
PartialOrd,
Eq,
PartialEq,
Hash,
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
Pod,
Zeroable,
)]
#[repr(C)]
pub struct ZoomLevel(u8);
impl ZoomLevel {
@ -290,7 +307,22 @@ impl From<(u32, u32, ZoomLevel)> for TileCoords {
/// # Coordinate System Origin
///
/// The origin of the coordinate system is in the upper-left corner.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize)]
// FIXME: does Zeroable make sense?
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Default,
Serialize,
Deserialize,
Zeroable,
)]
#[repr(C)]
pub struct WorldTileCoords {
pub x: i32,
pub y: i32,

View File

@ -8,7 +8,7 @@ use crate::{HttpClient, MapWindowConfig, Scheduler};
pub trait Environment: 'static {
type MapWindowConfig: MapWindowConfig;
type AsyncProcedureCall: AsyncProcedureCall<Self::Transferables>;
type AsyncProcedureCall: AsyncProcedureCall<Self::Transferables, Self::HttpClient>;
type Scheduler: Scheduler;
type HttpClient: HttpClient;

View File

@ -13,6 +13,8 @@ use tokio::{runtime::Handle, task};
use wgpu::{BufferAsyncError, BufferSlice};
use crate::environment::DefaultTransferables;
use crate::io::apc::AsyncProcedureCall;
use crate::io::transferables::Transferables;
use crate::platform::apc::TokioAsyncProcedureCall;
use crate::{
context::{MapContext, ViewState},
@ -62,17 +64,26 @@ impl MapWindow for HeadlessMapWindow {
}
}
pub struct HeadlessEnvironment<S: Scheduler, HC: HttpClient> {
pub struct HeadlessEnvironment<
S: Scheduler,
HC: HttpClient,
T: Transferables,
APC: AsyncProcedureCall<T, HC>,
> {
phantom_s: PhantomData<S>,
phantom_hc: PhantomData<HC>,
phantom_t: PhantomData<T>,
phantom_apc: PhantomData<APC>,
}
impl<S: Scheduler, HC: HttpClient> Environment for HeadlessEnvironment<S, HC> {
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
for HeadlessEnvironment<S, HC, T, APC>
{
type MapWindowConfig = HeadlessMapWindowConfig;
type AsyncProcedureCall = TokioAsyncProcedureCall<DefaultTransferables>;
type AsyncProcedureCall = APC;
type Scheduler = S;
type HttpClient = HC;
type Transferables = DefaultTransferables;
type Transferables = T;
}
pub struct HeadlessMap<E: Environment> {
@ -329,9 +340,9 @@ pub mod utils {
) {
self.layers.push(StoredLayer::TessellatedLayer {
coords: *coords,
layer_name: layer_data.name,
buffer,
feature_indices,
layer_data,
})
}
}

View File

@ -1,7 +1,9 @@
use crate::coords::WorldTileCoords;
use crate::io::source_client::{HttpSourceClient, SourceClient};
use crate::io::transferables::Transferables;
use crate::Environment;
use serde::Serialize;
use crate::io::TileRequest;
use crate::{Environment, HttpClient};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
@ -16,23 +18,29 @@ pub enum Transferable<T: Transferables> {
TessellatedLayer(T::TessellatedLayer),
}
pub trait Context<T: Transferables> {
fn send(&self, data: Transferable<T>);
#[derive(Clone, Serialize, Deserialize)]
pub enum Input {
TileRequest(TileRequest),
}
pub type AsyncProcedure<I, C> =
fn(input: I, context: C) -> Pin<Box<dyn Future<Output = ()> + Send>>;
pub trait Context<T: Transferables, HC: HttpClient> {
fn send(&self, data: Transferable<T>);
pub trait AsyncProcedureCall<T: Transferables> {
type Context: Context<T> + Send;
fn source_client(&self) -> &SourceClient<HC>;
}
fn new() -> Self;
pub type AsyncProcedure<T, HC> =
fn(input: Input, context: Box<dyn Context<T, HC>>) -> Pin<Box<dyn Future<Output = ()>>>;
fn receive(&self) -> Option<Transferable<T>>;
pub trait AsyncProcedureCall<T: Transferables, HC: HttpClient>: 'static {
type Context: Context<T, HC> + Send;
fn schedule<I: Send + Serialize + 'static>(
fn receive(&mut self) -> Option<Box<Transferable<T>>>; // FIXME remove box
fn schedule(
&self,
input: I,
procedure: AsyncProcedure<I, Self::Context>,
input: Input,
procedure: AsyncProcedure<T, HC>,
http_client: HttpSourceClient<HC>,
);
}

View File

@ -3,7 +3,7 @@
use std::{collections::HashSet, fmt};
use crate::coords::WorldTileCoords;
use serde::Serialize;
use serde::{Deserialize, Serialize};
pub mod apc;
pub mod geometry_index;
@ -19,7 +19,7 @@ pub mod transferables;
pub use geozero::mvt::tile::Layer as RawLayer;
/// A request for a tile at the given coordinates and in the given layers.
#[derive(Clone, Serialize)]
#[derive(Clone, Serialize, Deserialize)]
pub struct TileRequest {
pub coords: WorldTileCoords,
pub layers: HashSet<String>,

View File

@ -23,3 +23,14 @@ pub trait Scheduler: 'static {
where
T: Future<Output = ()> + 'static;
}
pub struct NopScheduler;
impl Scheduler for NopScheduler {
fn schedule<T>(&self, future_factory: impl FnOnce() -> T + Send + 'static) -> Result<(), Error>
where
T: Future<Output = ()> + 'static,
{
Err(Error::Schedule)
}
}

View File

@ -18,10 +18,10 @@ pub enum StoredLayer {
},
TessellatedLayer {
coords: WorldTileCoords,
layer_name: String,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
/// Holds for each feature the count of indices.
feature_indices: Vec<u32>,
layer_data: tile::Layer,
},
}
@ -36,7 +36,7 @@ impl StoredLayer {
pub fn layer_name(&self) -> &str {
match self {
StoredLayer::UnavailableLayer { layer_name, .. } => layer_name.as_str(),
StoredLayer::TessellatedLayer { layer_data, .. } => &layer_data.name,
StoredLayer::TessellatedLayer { layer_name, .. } => layer_name.as_str(),
}
}
}

View File

@ -64,7 +64,7 @@ pub struct DefaultTessellatedLayer {
pub buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
/// Holds for each feature the count of indices.
pub feature_indices: Vec<u32>,
pub layer_data: tile::Layer,
pub layer_data: Layer, // FIXME: Introduce a better structure for this
}
impl TessellatedLayer for DefaultTessellatedLayer {
@ -85,9 +85,9 @@ impl TessellatedLayer for DefaultTessellatedLayer {
fn to_stored_layer(self) -> StoredLayer {
StoredLayer::TessellatedLayer {
coords: self.coords,
layer_name: self.layer_data.name,
buffer: self.buffer,
feature_indices: self.feature_indices,
layer_data: self.layer_data,
}
}
}

View File

@ -27,6 +27,9 @@ use crate::{
style::Style,
window::{EventLoop, HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
};
use std::borrow::{Borrow, BorrowMut};
use std::cell::RefCell;
use std::rc::Rc;
pub mod context;
pub mod coords;
@ -56,11 +59,14 @@ pub(crate) mod tessellation;
pub mod environment;
pub use geozero::mvt::tile;
/// The [`Map`] defines the public interface of the map renderer.
// DO NOT IMPLEMENT INTERNALS ON THIS STRUCT.
pub struct Map<E: Environment> {
map_schedule: InteractiveMapSchedule<E>,
window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
// FIXME: Avoid RefCell, change ownership model!
map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
window: RefCell<Option<<E::MapWindowConfig as MapWindowConfig>::MapWindow>>,
}
impl<E: Environment> Map<E>
@ -68,7 +74,7 @@ where
<E::MapWindowConfig as MapWindowConfig>::MapWindow: EventLoop<E>,
{
/// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
pub fn run(self) {
pub fn run(&self) {
self.run_with_optionally_max_frames(None);
}
@ -77,7 +83,7 @@ where
/// # Arguments
///
/// * `max_frames` - Maximum number of frames per second.
pub fn run_with_max_frames(self, max_frames: u64) {
pub fn run_with_max_frames(&self, max_frames: u64) {
self.run_with_optionally_max_frames(Some(max_frames));
}
@ -86,22 +92,27 @@ where
/// # Arguments
///
/// * `max_frames` - Optional maximum number of frames per second.
pub fn run_with_optionally_max_frames(self, max_frames: Option<u64>) {
self.window.run(self.map_schedule, max_frames);
pub fn run_with_optionally_max_frames(&self, max_frames: Option<u64>) {
self.window
.borrow_mut()
.take()
.unwrap()
.run(self.map_schedule.clone(), max_frames);
}
pub fn map_schedule(&self) -> &InteractiveMapSchedule<E> {
&self.map_schedule
pub fn map_schedule(&self) -> Rc<RefCell<InteractiveMapSchedule<E>>> {
self.map_schedule.clone()
}
pub fn map_schedule_mut(&mut self) -> &mut InteractiveMapSchedule<E> {
/* pub fn map_schedule_mut(&mut self) -> &mut InteractiveMapSchedule<E> {
&mut self.map_schedule
}
}*/
}
/// Stores the map configuration before the map's state has been fully initialized.
pub struct UninitializedMap<E: Environment> {
scheduler: E::Scheduler,
apc: E::AsyncProcedureCall,
http_client: E::HttpClient,
style: Style,
@ -131,17 +142,18 @@ where
.await
.ok();
Map {
map_schedule: InteractiveMapSchedule::new(
map_schedule: Rc::new(RefCell::new(InteractiveMapSchedule::new(
self.map_window_config,
window_size,
renderer,
self.scheduler,
self.apc,
self.http_client,
self.style,
self.wgpu_settings,
self.renderer_settings,
),
window,
))),
window: RefCell::new(Some(window)),
}
}
}
@ -175,6 +187,7 @@ impl<E: Environment> UninitializedMap<E> {
pub struct MapBuilder<E: Environment> {
scheduler: Option<E::Scheduler>,
apc: Option<E::AsyncProcedureCall>,
http_client: Option<E::HttpClient>,
style: Option<Style>,
@ -187,6 +200,7 @@ impl<E: Environment> MapBuilder<E> {
pub fn new() -> Self {
Self {
scheduler: None,
apc: None,
http_client: None,
style: None,
map_window_config: None,
@ -215,6 +229,11 @@ impl<E: Environment> MapBuilder<E> {
self
}
pub fn with_apc(mut self, apc: E::AsyncProcedureCall) -> Self {
self.apc = Some(apc);
self
}
pub fn with_http_client(mut self, http_client: E::HttpClient) -> Self {
self.http_client = Some(http_client);
self
@ -227,13 +246,11 @@ impl<E: Environment> MapBuilder<E> {
/// Builds the UninitializedMap with the given configuration.
pub fn build(self) -> UninitializedMap<E> {
let scheduler = self.scheduler.unwrap();
let style = self.style.unwrap_or_default();
UninitializedMap {
scheduler,
scheduler: self.scheduler.unwrap(),
apc: self.apc.unwrap(),
http_client: self.http_client.unwrap(),
style,
style: self.style.unwrap_or_default(),
wgpu_settings: self.wgpu_settings.unwrap_or_default(),
renderer_settings: self.renderer_settings.unwrap_or_default(),
map_window_config: self.map_window_config.unwrap(),

View File

@ -1,3 +1,5 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::{marker::PhantomData, mem};
use crate::{
@ -21,6 +23,9 @@ use crate::{
pub struct InteractiveMapSchedule<E: Environment> {
map_window_config: E::MapWindowConfig,
// FIXME: avoid RefCell, change ownership model
pub apc: Rc<RefCell<E::AsyncProcedureCall>>,
map_context: EventuallyMapContext,
schedule: Schedule,
@ -34,6 +39,7 @@ impl<E: Environment> InteractiveMapSchedule<E> {
window_size: WindowSize,
renderer: Option<Renderer>,
scheduler: E::Scheduler,
apc: E::AsyncProcedureCall,
http_client: E::HttpClient,
style: Style,
wgpu_settings: WgpuSettings,
@ -50,14 +56,17 @@ impl<E: Environment> InteractiveMapSchedule<E> {
let tile_repository = TileRepository::new();
let mut schedule = Schedule::default();
let apc = Rc::new(RefCell::new(apc)); // TODO: remove refcell, rc
let http_source_client: HttpSourceClient<E::HttpClient> =
HttpSourceClient::new(http_client);
register_stages::<E>(&mut schedule, http_source_client, Box::new(scheduler));
register_stages::<E>(&mut schedule, http_source_client, apc.clone());
let graph = create_default_render_graph().unwrap();
register_default_render_stages(graph, &mut schedule);
Self {
apc,
map_window_config,
map_context: match renderer {
None => EventuallyMapContext::Premature(PrematureMapContext {
@ -116,6 +125,10 @@ impl<E: Environment> InteractiveMapSchedule<E> {
_ => panic!("should not happen"),
}
}
pub fn apc(&self) -> &Rc<RefCell<E::AsyncProcedureCall>> {
&self.apc
}
}
impl<E: Environment> InteractiveMapSchedule<E>

View File

@ -1,18 +1,22 @@
use crate::environment::DefaultTransferables;
use crate::io::apc::{AsyncProcedure, AsyncProcedureCall, Context, Transferable};
use crate::io::apc::{AsyncProcedure, AsyncProcedureCall, Context, Input, Transferable};
use crate::io::source_client::{HttpSourceClient, SourceClient};
use crate::io::transferables::Transferables;
use crate::Environment;
use crate::{Environment, HttpClient};
use serde::Serialize;
use std::collections::HashMap;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use tokio_util::task::LocalPoolHandle;
// FIXME: Make this generic using the Schedule
#[derive(Clone)]
pub struct TokioContext<T: Transferables> {
pub struct TokioContext<T: Transferables, HC: HttpClient> {
sender: Sender<Transferable<T>>,
source_client: SourceClient<HC>,
}
impl<T: Transferables> Context<T> for TokioContext<T>
impl<T: Transferables, HC: HttpClient> Context<T, HC> for TokioContext<T, HC>
where
T: Clone,
{
@ -20,46 +24,55 @@ where
self.sender.send(data).unwrap();
log::debug!("sent");
}
fn source_client(&self) -> &SourceClient<HC> {
&self.source_client
}
}
pub struct TokioAsyncProcedureCall<T: Transferables> {
channel: (Sender<Transferable<T>>, Receiver<Transferable<T>>),
pub struct TokioAsyncProcedureCall {
channel: (
Sender<Transferable<DefaultTransferables>>,
Receiver<Transferable<DefaultTransferables>>,
),
pool: LocalPoolHandle,
}
impl<T: Transferables> TokioAsyncProcedureCall<T> {
impl TokioAsyncProcedureCall {
pub fn new() -> Self {
Self {
channel: mpsc::channel(),
pool: LocalPoolHandle::new(4),
}
}
}
impl<T: Transferables> AsyncProcedureCall<T> for TokioAsyncProcedureCall<T>
where
T: Clone,
{
type Context = TokioContext<T>;
impl<HC: HttpClient> AsyncProcedureCall<DefaultTransferables, HC> for TokioAsyncProcedureCall {
type Context = TokioContext<DefaultTransferables, HC>;
fn new() -> Self {
Self {
channel: mpsc::channel(),
}
}
fn receive(&self) -> Option<Transferable<T>> {
fn receive(&mut self) -> Option<Box<Transferable<DefaultTransferables>>> {
let transferred = self.channel.1.try_recv().ok()?;
log::debug!("received");
Some(transferred)
Some(Box::new(transferred))
}
fn schedule<I: Serialize + Send + 'static>(
fn schedule(
&self,
input: I,
procedure: AsyncProcedure<I, TokioContext<T>>,
input: Input,
procedure: AsyncProcedure<DefaultTransferables, HC>,
http_client: HttpSourceClient<HC>,
) {
let sender = self.channel.0.clone();
tokio::task::spawn(async move {
(procedure)(input, TokioContext { sender }).await;
self.pool.spawn_pinned(move || async move {
(procedure)(
input,
Box::new(TokioContext {
sender,
source_client: SourceClient::Http(http_client),
}),
)
.await;
});
}
}

View File

@ -187,7 +187,6 @@ impl UploadStage {
StoredLayer::TessellatedLayer {
coords,
feature_indices,
layer_data,
buffer,
..
} => {
@ -197,17 +196,16 @@ impl UploadStage {
);
let guard = allocate_feature_metadata.enter();
let feature_metadata = layer_data
.features
.iter()
.enumerate()
.flat_map(|(i, _feature)| {
iter::repeat(ShaderFeatureStyle {
color: color.unwrap(),
let feature_metadata =
(0..feature_indices.len()) // FIXME: Iterate over actual featrues
.enumerate()
.flat_map(|(i, _feature)| {
iter::repeat(ShaderFeatureStyle {
color: color.unwrap(),
})
.take(feature_indices[i] as usize)
})
.take(feature_indices[i] as usize)
})
.collect::<Vec<_>>();
.collect::<Vec<_>>();
drop(guard);
tracing::trace!("Allocating geometry at {}", &coords);

View File

@ -1,5 +1,6 @@
//! [Stages](Stage) for requesting and preparing data
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{mpsc, Arc, Mutex};
@ -13,7 +14,6 @@ use crate::io::transferables::{
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, TessellatedLayer,
TileTessellated, UnavailableLayer,
};
use crate::platform::apc::{TokioAsyncProcedureCall, TokioContext};
use crate::{
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
error::Error,
@ -38,10 +38,8 @@ mod request_stage;
pub fn register_stages<E: Environment>(
schedule: &mut Schedule,
http_source_client: HttpSourceClient<E::HttpClient>,
scheduler: Box<E::Scheduler>,
apc: Rc<RefCell<E::AsyncProcedureCall>>,
) {
let apc = Rc::new(E::AsyncProcedureCall::new());
schedule.add_stage(
"request",
RequestStage::<E>::new(http_source_client, apc.clone()),
@ -50,7 +48,7 @@ pub fn register_stages<E: Environment>(
}
pub struct HeadedPipelineProcessor<E: Environment> {
context: <E::AsyncProcedureCall as AsyncProcedureCall<E::Transferables>>::Context,
context: Box<dyn Context<E::Transferables, E::HttpClient>>, // TODO: remove box
}
impl<E: Environment> PipelineProcessor for HeadedPipelineProcessor<E> {
@ -76,7 +74,6 @@ impl<E: Environment> PipelineProcessor for HeadedPipelineProcessor<E> {
feature_indices: Vec<u32>,
layer_data: tile::Layer,
) {
log::info!("layer_tesselation_finished");
self.context.send(Transferable::TessellatedLayer(
<E::Transferables as Transferables>::TessellatedLayer::new(
*coords,
@ -99,6 +96,7 @@ impl<E: Environment> PipelineProcessor for HeadedPipelineProcessor<E> {
}
}
// FIXME: clean this up
///// Stores and provides access to the thread safe data shared between the schedulers.
//[derive(Clone)]
//pub struct SharedThreadState {

View File

@ -3,14 +3,17 @@
use crate::io::apc::{AsyncProcedureCall, Transferable};
use crate::io::transferables::{TessellatedLayer, TileTessellated, UnavailableLayer};
use crate::{context::MapContext, io::tile_repository::StoredLayer, schedule::Stage, Environment};
use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
pub struct PopulateTileStore<E: Environment> {
apc: Rc<E::AsyncProcedureCall>,
apc: Rc<RefCell<E::AsyncProcedureCall>>,
}
impl<E: Environment> PopulateTileStore<E> {
pub fn new(apc: Rc<E::AsyncProcedureCall>) -> Self {
pub fn new(apc: Rc<RefCell<E::AsyncProcedureCall>>) -> Self {
Self { apc }
}
}
@ -22,30 +25,39 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
tile_repository, ..
}: &mut MapContext,
) {
if let Some(result) = self.apc.receive() {
match result {
Transferable::TileTessellated(tranferred) => {
let coords = tranferred.coords();
tile_repository.success(coords);
tracing::trace!("Tile at {} finished loading", coords);
}
Transferable::UnavailableLayer(tranferred) => {
let layer: StoredLayer = tranferred.to_stored_layer();
tracing::debug!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
tile_repository.put_tessellated_layer(layer);
}
Transferable::TessellatedLayer(data) => {
let layer: StoredLayer = data.to_stored_layer();
tracing::debug!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
tile_repository.put_tessellated_layer(layer);
if let Ok(mut apc) = self.apc.deref().try_borrow_mut() {
if let Some(result) = apc.receive() {
match *result {
Transferable::TileTessellated(tranferred) => {
let coords = tranferred.coords();
tile_repository.success(coords);
tracing::trace!("Tile at {} finished loading", coords);
log::warn!("Tile at {} finished loading", coords);
}
// FIXME: deduplicate
Transferable::UnavailableLayer(tranferred) => {
let layer: StoredLayer = tranferred.to_stored_layer();
tracing::debug!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
tile_repository.put_tessellated_layer(layer);
}
Transferable::TessellatedLayer(data) => {
let layer: StoredLayer = data.to_stored_layer();
tracing::debug!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
log::warn!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
tile_repository.put_tessellated_layer(layer);
}
}
}
}

View File

@ -1,11 +1,10 @@
//! Requests tiles which are currently in view
use crate::coords::ZoomLevel;
use crate::io::apc::AsyncProcedureCall;
use crate::io::apc::{AsyncProcedureCall, Context, Input};
use crate::io::pipeline::PipelineContext;
use crate::io::pipeline::Processable;
use crate::io::tile_pipelines::build_vector_tile_pipeline;
use crate::platform::http_client::ReqwestHttpClient;
use crate::stages::HeadedPipelineProcessor;
use crate::{
context::MapContext,
@ -19,22 +18,25 @@ use crate::{
schedule::Stage,
Environment, HttpClient, Scheduler, Style,
};
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::future::Future;
use std::ops::Deref;
use std::pin::Pin;
use std::process::Output;
use std::rc::Rc;
use std::str::FromStr;
pub struct RequestStage<E: Environment> {
apc: Rc<E::AsyncProcedureCall>,
apc: Rc<RefCell<E::AsyncProcedureCall>>,
http_source_client: HttpSourceClient<E::HttpClient>,
}
impl<E: Environment> RequestStage<E> {
pub fn new(
http_source_client: HttpSourceClient<E::HttpClient>,
apc: Rc<E::AsyncProcedureCall>,
apc: Rc<RefCell<E::AsyncProcedureCall>>,
) -> Self {
Self {
apc,
@ -68,13 +70,19 @@ impl<E: Environment> Stage for RequestStage<E> {
}
pub fn schedule<E: Environment>(
input: TileRequest,
context: <E::AsyncProcedureCall as AsyncProcedureCall<E::Transferables>>::Context,
) -> Pin<Box<(dyn Future<Output = ()> + Send + 'static)>> {
input: Input,
context: Box<dyn Context<E::Transferables, E::HttpClient>>, // TODO: remove box
) -> Pin<Box<(dyn Future<Output = ()> + 'static)>> {
// FIXME: improve input handling
let input = match input {
Input::TileRequest(input) => Some(input),
_ => None,
}
.unwrap();
Box::pin(async move {
let coords = input.coords;
let client = SourceClient::Http(HttpSourceClient::new(ReqwestHttpClient::new(None)));
let request_id = 0;
let client = context.source_client();
match client.fetch(&coords).await {
Ok(data) => {
@ -131,12 +139,13 @@ impl<E: Environment> RequestStage<E> {
tile_repository.create_tile(coords);
tracing::info!("new tile request: {}", &coords);
self.apc.schedule(
TileRequest {
self.apc.deref().borrow().schedule(
Input::TileRequest(TileRequest {
coords: *coords,
layers: layers.clone(),
},
}),
schedule::<E>,
self.http_source_client.clone(),
);
}

View File

@ -56,6 +56,21 @@ impl<V, I> OverAlignedVertexBuffer<V, I> {
usable_indices: 0,
}
}
pub fn from_slices(vertices: &[V], indices: &[I], usable_indices: u32) -> Self
where
V: Copy,
I: Copy,
{
// FIXME, make this fn not needed
let mut buffers = VertexBuffers::with_capacity(0, 0);
buffers.vertices = Vec::from(vertices);
buffers.indices = Vec::from(indices);
Self {
buffer: buffers,
usable_indices,
}
}
}
impl<V: Pod, I: Pod> From<VertexBuffers<V, I>> for OverAlignedVertexBuffer<V, I> {

View File

@ -1,6 +1,8 @@
//! Utilities for the window system.
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use std::cell::RefCell;
use std::rc::Rc;
use crate::{Environment, HttpClient, InteractiveMapSchedule};
@ -28,7 +30,8 @@ pub trait MapWindowConfig: 'static {
/// The event loop is responsible for processing events and propagating them to the map renderer.
/// Only non-headless windows use an [`EventLoop`].
pub trait EventLoop<E: Environment> {
fn run(self, map_schedule: InteractiveMapSchedule<E>, max_frames: Option<u64>);
// FIXME: Avoid Rc, change ownership model
fn run(self, map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>, max_frames: Option<u64>);
}
/// Window size with a width and an height in pixels.

View File

@ -38,6 +38,10 @@ wasm-bindgen = "0.2.81"
wasm-bindgen-futures = "0.4.31"
console_log = { version = "0.2.0", features = ["color"] }
tracing-wasm = { version = "0.2.1", optional = true } # FIXME: Low quality dependency
serde = "1.0.144" # FIXME: Remove
serde_json = "1.0.85"
bytemuck = "1.12.1"
bytemuck_derive = "1.2.1"
[dev-dependencies]
wasm-bindgen-test = "0.3.31"

View File

@ -1,10 +1,10 @@
import {create_pool_scheduler, run} from "../wasm/maplibre"
import {run} from "../wasm/maplibre"
import {Spector} from "spectorjs"
import {checkRequirements, checkWasmFeatures} from "../browser";
import init from "../wasm/maplibre";
import {preventDefaultTouchActions} from "../canvas";
// @ts-ignore esbuild plugin is handling this
import PoolWorker from './pool.worker.js';
import PoolWorker from './multithreaded-pool.worker.js';
const initializeSharedModule = async (wasmPath) => {
let MEMORY_PAGES = 16 * 1024
@ -33,11 +33,9 @@ export const startMapLibre = async (wasmPath: string | undefined, workerPath: st
await initializeSharedModule(wasmPath);
const schedulerPtr = create_pool_scheduler(() => {
await run(() => {
return workerPath ? new Worker(workerPath, {
type: 'module'
}) : PoolWorker();
})
await run(schedulerPtr)
}

View File

@ -1,11 +1,7 @@
import init, {worker_entry} from "../wasm/maplibre"
const initializeExisting = async (module: string, memory?: string) => {
await init(module, memory)
}
import init, {sync_worker_entry} from "../wasm/maplibre"
onmessage = async message => {
const initialised = initializeExisting(message.data[0], message.data[1]).catch(err => {
const initialised = init(message.data[0], message.data[1]).catch(err => {
// Propagate to main `onerror`:
setTimeout(() => {
throw err;
@ -17,6 +13,6 @@ onmessage = async message => {
self.onmessage = async message => {
// This will queue further commands up until the module is fully initialised:
await initialised;
await worker_entry(message.data);
await sync_worker_entry(message.data);
};
}

View File

@ -1,4 +1,4 @@
import init, {create_scheduler, run} from "../wasm/maplibre"
import init, {run, create_map, clone_map, unsync_main_entry} from "../wasm/maplibre"
import {Spector} from "spectorjs"
import {checkRequirements, checkWasmFeatures} from "../browser";
import {preventDefaultTouchActions} from "../canvas";
@ -22,31 +22,29 @@ export const startMapLibre = async (wasmPath: string | undefined, workerPath: st
}
preventDefaultTouchActions();
let MEMORY_PAGES = 16 * 1024
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY_PAGES, shared: false})
await init(wasmPath, memory);
await init(wasmPath);
let callback = [undefined]
const schedulerPtr = create_scheduler(() => {
let map = await create_map(() => {
let worker = workerPath ? new Worker(workerPath, {
type: 'module'
}) : PoolWorker();
let memories = []
worker.onmessage = (message) => {
console.warn("new message");
//let uint8Array = new Uint8Array(message.data[0], message.data[1]);
memories.push(message.data[0])
console.warn(memories.map(v => new Uint8Array(v, message.data[1])[0]));
console.warn(memories[0] == memories[1]);
worker.postMessage("test")
worker.onmessage = (message) => {
callback[0](message)
}
return worker;
})
await run(schedulerPtr)
let clonedMap = clone_map(map)
callback[0] = (message) => {
unsync_main_entry(clonedMap, message.data[0], new Uint8Array(message.data[1]))
}
run(map)
}

View File

@ -1,11 +1,13 @@
import init, {worker_entry} from "../wasm/maplibre"
import init, {unsync_worker_entry} from "../wasm/maplibre"
const initializeExisting = async (module: string) => {
await init(module)
}
onmessage = async message => {
const initialised = initializeExisting(message.data[0]).catch(err => {
let MEMORY_PAGES = 16 * 1024
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY_PAGES, shared: false})
const initialised = await init(message.data[0], memory).catch(err => {
// Propagate to main `onerror`:
setTimeout(() => {
throw err;
@ -17,6 +19,8 @@ onmessage = async message => {
self.onmessage = async message => {
// This will queue further commands up until the module is fully initialised:
await initialised;
await worker_entry();
let procedure_ptr = message.data[0];
let input = message.data[1];
await unsync_worker_entry(procedure_ptr, input);
};
}

View File

@ -1,11 +1,20 @@
use std::panic;
#![feature(allocator_api, new_uninit)]
use maplibre::{io::scheduler::Scheduler, MapBuilder};
use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
use std::{mem, panic};
use maplibre::io::scheduler::NopScheduler;
use maplibre::{Map, MapBuilder};
use maplibre_winit::winit::{WinitEnvironment, WinitMapWindowConfig};
use wasm_bindgen::prelude::*;
use crate::platform::unsync::UnsyncScheduler;
use crate::platform::{http_client::WHATWGFetchHttpClient, NopScheduler};
use crate::platform::http_client::WHATWGFetchHttpClient;
use crate::platform::unsync::apc::PassingAsyncProcedureCall;
use crate::platform::unsync::transferables::LinearTransferables;
use crate::platform::AsyncProcedureCall;
mod error;
mod platform;
@ -13,8 +22,6 @@ mod platform;
#[cfg(not(target_arch = "wasm32"))]
compile_error!("web works only on wasm32.");
type CurrentScheduler = UnsyncScheduler;
#[cfg(feature = "trace")]
fn enable_tracing() {
use tracing_subscriber::{layer::SubscriberExt, Registry};
@ -39,27 +46,52 @@ pub fn wasm_bindgen_start() {
enable_tracing();
}
pub type MapType = Map<
WinitEnvironment<
NopScheduler,
WHATWGFetchHttpClient,
LinearTransferables,
PassingAsyncProcedureCall,
>,
>;
#[wasm_bindgen]
pub fn create_scheduler(new_worker: js_sys::Function) -> *mut CurrentScheduler {
let scheduler = UnsyncScheduler::new(new_worker);
Box::into_raw(Box::new(scheduler))
pub async fn create_map(new_worker: js_sys::Function) -> u32 {
#[cfg(target_feature = "atomics")]
let scheduler = platform::Scheduler::new(new_worker.clone());
#[cfg(target_feature = "atomics")]
let apc = AsyncProcedureCall::new(scheduler);
#[cfg(not(target_feature = "atomics"))]
let apc = AsyncProcedureCall::new(new_worker);
// Either call forget or the main loop to keep worker loop alive
let map: MapType = MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(WHATWGFetchHttpClient::new())
.with_scheduler(NopScheduler)
.with_apc(apc)
.build()
.initialize()
.await;
Rc::into_raw(Rc::new(RefCell::new(map))) as u32
}
#[wasm_bindgen]
pub async fn run(scheduler_ptr: *mut CurrentScheduler) {
let scheduler = unsafe { Box::from_raw(scheduler_ptr) };
pub fn clone_map(map_ptr: *const RefCell<MapType>) -> *const RefCell<MapType> {
let mut map = unsafe { Rc::from_raw(map_ptr) };
let rc = map.clone();
let cloned = Rc::into_raw(rc);
mem::forget(map);
cloned
}
// Either call forget or the main loop to keep worker loop alive
MapBuilder::<WinitEnvironment<_, _>>::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(WHATWGFetchHttpClient::new())
.with_scheduler(*scheduler)
.build()
.initialize()
.await
.run();
#[wasm_bindgen]
pub fn run(map_ptr: *const RefCell<MapType>) {
let mut map = unsafe { Rc::from_raw(map_ptr) };
// std::mem::forget(scheduler);
map.deref().borrow().run();
}
#[cfg(test)]

View File

@ -1,21 +1,15 @@
use maplibre::error::Error;
use maplibre::io::scheduler::Scheduler;
use std::future::Future;
pub mod http_client;
#[cfg(target_feature = "atomics")]
pub mod sync;
#[cfg(target_feature = "atomics")]
type Scheduler = sync::pool_scheduler::WebWorkerPoolScheduler;
#[cfg(target_feature = "atomics")]
pub type AsyncProcedureCall = sync::apc::AtomicAsyncProcedureCall;
#[cfg(not(target_feature = "atomics"))]
pub mod unsync;
pub struct NopScheduler;
impl Scheduler for NopScheduler {
fn schedule<T>(&self, future_factory: impl FnOnce() -> T + Send + 'static) -> Result<(), Error>
where
T: Future<Output = ()> + 'static,
{
Err(Error::Schedule)
}
}
#[cfg(not(target_feature = "atomics"))]
pub type AsyncProcedureCall = unsync::apc::PassingAsyncProcedureCall;

View File

@ -0,0 +1,78 @@
use crate::platform::sync::pool_scheduler::WebWorkerPoolScheduler;
use maplibre::environment::DefaultTransferables;
use maplibre::environment::Environment;
use maplibre::io::apc::{AsyncProcedure, AsyncProcedureCall, Context, Transferable};
use maplibre::io::scheduler::Scheduler;
use maplibre::io::source_client::{HttpClient, HttpSourceClient, SourceClient};
use maplibre::io::transferables::Transferables;
use serde::Serialize;
use std::collections::HashMap;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
#[derive(Clone)]
pub struct AtomicContext<T: Transferables, HC: HttpClient> {
sender: Sender<Transferable<T>>,
source_client: SourceClient<HC>,
}
impl<T: Transferables, HC: HttpClient> Context<T, HC> for AtomicContext<T, HC>
where
T: Clone,
{
fn send(&self, data: Transferable<T>) {
self.sender.send(data).unwrap();
}
fn source_client(&self) -> &SourceClient<HC> {
&self.source_client
}
}
pub struct AtomicAsyncProcedureCall {
channel: (
Sender<Transferable<DefaultTransferables>>,
Receiver<Transferable<DefaultTransferables>>,
),
scheduler: WebWorkerPoolScheduler,
}
impl AtomicAsyncProcedureCall {
pub fn new(scheduler: WebWorkerPoolScheduler) -> Self {
Self {
channel: mpsc::channel(),
scheduler,
}
}
}
impl<HC: HttpClient> AsyncProcedureCall<DefaultTransferables, HC> for AtomicAsyncProcedureCall {
type Context = AtomicContext<DefaultTransferables, HC>;
fn receive(&self) -> Option<Transferable<DefaultTransferables>> {
let transferred = self.channel.1.try_recv().ok()?;
Some(transferred)
}
fn schedule<I: Serialize + Send + 'static>(
&self,
input: I,
procedure: AsyncProcedure<I, AtomicContext<DefaultTransferables, HC>>,
http_client: HttpSourceClient<HC>,
) {
let sender = self.channel.0.clone();
self.scheduler
.schedule(move || async move {
(procedure)(
input,
AtomicContext {
sender,
source_client: SourceClient::Http(http_client),
},
)
.await;
})
.unwrap();
}
}

View File

@ -1,2 +1,3 @@
pub mod apc;
pub mod pool;
pub mod pool_scheduler;

View File

@ -148,7 +148,7 @@ impl PoolState {
/// Entry point invoked by the worker.
#[wasm_bindgen]
pub async fn worker_entry(ptr: u32) -> Result<(), JsValue> {
pub async fn sync_worker_entry(ptr: u32) -> Result<(), JsValue> {
let ptr = unsafe { Box::from_raw(ptr as *mut Work) };
JsFuture::from((ptr.func)()).await?;
Ok(())

View File

@ -0,0 +1,186 @@
use crate::platform::unsync::transferables::{
InnerData, LinearTessellatedLayer, LinearTransferables,
};
use crate::{MapType, WHATWGFetchHttpClient};
use js_sys::Uint8Array;
use maplibre::environment::Environment;
use maplibre::io::apc::{AsyncProcedure, AsyncProcedureCall, Context, Input, Transferable};
use maplibre::io::scheduler::Scheduler;
use maplibre::io::source_client::{HttpClient, HttpSourceClient, SourceClient};
use maplibre::io::transferables::Transferables;
use serde::Serialize;
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::mem;
use std::mem::{size_of, MaybeUninit};
use std::ops::Deref;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::{DedicatedWorkerGlobalScope, Worker};
#[derive(Clone)]
pub struct PassingContext<HC: HttpClient> {
source_client: SourceClient<HC>,
}
impl<HC: HttpClient> Context<LinearTransferables, HC> for PassingContext<HC> {
fn send(&self, data: Transferable<LinearTransferables>) {
// TODO: send back to main thread via postMessage
let (tag, serialized): (u32, &[u8]) = match &data {
Transferable::TileTessellated(data) => (1, bytemuck::bytes_of(data)),
Transferable::UnavailableLayer(data) => (2, bytemuck::bytes_of(data)),
Transferable::TessellatedLayer(data) => (3, bytemuck::bytes_of(data.data.as_ref())),
};
let serialized_array_buffer = js_sys::ArrayBuffer::new(serialized.len() as u32);
let serialized_array = js_sys::Uint8Array::new(&serialized_array_buffer);
unsafe {
serialized_array.set(&Uint8Array::view(serialized), 0);
}
let global = js_sys::global().unchecked_into::<DedicatedWorkerGlobalScope>();
let array = js_sys::Array::new();
array.push(&JsValue::from(tag));
array.push(&serialized_array_buffer);
global.post_message(&array).unwrap();
}
fn source_client(&self) -> &SourceClient<HC> {
&self.source_client
}
}
pub struct PassingAsyncProcedureCall {
new_worker: Box<dyn Fn() -> Worker>,
workers: Vec<Worker>,
received: Vec<Box<Transferable<LinearTransferables>>>,
}
impl PassingAsyncProcedureCall {
pub fn new(new_worker: js_sys::Function) -> Self {
let create_new_worker = Box::new(move || {
new_worker
.call0(&JsValue::undefined())
.unwrap()
.dyn_into::<Worker>()
.unwrap()
});
let worker = create_new_worker();
let array = js_sys::Array::new();
array.push(&wasm_bindgen::module());
worker.post_message(&array).unwrap();
Self {
new_worker: create_new_worker,
workers: vec![worker],
received: vec![],
}
}
}
impl<HC: HttpClient> AsyncProcedureCall<LinearTransferables, HC> for PassingAsyncProcedureCall {
type Context = PassingContext<HC>;
fn receive(&mut self) -> Option<Box<Transferable<LinearTransferables>>> {
self.received.pop()
}
fn schedule(
&self,
input: Input,
procedure: AsyncProcedure<LinearTransferables, HC>,
http_client: HttpSourceClient<HC>, // FIXME
) {
let procedure_ptr =
procedure as *mut AsyncProcedure<LinearTransferables, WHATWGFetchHttpClient> as u32; // TODO: is u32 fine?
let input = serde_json::to_string(&input).unwrap();
let array = js_sys::Array::new();
array.push(&JsValue::from(procedure_ptr));
array.push(&JsValue::from(input));
self.workers[0].post_message(&array).unwrap();
}
}
/// Entry point invoked by the worker.
#[wasm_bindgen]
pub async fn unsync_worker_entry(procedure_ptr: u32, input: String) -> Result<(), JsValue> {
log::info!("worker_entry unsync");
let procedure: AsyncProcedure<LinearTransferables, WHATWGFetchHttpClient> =
unsafe { std::mem::transmute(procedure_ptr) };
let input = serde_json::from_str::<Input>(&input).unwrap();
let context = PassingContext {
source_client: SourceClient::Http(HttpSourceClient::new(WHATWGFetchHttpClient::new())),
};
(procedure)(input, Box::new(context)).await;
Ok(())
}
/// Entry point invoked by the main thread.
#[wasm_bindgen]
pub fn unsync_main_entry(
map_ptr: *const RefCell<MapType>,
tag: u32,
data: Uint8Array,
) -> Result<(), JsValue> {
let mut map = unsafe { Rc::from_raw(map_ptr) };
let transferred = match tag {
3 => Some(Transferable::<LinearTransferables>::TessellatedLayer(
LinearTessellatedLayer {
data: unsafe {
let mut uninit = Box::<InnerData>::new_zeroed();
data.raw_copy_to_ptr(uninit.as_mut_ptr() as *mut u8);
let x = uninit.assume_init();
x
},
},
)),
1 => Some(Transferable::<LinearTransferables>::TileTessellated(
*bytemuck::from_bytes::<<LinearTransferables as Transferables>::TileTessellated>(
&data.to_vec(),
),
)),
2 => Some(Transferable::<LinearTransferables>::UnavailableLayer(
*bytemuck::from_bytes::<<LinearTransferables as Transferables>::UnavailableLayer>(
&data.to_vec(),
),
)),
_ => None,
}
.unwrap();
// FIXME: avoid this borrow mess
map.deref()
.borrow()
.map_schedule()
.deref()
.borrow()
.apc
.deref()
.borrow_mut()
.received
.push(Box::new(transferred)); // FIXME: remove box
mem::forget(map);
Ok(())
}

View File

@ -1,95 +1,2 @@
use js_sys::{ArrayBuffer, Uint8Array};
use maplibre::error::Error;
use maplibre::io::scheduler::Scheduler;
use std::borrow::{Borrow, BorrowMut};
use std::cell::RefCell;
use std::future::Future;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::{DedicatedWorkerGlobalScope, Worker};
pub struct UnsyncScheduler {
new_worker: Box<dyn Fn() -> Worker>,
workers: RefCell<Vec<Worker>>,
}
impl UnsyncScheduler {
pub fn new(new_worker: js_sys::Function) -> Self {
let create_new_worker = Box::new(move || {
new_worker
.call0(&JsValue::undefined())
.unwrap()
.dyn_into::<Worker>()
.unwrap()
});
let worker = create_new_worker();
let array = js_sys::Array::new();
array.push(&wasm_bindgen::module());
worker.post_message(&array).unwrap();
log::info!("new unsync");
Self {
new_worker: create_new_worker,
workers: RefCell::new(vec![worker]),
}
}
}
impl Scheduler for UnsyncScheduler {
fn schedule<T>(&self, future_factory: impl FnOnce() -> T + Send + 'static) -> Result<(), Error>
where
T: Future<Output = ()> + 'static,
{
self.workers.borrow()[0]
.post_message(&JsValue::undefined())
.unwrap();
Ok(())
}
}
thread_local! {
static VEC: RefCell<Vec<u8>> = RefCell::new(vec![165u8, 162, 145, 224, 111]);
}
/// Entry point invoked by the worker.
#[wasm_bindgen]
pub async fn worker_entry() -> Result<(), JsValue> {
log::info!("worker_entry unsync");
//let vec = vec![165u8, 162, 145, 224, 111];
VEC.with(|d| {
let mut ref_mut = d.borrow_mut();
ref_mut[0] += 1;
unsafe {
let uint8 = Uint8Array::view(&ref_mut);
let array_buffer = uint8.buffer();
log::info!(
"{}",
array_buffer
== wasm_bindgen::memory()
.dyn_into::<js_sys::WebAssembly::Memory>()
.unwrap()
.buffer()
.dyn_into::<ArrayBuffer>()
.unwrap()
);
let global = js_sys::global().unchecked_into::<DedicatedWorkerGlobalScope>();
let array = js_sys::Array::new();
array.push(&array_buffer);
array.push(&JsValue::from(uint8.byte_offset()));
global.post_message(&array).unwrap();
};
});
Ok(())
}
pub mod apc;
pub mod transferables;

View File

@ -0,0 +1,156 @@
use bytemuck::{TransparentWrapper, Zeroable};
use bytemuck_derive::{Pod, Zeroable};
use maplibre::benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer};
use maplibre::coords::WorldTileCoords;
use maplibre::io::tile_repository::StoredLayer;
use maplibre::io::transferables::{
TessellatedLayer, TileTessellated, Transferables, UnavailableLayer,
};
use maplibre::render::ShaderVertex;
use maplibre::tile::Layer;
// FIXME: properly do this!, fix this whole file
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct WrapperWorldTileCoords(WorldTileCoords);
unsafe impl TransparentWrapper<WorldTileCoords> for WrapperWorldTileCoords {}
unsafe impl bytemuck::Zeroable for WrapperWorldTileCoords {}
unsafe impl bytemuck::Pod for WrapperWorldTileCoords {}
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct LongVertexShader([ShaderVertex; 15000]);
unsafe impl TransparentWrapper<[ShaderVertex; 15000]> for LongVertexShader {}
unsafe impl bytemuck::Zeroable for LongVertexShader {}
unsafe impl bytemuck::Pod for LongVertexShader {}
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct LongIndices([IndexDataType; 40000]);
unsafe impl TransparentWrapper<[IndexDataType; 40000]> for LongIndices {}
unsafe impl bytemuck::Zeroable for LongIndices {}
unsafe impl bytemuck::Pod for LongIndices {}
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct LinearTileTessellated {
pub coords: WrapperWorldTileCoords,
}
impl TileTessellated for LinearTileTessellated {
fn new(coords: WorldTileCoords) -> Self {
Self {
coords: WrapperWorldTileCoords::wrap(coords),
}
}
fn coords(&self) -> &WorldTileCoords {
WrapperWorldTileCoords::peel_ref(&self.coords)
}
}
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct LinearUnavailableLayer {
pub coords: WrapperWorldTileCoords,
pub layer_name: [u8; 32],
}
impl UnavailableLayer for LinearUnavailableLayer {
fn new(coords: WorldTileCoords, layer_name: String) -> Self {
let mut new_layer_name = [0; 32];
new_layer_name[0..layer_name.len()].clone_from_slice(layer_name.as_bytes());
Self {
coords: WrapperWorldTileCoords::wrap(coords),
layer_name: new_layer_name,
}
}
fn to_stored_layer(self) -> StoredLayer {
StoredLayer::UnavailableLayer {
coords: WrapperWorldTileCoords::peel(self.coords),
layer_name: String::from_utf8(Vec::from(self.layer_name)).unwrap(),
}
}
}
#[derive(Copy, Clone, Pod, Zeroable, Debug)]
#[repr(C)]
pub struct InnerData {
pub coords: WrapperWorldTileCoords,
pub layer_name: [u8; 32],
pub layer_name_len: usize,
pub vertices: LongVertexShader,
pub vertices_len: usize,
pub indices: LongIndices,
pub indices_len: usize,
pub usable_indices: u32,
/// Holds for each feature the count of indices.
pub feature_indices: [u32; 2048],
pub feature_indices_len: usize,
}
#[derive(Clone)]
pub struct LinearTessellatedLayer {
pub data: Box<InnerData>,
}
impl TessellatedLayer for LinearTessellatedLayer {
fn new(
coords: WorldTileCoords,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
feature_indices: Vec<u32>,
layer_data: Layer,
) -> Self {
let mut data = Box::new(InnerData {
coords: WrapperWorldTileCoords::wrap(coords),
layer_name: [0; 32],
layer_name_len: layer_data.name.len(),
vertices: LongVertexShader::wrap([ShaderVertex::zeroed(); 15000]),
vertices_len: buffer.buffer.vertices.len(),
indices: LongIndices::wrap([IndexDataType::zeroed(); 40000]),
indices_len: buffer.buffer.indices.len(),
usable_indices: buffer.usable_indices,
feature_indices: [0u32; 2048],
feature_indices_len: feature_indices.len(),
});
data.vertices.0[0..buffer.buffer.vertices.len()].clone_from_slice(&buffer.buffer.vertices);
data.indices.0[0..buffer.buffer.indices.len()].clone_from_slice(&buffer.buffer.indices);
data.feature_indices[0..feature_indices.len()].clone_from_slice(&feature_indices);
data.layer_name[0..layer_data.name.len()].clone_from_slice(layer_data.name.as_bytes());
Self { data }
}
fn to_stored_layer(self) -> StoredLayer {
let layer = StoredLayer::TessellatedLayer {
coords: WrapperWorldTileCoords::peel(self.data.coords),
layer_name: String::from_utf8(Vec::from(
&self.data.layer_name[..self.data.layer_name_len],
))
.unwrap(),
buffer: OverAlignedVertexBuffer::from_slices(
&self.data.vertices.0[..self.data.vertices_len],
&self.data.indices.0[..self.data.indices_len],
self.data.usable_indices,
),
feature_indices: Vec::from(&self.data.feature_indices[..self.data.feature_indices_len]),
};
layer
}
}
pub struct LinearTransferables;
impl Transferables for LinearTransferables {
type TileTessellated = LinearTileTessellated;
type UnavailableLayer = LinearUnavailableLayer;
type TessellatedLayer = LinearTessellatedLayer;
}