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() { 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")), all(target_arch = "aarch64", not(target_os = "android")),
target_arch = "wasm32" target_arch = "wasm32"
)))] )))]
Tokio(crate::platform::TokioScheduleMethod), Tokio(crate::platform::scheduler::TokioScheduleMethod),
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
WebWorker(crate::platform::WebWorkerScheduleMethod), WebWorker(crate::platform::scheduler::WebWorkerScheduleMethod),
} }
impl ScheduleMethod { impl ScheduleMethod {

View File

@ -1,15 +1,109 @@
use crate::io::scheduler::IOScheduler;
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
mod input; mod input;
pub(crate) mod coords; pub(crate) mod coords;
pub(crate) mod error; pub(crate) mod error;
pub(crate) mod io; pub(crate) mod io;
pub(crate) mod main_loop; pub(crate) mod main_loop;
pub(crate) mod platform;
pub(crate) mod render; pub(crate) mod render;
pub(crate) mod tessellation; pub(crate) mod tessellation;
pub(crate) mod util; pub(crate) mod util;
// Used from outside to initialize mapr
pub mod platform;
// Used for benchmarking // Used for benchmarking
pub mod 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::io::scheduler::ScheduleMethod;
use crate::main_loop; use crate::platform::scheduler::TokioScheduleMethod;
pub use std::time::Instant; pub use std::time::Instant;
use tokio::task;
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm; pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] #[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
#[tokio::main] pub fn main() {
pub async fn main() {
use winit::event_loop::EventLoop;
use winit::window::WindowBuilder;
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
let event_loop = EventLoop::new(); MapBuilder::from_window("A fantastic window!")
let window = WindowBuilder::new() .with_schedule_method(ScheduleMethod::Tokio(TokioScheduleMethod::new(Some(
.with_title("A fantastic window!") "/tmp/mapr_cache".to_string(),
.build(&event_loop) ))))
.unwrap(); .build()
.run_sync();
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()
} }

View File

@ -1,36 +1,18 @@
use winit::event_loop::EventLoop; use crate::io::scheduler::ScheduleMethod;
use winit::window::WindowBuilder; use crate::platform::scheduler::TokioScheduleMethod;
use crate::io::scheduler::IOScheduler;
use crate::main_loop;
pub use std::time::Instant; pub use std::time::Instant;
use tokio::task;
// macOS and iOS (Metal) // macOS and iOS (Metal)
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb;
#[no_mangle] #[no_mangle]
#[tokio::main] pub fn mapr_apple_main() {
pub async fn mapr_apple_main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
let event_loop = EventLoop::new(); MapBuilder::from_window("A fantastic window!")
let window = WindowBuilder::new() .with_schedule_method(ScheduleMethod::Tokio(TokioScheduleMethod::new(Some(
.with_title("A fantastic window!") "/tmp/mapr_cache".to_string(),
.build(&event_loop) ))))
.unwrap(); .build()
.run_sync();
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()
} }

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

View File

@ -1,87 +1,90 @@
//! Module which is used target platform is not web related. //! Module which is used target platform is not web related.
use crate::coords::TileCoords; pub use std::time::Instant;
use reqwest::{Client, StatusCode}; pub mod scheduler {
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; use reqwest::{Client, StatusCode};
use reqwest_middleware_cache::managers::CACacheManager; use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use reqwest_middleware_cache::{Cache, CacheMode}; use reqwest_middleware_cache::managers::CACacheManager;
use reqwest_middleware_cache::{Cache, CacheMode};
use crate::error::Error; use crate::coords::TileCoords;
use crate::io::scheduler::IOScheduler; use crate::error::Error;
use crate::io::TileRequestID; use crate::io::scheduler::IOScheduler;
use crate::io::TileRequestID;
use crate::{MapBuilder, ScheduleMethod};
impl From<reqwest::Error> for Error { impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self { fn from(err: reqwest::Error) -> Self {
Error::Network(err.to_string()) Error::Network(err.to_string())
}
} }
}
impl From<reqwest_middleware::Error> for Error { impl From<reqwest_middleware::Error> for Error {
fn from(err: reqwest_middleware::Error) -> Self { fn from(err: reqwest_middleware::Error) -> Self {
Error::Network(err.to_string()) Error::Network(err.to_string())
}
} }
}
pub struct TokioScheduleMethod { pub struct TokioScheduleMethod {
client: ClientWithMiddleware, client: ClientWithMiddleware,
} }
impl TokioScheduleMethod { impl TokioScheduleMethod {
/// cache_path: Under which path should we cache requests. /// cache_path: Under which path should we cache requests.
pub fn new(cache_path: String) -> Self { pub fn new(cache_path: Option<String>) -> Self {
let mut builder = ClientBuilder::new(Client::new()); let mut builder = ClientBuilder::new(Client::new());
// FIXME: Cache only works on desktop so far if let Some(cache_path) = cache_path {
if cfg!(not(any(target_os = "android", target_arch = "aarch64"))) { builder = builder.with(Cache {
builder = builder.with(Cache { mode: CacheMode::Default,
mode: CacheMode::Default, cache_manager: CACacheManager { path: cache_path },
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::platform::web::WindowBuilderExtWebSys;
use winit::window::{Window, WindowBuilder}; 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; use console_error_panic_hook;
pub use instant::Instant; pub use instant::Instant;
use style_spec::source::TileAdressingScheme; use style_spec::source::TileAdressingScheme;
@ -16,11 +21,6 @@ use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::Window as WebSysWindow; 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 // WebGPU
#[cfg(not(feature = "web-webgl"))] #[cfg(not(feature = "web-webgl"))]
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8Unorm; 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; 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] #[wasm_bindgen]
pub fn tessellate_layers( pub fn tessellate_layers(
tessellator_state_ptr: *mut ThreadLocalTessellatorState, tessellator_state_ptr: *mut ThreadLocalTessellatorState,
@ -104,36 +78,74 @@ pub fn tessellate_layers(
std::mem::forget(tessellator_state); std::mem::forget(tessellator_state);
} }
#[wasm_bindgen] pub fn get_body_size() -> Option<LogicalSize<i32>> {
pub async fn run(workflow_ptr: *mut IOScheduler) {
let workflow: Box<IOScheduler> = unsafe { Box::from_raw(workflow_ptr) };
let event_loop = EventLoop::new();
let web_window: WebSysWindow = web_sys::window().unwrap(); let web_window: WebSysWindow = web_sys::window().unwrap();
let document = web_window.document().unwrap(); let document = web_window.document().unwrap();
let body = document.body().unwrap(); let body = document.body().unwrap();
let builder = WindowBuilder::new(); Some(LogicalSize {
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 {
width: body.client_width(), width: body.client_width(),
height: body.client_height(), 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 // 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); // 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 crate::error::Error;
use js_sys::{ArrayBuffer, Error as JSError, Uint8Array}; use js_sys::{ArrayBuffer, Error as JSError, Uint8Array};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;