diff --git a/.idea/maplibre-rs.iml b/.idea/maplibre-rs.iml index b6fc0101..cea86611 100644 --- a/.idea/maplibre-rs.iml +++ b/.idea/maplibre-rs.iml @@ -15,6 +15,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index cfa26158..de596bc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ resolver = "2" members = [ "maplibre", + "maplibre-winit", "maplibre-build-tools", "maplibre-demo", diff --git a/android/Cargo.toml b/android/Cargo.toml index 601fbc6f..348ab557 100644 --- a/android/Cargo.toml +++ b/android/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] maplibre = { path = "../maplibre" } +maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" } env_logger = "0.9" log = "0.4.16" ndk-glue = "0.5.0" # version is required by winit diff --git a/android/src/lib.rs b/android/src/lib.rs index 01d3e760..a8e4237b 100644 --- a/android/src/lib.rs +++ b/android/src/lib.rs @@ -2,9 +2,10 @@ use jni::objects::JClass; use jni::JNIEnv; use log::Level; use maplibre::platform::http_client::ReqwestHttpClient; +use maplibre::platform::run_multithreaded; use maplibre::platform::schedule_method::TokioScheduleMethod; -use maplibre::window::FromWindow; use maplibre::MapBuilder; +use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow}; use std::ffi::CString; #[cfg(not(target_os = "android"))] @@ -14,11 +15,16 @@ compile_error!("android works only on android."); pub fn android_main() { env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); - MapBuilder::from_window("A fantastic window!") - .with_http_client(ReqwestHttpClient::new(None)) - .with_schedule_method(TokioScheduleMethod::new()) - .build() - .run_sync(); + run_multithreaded(async { + MapBuilder::new() + .with_map_window_config(WinitMapWindowConfig::new("maplibre android".to_string())) + .with_http_client(ReqwestHttpClient::new(None)) + .with_schedule_method(TokioScheduleMethod::new()) + .build() + .initialize() + .await + .run() + }) } #[no_mangle] diff --git a/apple/Cargo.toml b/apple/Cargo.toml index be8b6c43..c8751616 100644 --- a/apple/Cargo.toml +++ b/apple/Cargo.toml @@ -8,6 +8,8 @@ publish = false [dependencies] maplibre = { path = "../maplibre" } +maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" } + env_logger = "0.9" [lib] diff --git a/apple/src/lib.rs b/apple/src/lib.rs index 5d4a3847..1e0720fc 100644 --- a/apple/src/lib.rs +++ b/apple/src/lib.rs @@ -1,7 +1,8 @@ use maplibre::platform::http_client::ReqwestHttpClient; +use maplibre::platform::run_multithreaded; use maplibre::platform::schedule_method::TokioScheduleMethod; -use maplibre::window::FromWindow; use maplibre::MapBuilder; +use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow}; #[cfg(not(any(target_os = "macos", target_os = "ios")))] compile_error!("apple works only on macOS and iOS."); @@ -10,9 +11,14 @@ compile_error!("apple works only on macOS and iOS."); pub fn maplibre_apple_main() { env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); - MapBuilder::from_window("A fantastic window!") - .with_http_client(ReqwestHttpClient::new(None)) - .with_schedule_method(TokioScheduleMethod::new()) - .build() - .run_sync(); + run_multithreaded(async { + MapBuilder::new() + .with_map_window_config(WinitMapWindowConfig::new("maplibre apple".to_string())) + .with_http_client(ReqwestHttpClient::new(None)) + .with_schedule_method(TokioScheduleMethod::new()) + .build() + .initialize() + .await + .run() + }) } diff --git a/maplibre-demo/Cargo.toml b/maplibre-demo/Cargo.toml index 67fc0cdb..6e04dabe 100644 --- a/maplibre-demo/Cargo.toml +++ b/maplibre-demo/Cargo.toml @@ -15,6 +15,7 @@ enable-tracing = ["maplibre/enable-tracing", "tracing-subscriber", "tracing-trac [dependencies] env_logger = "0.9" maplibre = { path = "../maplibre", version = "0.0.2" } +maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", optional = true } diff --git a/maplibre-demo/src/main.rs b/maplibre-demo/src/main.rs index 94ecb3e5..9884520f 100644 --- a/maplibre-demo/src/main.rs +++ b/maplibre-demo/src/main.rs @@ -1,7 +1,8 @@ use maplibre::platform::http_client::ReqwestHttpClient; +use maplibre::platform::run_multithreaded; use maplibre::platform::schedule_method::TokioScheduleMethod; -use maplibre::window::FromWindow; use maplibre::MapBuilder; +use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow}; #[cfg(feature = "enable-tracing")] fn enable_tracing() { @@ -14,19 +15,16 @@ fn enable_tracing() { } fn run_in_window() { - MapBuilder::from_window("A fantastic window!") - .with_http_client(ReqwestHttpClient::new(None)) - .with_schedule_method(TokioScheduleMethod::new()) - .build() - .run_sync(); -} - -fn run_headless() { - MapBuilder::from_window("A fantastic window!") - .with_http_client(ReqwestHttpClient::new(None)) - .with_schedule_method(TokioScheduleMethod::new()) - .build() - .run_sync(); + run_multithreaded(async { + MapBuilder::new() + .with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string())) + .with_http_client(ReqwestHttpClient::new(None)) + .with_schedule_method(TokioScheduleMethod::new()) + .build() + .initialize() + .await + .run() + }) } fn main() { @@ -35,9 +33,5 @@ fn main() { #[cfg(feature = "enable-tracing")] enable_tracing(); - MapBuilder::from_window("A fantastic window!") - .with_http_client(ReqwestHttpClient::new(None)) - .with_schedule_method(TokioScheduleMethod::new()) - .build() - .run_sync(); + run_in_window() } diff --git a/maplibre-winit/Cargo.toml b/maplibre-winit/Cargo.toml new file mode 100644 index 00000000..de062b8b --- /dev/null +++ b/maplibre-winit/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "maplibre-winit" +version = "0.0.1" +edition = "2021" + +[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android"))'.dependencies] +tokio = { version = "1.17", features = ["rt"] } + +[target.'cfg(target_os = "android")'.dependencies] +winit = { version = "0.26", default-features = false } + +[target.'cfg(target_os = "linux")'.dependencies] +winit = { version = "0.26", default-features = false, features = ["x11", "wayland"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +web-sys = { version = "0.3", features = ["Window"] } +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" + +[dependencies] +maplibre = { path = "../maplibre", version = "0.0.2" } +winit = { version = "0.26", default-features = false } +cgmath = "0.18.0" +instant = { version = "0.1", features = ["wasm-bindgen"] } # FIXME: Untrusted dependency +log = "0.4" \ No newline at end of file diff --git a/maplibre/src/input/mod.rs b/maplibre-winit/src/input/mod.rs similarity index 96% rename from maplibre/src/input/mod.rs rename to maplibre-winit/src/input/mod.rs index bcae2d86..9cf694e7 100644 --- a/maplibre/src/input/mod.rs +++ b/maplibre-winit/src/input/mod.rs @@ -2,8 +2,8 @@ use std::time::Duration; -use crate::coords::Zoom; use cgmath::Vector2; + use winit::event::{DeviceEvent, KeyboardInput, TouchPhase, WindowEvent}; use crate::input::pan_handler::PanHandler; @@ -12,9 +12,7 @@ use crate::input::query_handler::QueryHandler; use crate::input::shift_handler::ShiftHandler; use crate::input::tilt_handler::TiltHandler; use crate::input::zoom_handler::ZoomHandler; -use crate::map_state::{MapState, ViewState}; -use crate::render::camera::Camera; -use crate::MapWindow; +use maplibre::map_state::ViewState; mod pan_handler; mod pinch_handler; @@ -131,7 +129,6 @@ pub trait UpdateState { } impl UpdateState for InputController { - #[tracing::instrument(skip_all)] fn update_state(&mut self, state: &mut ViewState, dt: Duration) { self.pan_handler.update_state(state, dt); self.pinch_handler.update_state(state, dt); diff --git a/maplibre/src/input/pan_handler.rs b/maplibre-winit/src/input/pan_handler.rs similarity index 97% rename from maplibre/src/input/pan_handler.rs rename to maplibre-winit/src/input/pan_handler.rs index 3fed29a0..d630c00b 100644 --- a/maplibre/src/input/pan_handler.rs +++ b/maplibre-winit/src/input/pan_handler.rs @@ -1,10 +1,10 @@ use super::UpdateState; -use crate::map_state::{MapState, ViewState}; -use crate::render::camera::Camera; +use maplibre::map_state::ViewState; +use maplibre::render::camera::Camera; -use crate::MapWindow; use cgmath::{EuclideanSpace, Point3, Vector2, Vector3, Zero}; + use std::time::Duration; use winit::event::{ElementState, MouseButton}; diff --git a/maplibre/src/input/pinch_handler.rs b/maplibre-winit/src/input/pinch_handler.rs similarity index 80% rename from maplibre/src/input/pinch_handler.rs rename to maplibre-winit/src/input/pinch_handler.rs index 565e992c..0d68a24a 100644 --- a/maplibre/src/input/pinch_handler.rs +++ b/maplibre-winit/src/input/pinch_handler.rs @@ -1,7 +1,6 @@ use super::UpdateState; -use crate::map_state::ViewState; -use crate::{MapState, MapWindow}; +use maplibre::map_state::ViewState; use std::time::Duration; pub struct PinchHandler {} diff --git a/maplibre/src/input/query_handler.rs b/maplibre-winit/src/input/query_handler.rs similarity index 97% rename from maplibre/src/input/query_handler.rs rename to maplibre-winit/src/input/query_handler.rs index 7c3d34b7..99774ff1 100644 --- a/maplibre/src/input/query_handler.rs +++ b/maplibre-winit/src/input/query_handler.rs @@ -1,8 +1,7 @@ use cgmath::Vector2; use crate::input::UpdateState; -use crate::map_state::{MapState, ViewState}; -use crate::MapWindow; +use maplibre::map_state::ViewState; use std::time::Duration; use winit::event::{ElementState, MouseButton}; diff --git a/maplibre/src/input/shift_handler.rs b/maplibre-winit/src/input/shift_handler.rs similarity index 97% rename from maplibre/src/input/shift_handler.rs rename to maplibre-winit/src/input/shift_handler.rs index 660fa511..d3f8a6c8 100644 --- a/maplibre/src/input/shift_handler.rs +++ b/maplibre-winit/src/input/shift_handler.rs @@ -1,8 +1,7 @@ use super::UpdateState; -use crate::map_state::ViewState; -use crate::{MapState, MapWindow}; use cgmath::{Vector3, Zero}; +use maplibre::map_state::ViewState; use std::time::Duration; pub struct ShiftHandler { diff --git a/maplibre/src/input/tilt_handler.rs b/maplibre-winit/src/input/tilt_handler.rs similarity index 91% rename from maplibre/src/input/tilt_handler.rs rename to maplibre-winit/src/input/tilt_handler.rs index 44eeb9d6..54b307c8 100644 --- a/maplibre/src/input/tilt_handler.rs +++ b/maplibre-winit/src/input/tilt_handler.rs @@ -1,11 +1,9 @@ use super::UpdateState; -use crate::map_state::{MapState, ViewState}; +use maplibre::map_state::ViewState; -use crate::coords::Zoom; -use crate::render::camera::Camera; -use crate::MapWindow; use cgmath::{Deg, Rad, Zero}; + use std::time::Duration; pub struct TiltHandler { diff --git a/maplibre/src/input/zoom_handler.rs b/maplibre-winit/src/input/zoom_handler.rs similarity index 93% rename from maplibre/src/input/zoom_handler.rs rename to maplibre-winit/src/input/zoom_handler.rs index 34e7d5d3..ec00b9ad 100644 --- a/maplibre/src/input/zoom_handler.rs +++ b/maplibre-winit/src/input/zoom_handler.rs @@ -1,11 +1,10 @@ use super::UpdateState; -use crate::coords::Zoom; -use crate::map_state::{MapState, ViewState}; +use maplibre::coords::Zoom; +use maplibre::map_state::ViewState; -use crate::render::camera::Camera; -use crate::MapWindow; use cgmath::{Vector2, Vector3}; + use std::time::Duration; pub struct ZoomHandler { @@ -15,7 +14,7 @@ pub struct ZoomHandler { } impl UpdateState for ZoomHandler { - fn update_state(&mut self, state: &mut ViewState, dt: Duration) { + fn update_state(&mut self, state: &mut ViewState, _dt: Duration) { if let Some(zoom_delta) = self.zoom_delta { if let Some(window_position) = self.window_position { let current_zoom = state.zoom(); diff --git a/maplibre-winit/src/lib.rs b/maplibre-winit/src/lib.rs new file mode 100644 index 00000000..0e7d9675 --- /dev/null +++ b/maplibre-winit/src/lib.rs @@ -0,0 +1,2 @@ +pub mod input; +pub mod winit; diff --git a/maplibre-winit/src/winit/mod.rs b/maplibre-winit/src/winit/mod.rs new file mode 100644 index 00000000..c579bff4 --- /dev/null +++ b/maplibre-winit/src/winit/mod.rs @@ -0,0 +1,178 @@ +use instant::Instant; +use maplibre::error::Error; +use maplibre::io::scheduler::ScheduleMethod; +use maplibre::io::source_client::HTTPClient; +use winit::event::{ElementState, KeyboardInput, VirtualKeyCode, WindowEvent}; +use winit::event_loop::ControlFlow; + +use crate::input::{InputController, UpdateState}; +use maplibre::map_state::MapState; +use maplibre::window::{MapWindow, MapWindowConfig, Runnable}; +use winit::event::Event; + +#[cfg(target_arch = "wasm32")] +mod web; + +#[cfg(not(target_arch = "wasm32"))] +mod noweb; + +#[cfg(target_arch = "wasm32")] +pub use web::*; + +#[cfg(not(target_arch = "wasm32"))] +pub use noweb::*; + +#[cfg(not(target_arch = "wasm32"))] +pub struct WinitMapWindowConfig { + title: String, +} + +#[cfg(not(target_arch = "wasm32"))] +impl WinitMapWindowConfig { + pub fn new(title: String) -> Self { + Self { title } + } +} + +#[cfg(target_arch = "wasm32")] +pub struct WinitMapWindowConfig { + canvas_id: String, +} + +#[cfg(target_arch = "wasm32")] +impl WinitMapWindowConfig { + pub fn new(canvas_id: String) -> Self { + Self { canvas_id } + } +} + +impl MapWindowConfig for WinitMapWindowConfig { + type MapWindow = WinitMapWindow; +} + +pub struct WinitMapWindow { + window: WinitWindow, + event_loop: Option, +} + +pub type WinitWindow = winit::window::Window; +pub type WinitEventLoop = winit::event_loop::EventLoop<()>; + +impl WinitMapWindow { + pub fn take_event_loop(&mut self) -> Option { + self.event_loop.take() + } +} + +///Main (platform-specific) main loop which handles: +///* Input (Mouse/Keyboard) +///* Platform Events like suspend/resume +///* Render a new frame +impl Runnable for WinitMapWindow +where + MWC: MapWindowConfig, + SM: ScheduleMethod, + HC: HTTPClient, +{ + fn run(mut self, mut map_state: MapState, max_frames: Option) { + let mut last_render_time = Instant::now(); + let mut current_frame: u64 = 0; + + let mut input_controller = InputController::new(0.2, 100.0, 0.1); + + self.take_event_loop() + .unwrap() + .run(move |event, _, control_flow| { + #[cfg(target_os = "android")] + if !map_state.is_initialized() && event == Event::Resumed { + use tokio::runtime::Handle; + use tokio::task; + + let state = task::block_in_place(|| { + Handle::current().block_on(async { + map_state.reinitialize().await; + }) + }); + return; + } + + match event { + Event::DeviceEvent { + ref event, + .. // We're not using device_id currently + } => { + input_controller.device_input(event); + } + + Event::WindowEvent { + ref event, + window_id, + } if window_id == self.inner().id() => { + if !input_controller.window_input(event) { + match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(physical_size) => { + map_state.resize(physical_size.width, physical_size.height); + } + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + map_state.resize(new_inner_size.width, new_inner_size.height); + } + _ => {} + } + } + } + Event::RedrawRequested(_) => { + let now = Instant::now(); + let dt = now - last_render_time; + last_render_time = now; + + input_controller.update_state(map_state.view_state_mut(), dt); + + match map_state.update_and_redraw() { + Ok(_) => {} + Err(Error::Render(e)) => { + eprintln!("{}", e); + if e.should_exit() { + *control_flow = ControlFlow::Exit; + } + } + e => eprintln!("{:?}", e) + }; + + if let Some(max_frames) = max_frames { + if current_frame >= max_frames { + log::info!("Exiting because maximum frames reached."); + *control_flow = ControlFlow::Exit; + } + + current_frame += 1; + } + } + Event::Suspended => { + map_state.suspend(); + } + Event::Resumed => { + map_state.recreate_surface(&self); + let size = self.size(); + map_state.resize(size.width(), size.height());// FIXME: Resumed is also called when the app launches for the first time. Instead of first using a "fake" inner_size() in State::new we should initialize with a proper size from the beginning + map_state.resume(); + } + Event::MainEventsCleared => { + // RedrawRequested will only trigger once, unless we manually + // request it. + self.inner().request_redraw(); + } + _ => {} + } + }); + } +} diff --git a/maplibre-winit/src/winit/noweb.rs b/maplibre-winit/src/winit/noweb.rs new file mode 100644 index 00000000..4338aded --- /dev/null +++ b/maplibre-winit/src/winit/noweb.rs @@ -0,0 +1,50 @@ +//! Main (platform-specific) main loop which handles: +//! * Input (Mouse/Keyboard) +//! * Platform Events like suspend/resume +//! * Render a new frame + +use winit::window::WindowBuilder; + +use super::WinitEventLoop; +use super::WinitMapWindow; +use super::WinitWindow; + +use super::WinitMapWindowConfig; +use maplibre::window::{MapWindow, WindowSize}; + +impl MapWindow for WinitMapWindow { + type EventLoop = WinitEventLoop; + type Window = WinitWindow; + type MapWindowConfig = WinitMapWindowConfig; + + fn create(map_window_config: &Self::MapWindowConfig) -> Self { + let event_loop = WinitEventLoop::new(); + let window = WindowBuilder::new() + .with_title(&map_window_config.title) + .build(&event_loop) + .unwrap(); + + Self { + window, + event_loop: Some(event_loop), + } + } + + fn size(&self) -> WindowSize { + let size = self.window.inner_size(); + #[cfg(target_os = "android")] + // On android we can not get the dimensions of the window initially. Therefore, we use a + // fallback until the window is ready to deliver its correct bounds. + let window_size = + WindowSize::new(size.width, size.height).unwrap_or(WindowSize::new(100, 100).unwrap()); + + #[cfg(not(target_os = "android"))] + let window_size = + WindowSize::new(size.width, size.height).expect("failed to get window dimensions."); + window_size + } + + fn inner(&self) -> &Self::Window { + &self.window + } +} diff --git a/maplibre-winit/src/winit/web.rs b/maplibre-winit/src/winit/web.rs new file mode 100644 index 00000000..981c8787 --- /dev/null +++ b/maplibre-winit/src/winit/web.rs @@ -0,0 +1,63 @@ +use winit::window::WindowBuilder; + +use super::WinitEventLoop; +use super::WinitMapWindow; +use super::WinitMapWindowConfig; +use super::WinitWindow; + +use maplibre::window::{MapWindow, WindowSize}; +use winit::platform::web::WindowBuilderExtWebSys; + +impl MapWindow for WinitMapWindow { + type EventLoop = WinitEventLoop; + type Window = WinitWindow; + type MapWindowConfig = WinitMapWindowConfig; + + fn create(map_window_config: &Self::MapWindowConfig) -> Self { + let event_loop = WinitEventLoop::new(); + + let window: winit::window::Window = WindowBuilder::new() + .with_canvas(Some(get_canvas(&map_window_config.canvas_id))) + .build(&event_loop) + .unwrap(); + + let size = get_body_size().unwrap(); + window.set_inner_size(size); + Self { + window, + event_loop: Some(event_loop), + } + } + + fn size(&self) -> WindowSize { + let size = self.window.inner_size(); + + WindowSize::new(size.width, size.height).expect("failed to get window dimensions.") + } + + fn inner(&self) -> &Self::Window { + &self.window + } +} + +pub fn get_body_size() -> Option> { + let web_window: web_sys::Window = web_sys::window().unwrap(); + let document = web_window.document().unwrap(); + let body = document.body().unwrap(); + Some(winit::dpi::LogicalSize { + width: body.client_width(), + height: body.client_height(), + }) +} + +pub fn get_canvas(element_id: &str) -> web_sys::HtmlCanvasElement { + use wasm_bindgen::JsCast; + + let web_window: web_sys::Window = web_sys::window().unwrap(); + let document = web_window.document().unwrap(); + document + .get_element_by_id(element_id) + .unwrap() + .dyn_into::() + .unwrap() +} diff --git a/maplibre/Cargo.toml b/maplibre/Cargo.toml index d5578e48..8befc603 100644 --- a/maplibre/Cargo.toml +++ b/maplibre/Cargo.toml @@ -14,12 +14,7 @@ web-webgl = ["wgpu/webgl"] enable-tracing = [ "tracing-subscriber", "tracing-tracy", "tracy-client"] no-thread-safe-futures = [] -[target.'cfg(target_arch = "wasm32")'.dependencies] -web-sys = { version = "0.3", features = [ - "Window" -] } -wasm-bindgen = "0.2" -wasm-bindgen-futures = "0.4" + [target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android"))'.dependencies] tokio = { version = "1.17", features = ["macros", "rt", "rt-multi-thread", "sync", "time"] } @@ -31,19 +26,13 @@ tracing-tracy = { version = "0.8", optional = true } tracy-client = { version = "0.12.7", optional = true } [target.'cfg(target_os = "android")'.dependencies] -winit = { version = "0.26", default-features = false } # Use rusttls on android because cross compiling is difficult reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "gzip"] } -[target.'cfg(target_os = "linux")'.dependencies] -winit = { version = "0.26", default-features = false, features = ["x11", "wayland"] } - [dependencies] async-trait = "0.1" instant = { version = "0.1", features = ["wasm-bindgen"] } # FIXME: Untrusted dependency -winit = { version = "0.26", default-features = false } - raw-window-handle = "0.4" tracing = { version = "0.1" } diff --git a/maplibre/src/error.rs b/maplibre/src/error.rs index 79f2d9de..23086c60 100644 --- a/maplibre/src/error.rs +++ b/maplibre/src/error.rs @@ -1,13 +1,47 @@ //! Errors which can happen in various parts of the library. use lyon::tessellation::TessellationError; +use std::fmt; +use std::fmt::Formatter; use std::sync::mpsc::SendError; +use wgpu::SurfaceError; + +#[derive(Debug)] +pub enum RenderError { + Surface(wgpu::SurfaceError), +} + +impl fmt::Display for RenderError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + RenderError::Surface(e) => write!(f, "{}", e), + } + } +} + +impl RenderError { + pub fn should_exit(&self) -> bool { + match self { + RenderError::Surface(e) => match e { + SurfaceError::OutOfMemory => true, + _ => false, + }, + } + } +} #[derive(Debug)] pub enum Error { Schedule, Network(String), Tesselation(TessellationError), + Render(RenderError), +} + +impl From for Error { + fn from(e: SurfaceError) -> Self { + Error::Render(RenderError::Surface(e)) + } } impl From for Error { diff --git a/maplibre/src/io/shared_thread_state.rs b/maplibre/src/io/shared_thread_state.rs index 8050adae..64896940 100644 --- a/maplibre/src/io/shared_thread_state.rs +++ b/maplibre/src/io/shared_thread_state.rs @@ -1,16 +1,15 @@ -use crate::coords::{TileCoords, WorldCoords, WorldTileCoords, Zoom}; +use crate::coords::{WorldCoords, WorldTileCoords, Zoom}; use crate::error::Error; use crate::io::geometry_index::{GeometryIndex, IndexProcessor, IndexedGeometry, TileIndex}; use crate::io::tile_request_state::TileRequestState; use crate::io::{ - LayerTessellateMessage, TessellateMessage, TileFetchResult, TileRequest, TileRequestID, - TileTessellateMessage, + LayerTessellateMessage, TessellateMessage, TileRequest, TileRequestID, TileTessellateMessage, }; -use crate::tessellation::Tessellated; + use std::collections::HashSet; use crate::tessellation::zero_tessellator::ZeroTessellator; -use geozero::mvt::tile; + use geozero::GeozeroDatasource; use prost::Message; use std::sync::{mpsc, Arc, Mutex}; @@ -41,9 +40,9 @@ impl SharedThreadState { let mut tile = geozero::mvt::Tile::decode(data.as_ref()).expect("failed to load tile"); - let mut index = IndexProcessor::new(); + let index = IndexProcessor::new(); - for mut layer in &mut tile.layers { + for layer in &mut tile.layers { let cloned_layer = layer.clone(); let layer_name: &str = &cloned_layer.name; if !tile_request.layers.contains(layer_name) { diff --git a/maplibre/src/lib.rs b/maplibre/src/lib.rs index ab161a5d..0014e2ff 100644 --- a/maplibre/src/lib.rs +++ b/maplibre/src/lib.rs @@ -1,9 +1,9 @@ use crate::io::scheduler::{ScheduleMethod, Scheduler}; use crate::io::source_client::HTTPClient; -use crate::map_state::{MapState, Runnable}; +use crate::map_state::MapState; use crate::render::render_state::RenderState; use crate::style::Style; -use crate::window::{MapWindow, WindowFactory, WindowSize}; +use crate::window::{MapWindow, MapWindowConfig, Runnable, WindowSize}; use std::marker::PhantomData; pub mod coords; @@ -17,28 +17,25 @@ pub mod window; pub mod benchmarking; // Internal modules -pub(crate) mod input; -pub(crate) mod map_state; -pub(crate) mod render; +pub mod map_state; +pub mod render; pub(crate) mod tessellation; pub(crate) mod tilejson; pub(crate) mod util; -pub(crate) mod winit; -pub struct Map +pub struct Map where W: MapWindow, SM: ScheduleMethod, HC: HTTPClient, { - map_state: MapState, - event_loop: E, + map_state: MapState, + window: W, } -impl Map +impl Map where - MapState: Runnable, - W: MapWindow, + W: MapWindow + Runnable, SM: ScheduleMethod, HC: HTTPClient, { @@ -51,118 +48,95 @@ where } pub fn run_with_optionally_max_frames(self, max_frames: Option) { - self.map_state.run(self.event_loop, max_frames); + self.window.run(self.map_state, max_frames); } } -pub struct UninitializedMap +pub struct UninitializedMap where + MWC: MapWindowConfig, SM: ScheduleMethod, HC: HTTPClient, { - window: W, - event_loop: E, scheduler: Scheduler, http_client: HC, style: Style, + + map_window_config: MWC, } -impl UninitializedMap +impl UninitializedMap where - W: MapWindow + raw_window_handle::HasRawWindowHandle, + MWC: MapWindowConfig, SM: ScheduleMethod, HC: HTTPClient, { - pub async fn initialize(self) -> Map { - #[cfg(target_os = "android")] - // On android we can not get the dimensions of the window initially. Therefore, we use a - // fallback until the window is ready to deliver its correct bounds. - let window_size = self.window.size().unwrap_or_default(); + pub async fn initialize(self) -> Map { + let instance = wgpu::Instance::new(wgpu::Backends::all()); + //let instance = wgpu::Instance::new(wgpu::Backends::GL); + //let instance = wgpu::Instance::new(wgpu::Backends::VULKAN); - #[cfg(not(target_os = "android"))] - let window_size = self - .window - .size() - .expect("failed to get window dimensions."); + let window = MWC::MapWindow::create(&self.map_window_config); + let window_size = window.size(); - let render_state = RenderState::initialize(&self.window, window_size).await; + let surface = unsafe { instance.create_surface(window.inner()) }; + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: crate::platform::COLOR_TEXTURE_FORMAT, + width: window_size.width(), + height: window_size.height(), + // present_mode: wgpu::PresentMode::Mailbox, + present_mode: wgpu::PresentMode::Fifo, // VSync + }; + + let render_state = RenderState::initialize(instance, surface, surface_config).await; Map { map_state: MapState::new( - self.window, + self.map_window_config, window_size, render_state, self.scheduler, self.http_client, self.style, ), - event_loop: self.event_loop, + window, } } } -#[cfg(not(target_arch = "wasm32"))] -impl UninitializedMap -where - W: MapWindow + raw_window_handle::HasRawWindowHandle, - MapState: Runnable, - SM: ScheduleMethod, - HC: HTTPClient, -{ - pub fn run_sync(self) { - self.run_sync_with_optionally_max_frames(None); - } - - pub fn run_sync_with_max_frames(self, max_frames: u64) { - self.run_sync_with_optionally_max_frames(Some(max_frames)) - } - - fn run_sync_with_optionally_max_frames(self, max_frames: Option) { - tokio::runtime::Builder::new_multi_thread() - .worker_threads(4) - .enable_io() - .enable_time() - .on_thread_start(|| { - #[cfg(feature = "enable-tracing")] - tracy_client::set_thread_name("tokio-runtime-worker"); - }) - .build() - .unwrap() - .block_on(async { - self.initialize() - .await - .run_with_optionally_max_frames(max_frames); - }) - } -} - -pub struct MapBuilder +pub struct MapBuilder where SM: ScheduleMethod, { - window_factory: Box>, schedule_method: Option, scheduler: Option>, http_client: Option, style: Option