Add MapBuilder

This commit is contained in:
Maximilian Ammann 2022-03-13 14:54:55 +01:00
parent 4fe23acfb4
commit 14cf5f31d5
9 changed files with 266 additions and 231 deletions

View File

@ -1,5 +1,12 @@
use mapr::platform::mapr_generic_main;
use mapr::{MapBuilder, ScheduleMethod, TokioScheduleMethod};
fn main() {
mapr_generic_main()
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
MapBuilder::from_window("A fantastic window!")
.with_schedule_method(ScheduleMethod::Tokio(TokioScheduleMethod::new(Some(
"/tmp/mapr_cache".to_string(),
))))
.build()
.run_sync();
}

View File

@ -22,9 +22,9 @@ pub enum ScheduleMethod {
all(target_arch = "aarch64", not(target_os = "android")),
target_arch = "wasm32"
)))]
Tokio(crate::platform::TokioScheduleMethod),
Tokio(crate::platform::scheduler::TokioScheduleMethod),
#[cfg(target_arch = "wasm32")]
WebWorker(crate::platform::WebWorkerScheduleMethod),
WebWorker(crate::platform::scheduler::WebWorkerScheduleMethod),
}
impl ScheduleMethod {

View File

@ -1,15 +1,109 @@
use crate::io::scheduler::IOScheduler;
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
mod input;
pub(crate) mod coords;
pub(crate) mod error;
pub(crate) mod io;
pub(crate) mod main_loop;
pub(crate) mod platform;
pub(crate) mod render;
pub(crate) mod tessellation;
pub(crate) mod util;
// Used from outside to initialize mapr
pub mod platform;
// Used for benchmarking
pub mod benchmarking;
pub use io::scheduler::ScheduleMethod;
pub use platform::scheduler::*;
pub struct Map {
window: winit::window::Window,
event_loop: EventLoop<()>,
scheduler: Box<IOScheduler>,
}
impl Map {
#[cfg(target_arch = "wasm32")]
pub async fn run_async(self) {
main_loop::setup(self.window, self.event_loop, self.scheduler).await;
}
#[cfg(not(target_arch = "wasm32"))]
pub fn run_sync(self) {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap()
.block_on(async {
main_loop::setup(self.window, self.event_loop, self.scheduler).await;
})
}
}
pub struct MapBuilder {
create_window: Box<dyn FnOnce(&EventLoop<()>) -> winit::window::Window>,
schedule_method: Option<ScheduleMethod>,
scheduler: Option<Box<IOScheduler>>,
}
impl MapBuilder {
pub fn with_schedule_method(mut self, schedule_method: ScheduleMethod) -> Self {
self.schedule_method = Some(schedule_method);
self
}
pub fn with_existing_scheduler(mut self, scheduler: Box<IOScheduler>) -> Self {
self.scheduler = Some(scheduler);
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn from_window(title: &'static str) -> Self {
Self {
create_window: Box::new(move |event_loop| {
WindowBuilder::new()
.with_title(title)
.build(&event_loop)
.unwrap()
}),
schedule_method: None,
scheduler: None,
}
}
#[cfg(target_arch = "wasm32")]
pub fn from_canvas(dom_id: &'static str) -> Self {
Self {
create_window: Box::new(move |event_loop| {
use crate::platform::{get_body_size, get_canvas};
use winit::platform::web::WindowBuilderExtWebSys;
let window: winit::window::Window = WindowBuilder::new()
.with_canvas(Some(get_canvas(dom_id)))
.build(&event_loop)
.unwrap();
window.set_inner_size(get_body_size().unwrap());
window
}),
schedule_method: None,
scheduler: None,
}
}
pub fn build(self) -> Map {
let event_loop = EventLoop::new();
Map {
window: (self.create_window)(&event_loop),
event_loop,
scheduler: self
.scheduler
.unwrap_or_else(|| Box::new(IOScheduler::new(self.schedule_method.unwrap()))),
}
}
}

View File

@ -1,35 +1,17 @@
use crate::io::scheduler::IOScheduler;
use crate::main_loop;
use crate::io::scheduler::ScheduleMethod;
use crate::platform::scheduler::TokioScheduleMethod;
pub use std::time::Instant;
use tokio::task;
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
#[tokio::main]
pub async fn main() {
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
pub fn main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let mut scheduler = IOScheduler::new();
let download_tessellate_loop = scheduler.take_download_loop();
let join_handle = task::spawn_blocking(move || {
Handle::current().block_on(async move {
if let Err(e) = download_tessellate_loop.run_loop().await {
error!("Worker loop errored {:?}", e)
}
});
});
main_loop::setup(window, event_loop, Box::new(scheduler)).await;
join_handle.await.unwrap()
MapBuilder::from_window("A fantastic window!")
.with_schedule_method(ScheduleMethod::Tokio(TokioScheduleMethod::new(Some(
"/tmp/mapr_cache".to_string(),
))))
.build()
.run_sync();
}

View File

@ -1,36 +1,18 @@
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
use crate::io::scheduler::IOScheduler;
use crate::main_loop;
use crate::io::scheduler::ScheduleMethod;
use crate::platform::scheduler::TokioScheduleMethod;
pub use std::time::Instant;
use tokio::task;
// macOS and iOS (Metal)
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
#[no_mangle]
#[tokio::main]
pub async fn mapr_apple_main() {
pub fn mapr_apple_main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let mut scheduler = IOScheduler::new();
let download_tessellate_loop = scheduler.take_download_loop();
let join_handle = task::spawn_blocking(move || {
Handle::current().block_on(async move {
if let Err(e) = download_tessellate_loop.run_loop().await {
error!("Worker loop errored {:?}", e)
}
});
});
main_loop::setup(window, event_loop, Box::new(scheduler)).await;
join_handle.await.unwrap()
MapBuilder::from_window("A fantastic window!")
.with_schedule_method(ScheduleMethod::Tokio(TokioScheduleMethod::new(Some(
"/tmp/mapr_cache".to_string(),
))))
.build()
.run_sync();
}

View File

@ -1,39 +0,0 @@
//! Module which is used if android, apple and web is not used.
use crate::io::scheduler::{IOScheduler, ScheduleMethod};
use crate::main_loop;
use crate::platform::TokioScheduleMethod;
pub use std::time::Instant;
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
// Vulkan/OpenGL
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
#[tokio::main]
pub async fn mapr_generic_main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();
let scheduler = IOScheduler::new(ScheduleMethod::Tokio(TokioScheduleMethod::new(
"/tmp/mapr_cache".to_string(),
)));
/* let join_handle = task::spawn_blocking(move || {
Handle::current().block_on(async move {
if let Err(e) = download_tessellate_loop.run_loop().await {
error!("Worker loop errored {:?}", e)
}
});
});*/
main_loop::setup(window, event_loop, Box::new(scheduler)).await;
/* join_handle.await.unwrap()*/
}

View File

@ -13,12 +13,13 @@ mod android;
#[cfg(not(target_arch = "wasm32"))]
mod noweb;
/// For Vulkan/OpenGL
#[cfg(not(any(
target_os = "android",
all(target_arch = "aarch64", not(target_os = "android")),
target_arch = "wasm32"
)))]
mod generic;
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
#[cfg(target_arch = "wasm32")]
pub use web::*;
@ -32,13 +33,6 @@ pub use android::*;
#[cfg(not(target_arch = "wasm32"))]
pub use noweb::*;
#[cfg(not(any(
target_os = "android",
all(target_arch = "aarch64", not(target_os = "android")),
target_arch = "wasm32"
)))]
pub use generic::*;
// FIXME: This limit is enforced by WebGL. Actually this makes sense!
// FIXME: This can also be achieved by _pad attributes in shader_ffi.rs
pub const MIN_BUFFER_SIZE: u64 = 32;

View File

@ -1,87 +1,90 @@
//! Module which is used target platform is not web related.
use crate::coords::TileCoords;
pub use std::time::Instant;
use reqwest::{Client, StatusCode};
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_middleware_cache::managers::CACacheManager;
use reqwest_middleware_cache::{Cache, CacheMode};
pub mod scheduler {
use reqwest::{Client, StatusCode};
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_middleware_cache::managers::CACacheManager;
use reqwest_middleware_cache::{Cache, CacheMode};
use crate::error::Error;
use crate::io::scheduler::IOScheduler;
use crate::io::TileRequestID;
use crate::coords::TileCoords;
use crate::error::Error;
use crate::io::scheduler::IOScheduler;
use crate::io::TileRequestID;
use crate::{MapBuilder, ScheduleMethod};
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Error::Network(err.to_string())
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Error::Network(err.to_string())
}
}
}
impl From<reqwest_middleware::Error> for Error {
fn from(err: reqwest_middleware::Error) -> Self {
Error::Network(err.to_string())
impl From<reqwest_middleware::Error> for Error {
fn from(err: reqwest_middleware::Error) -> Self {
Error::Network(err.to_string())
}
}
}
pub struct TokioScheduleMethod {
client: ClientWithMiddleware,
}
pub struct TokioScheduleMethod {
client: ClientWithMiddleware,
}
impl TokioScheduleMethod {
/// cache_path: Under which path should we cache requests.
pub fn new(cache_path: String) -> Self {
let mut builder = ClientBuilder::new(Client::new());
impl TokioScheduleMethod {
/// cache_path: Under which path should we cache requests.
pub fn new(cache_path: Option<String>) -> Self {
let mut builder = ClientBuilder::new(Client::new());
// FIXME: Cache only works on desktop so far
if cfg!(not(any(target_os = "android", target_arch = "aarch64"))) {
builder = builder.with(Cache {
mode: CacheMode::Default,
cache_manager: CACacheManager { path: cache_path },
if let Some(cache_path) = cache_path {
builder = builder.with(Cache {
mode: CacheMode::Default,
cache_manager: CACacheManager { path: cache_path },
});
}
Self {
client: builder.build(),
}
}
async fn fetch(client: &ClientWithMiddleware, url: &str) -> Result<Vec<u8>, Error> {
let response = client.get(url).send().await?;
if response.status() != StatusCode::OK {
return Err(Error::Network("response code not 200".to_string()));
}
let body = response.bytes().await?;
Ok(Vec::from(body.as_ref()))
}
pub fn schedule_tile_request(
&self,
scheduler: &IOScheduler,
request_id: TileRequestID,
coords: TileCoords,
) {
let state = scheduler.new_tessellator_state();
let client = self.client.clone();
tokio::task::spawn(async move {
if let Ok(data) = Self::fetch(
&client,
format!(
"https://maps.tuerantuer.org/europe_germany/{z}/{x}/{y}.pbf",
x = coords.x,
y = coords.y,
z = coords.z
)
.as_str(),
)
.await
{
state
.tessellate_layers(request_id, data.into_boxed_slice())
.unwrap();
} else {
// TODO Error
}
});
}
Self {
client: builder.build(),
}
}
async fn fetch(client: &ClientWithMiddleware, url: &str) -> Result<Vec<u8>, Error> {
let response = client.get(url).send().await?;
if response.status() != StatusCode::OK {
return Err(Error::Network("response code not 200".to_string()));
}
let body = response.bytes().await?;
Ok(Vec::from(body.as_ref()))
}
pub fn schedule_tile_request(
&self,
scheduler: &IOScheduler,
request_id: TileRequestID,
coords: TileCoords,
) {
let state = scheduler.new_tessellator_state();
let client = self.client.clone();
tokio::task::spawn(async move {
if let Ok(data) = Self::fetch(
&client,
format!(
"https://maps.tuerantuer.org/europe_germany/{z}/{x}/{y}.pbf",
x = coords.x,
y = coords.y,
z = coords.z
)
.as_str(),
)
.await
{
state
.tessellate_layers(request_id, data.into_boxed_slice())
.unwrap();
} else {
// TODO Error
}
});
}
}

View File

@ -8,6 +8,11 @@ use winit::event_loop::EventLoop;
use winit::platform::web::WindowBuilderExtWebSys;
use winit::window::{Window, WindowBuilder};
use crate::io::scheduler::IOScheduler;
use crate::io::scheduler::ScheduleMethod;
use crate::io::scheduler::ThreadLocalTessellatorState;
use crate::MapBuilder;
use crate::WebWorkerScheduleMethod;
use console_error_panic_hook;
pub use instant::Instant;
use style_spec::source::TileAdressingScheme;
@ -16,11 +21,6 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::Window as WebSysWindow;
use crate::coords::{TileCoords, WorldTileCoords};
use crate::io::scheduler::{IOScheduler, ScheduleMethod, ThreadLocalTessellatorState};
use crate::io::tile_cache::TileCache;
use crate::io::TileRequestID;
// WebGPU
#[cfg(not(feature = "web-webgl"))]
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8Unorm;
@ -61,32 +61,6 @@ pub fn new_tessellator_state(workflow_ptr: *mut IOScheduler) -> *mut ThreadLocal
return tessellator_state_ptr;
}
pub struct WebWorkerScheduleMethod;
impl WebWorkerScheduleMethod {
pub fn new() -> Self {
Self
}
pub fn schedule_tile_request(
&self,
_scheduler: &IOScheduler,
request_id: TileRequestID,
coords: TileCoords,
) {
schedule_tile_request(
format!(
"https://maps.tuerantuer.org/europe_germany/{z}/{x}/{y}.pbf",
x = coords.x,
y = coords.y,
z = coords.z,
)
.as_str(),
request_id,
)
}
}
#[wasm_bindgen]
pub fn tessellate_layers(
tessellator_state_ptr: *mut ThreadLocalTessellatorState,
@ -104,36 +78,74 @@ pub fn tessellate_layers(
std::mem::forget(tessellator_state);
}
#[wasm_bindgen]
pub async fn run(workflow_ptr: *mut IOScheduler) {
let workflow: Box<IOScheduler> = unsafe { Box::from_raw(workflow_ptr) };
let event_loop = EventLoop::new();
pub fn get_body_size() -> Option<LogicalSize<i32>> {
let web_window: WebSysWindow = web_sys::window().unwrap();
let document = web_window.document().unwrap();
let body = document.body().unwrap();
let builder = WindowBuilder::new();
let canvas: web_sys::HtmlCanvasElement = document
.get_element_by_id("mapr")
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
let window: Window = builder
.with_canvas(Some(canvas))
.build(&event_loop)
.unwrap();
window.set_inner_size(LogicalSize {
Some(LogicalSize {
width: body.client_width(),
height: body.client_height(),
});
})
}
pub fn get_canvas(element_id: &'static str) -> web_sys::HtmlCanvasElement {
let web_window: WebSysWindow = web_sys::window().unwrap();
let document = web_window.document().unwrap();
document
.get_element_by_id(element_id)
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap()
}
#[wasm_bindgen]
pub async fn run(workflow_ptr: *mut IOScheduler) {
let scheduler: Box<IOScheduler> = unsafe { Box::from_raw(workflow_ptr) };
// Either call forget or the main loop to keep worker loop alive
crate::main_loop::setup(window, event_loop, workflow).await;
MapBuilder::from_canvas("mapr")
.with_existing_scheduler(scheduler)
.build()
.run_async()
.await;
// std::mem::forget(workflow);
}
pub mod scheduler {
use super::schedule_tile_request;
use crate::coords::{TileCoords, WorldTileCoords};
use crate::io::scheduler::{IOScheduler, ScheduleMethod, ThreadLocalTessellatorState};
use crate::io::tile_cache::TileCache;
use crate::io::TileRequestID;
pub struct WebWorkerScheduleMethod;
impl WebWorkerScheduleMethod {
pub fn new() -> Self {
Self
}
pub fn schedule_tile_request(
&self,
_scheduler: &IOScheduler,
request_id: TileRequestID,
coords: TileCoords,
) {
schedule_tile_request(
format!(
"https://maps.tuerantuer.org/europe_germany/{z}/{x}/{y}.pbf",
x = coords.x,
y = coords.y,
z = coords.z,
)
.as_str(),
request_id,
)
}
}
}
/*use crate::error::Error;
use js_sys::{ArrayBuffer, Error as JSError, Uint8Array};
use wasm_bindgen::prelude::*;