Move winit code to maplibre-winit (#86)

* Extract winit code to maplibre-winit

* Run clippy

* Fix android and apple

* Remove import

* Fix android compilation

* Fix android

* Update dependencies

* Fix android

* Fix android

* Add map window config

* Fix feature flag

* Add title to config

* Remove unused export

* Fix for android
This commit is contained in:
Max Ammann 2022-05-10 08:58:45 +02:00 committed by GitHub
parent 30cdb3da61
commit feff3201c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 561 additions and 445 deletions

1
.idea/maplibre-rs.iml generated
View File

@ -15,6 +15,7 @@
<sourceFolder url="file://$MODULE_DIR$/benchmarks/benches" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/benchmarks/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/maplibre-demo/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/maplibre-winit/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/web/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/docs/book" />

View File

@ -4,6 +4,7 @@ resolver = "2"
members = [
"maplibre",
"maplibre-winit",
"maplibre-build-tools",
"maplibre-demo",

View File

@ -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

View File

@ -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]

View File

@ -8,6 +8,8 @@ publish = false
[dependencies]
maplibre = { path = "../maplibre" }
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
env_logger = "0.9"
[lib]

View File

@ -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()
})
}

View File

@ -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 }

View File

@ -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()
}

25
maplibre-winit/Cargo.toml Normal file
View File

@ -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"

View File

@ -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);

View File

@ -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};

View File

@ -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 {}

View File

@ -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};

View File

@ -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 {

View File

@ -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 {

View File

@ -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();

View File

@ -0,0 +1,2 @@
pub mod input;
pub mod winit;

View File

@ -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<WinitEventLoop>,
}
pub type WinitWindow = winit::window::Window;
pub type WinitEventLoop = winit::event_loop::EventLoop<()>;
impl WinitMapWindow {
pub fn take_event_loop(&mut self) -> Option<WinitEventLoop> {
self.event_loop.take()
}
}
///Main (platform-specific) main loop which handles:
///* Input (Mouse/Keyboard)
///* Platform Events like suspend/resume
///* Render a new frame
impl<MWC, SM, HC> Runnable<MWC, SM, HC> for WinitMapWindow
where
MWC: MapWindowConfig<MapWindow = WinitMapWindow>,
SM: ScheduleMethod,
HC: HTTPClient,
{
fn run(mut self, mut map_state: MapState<MWC, SM, HC>, max_frames: Option<u64>) {
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();
}
_ => {}
}
});
}
}

View File

@ -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
}
}

View File

@ -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<winit::dpi::LogicalSize<i32>> {
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::<web_sys::HtmlCanvasElement>()
.unwrap()
}

View File

@ -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" }

View File

@ -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<SurfaceError> for Error {
fn from(e: SurfaceError) -> Self {
Error::Render(RenderError::Surface(e))
}
}
impl From<TessellationError> for Error {

View File

@ -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) {

View File

@ -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<W, E, SM, HC>
pub struct Map<W, SM, HC>
where
W: MapWindow,
SM: ScheduleMethod,
HC: HTTPClient,
{
map_state: MapState<W, SM, HC>,
event_loop: E,
map_state: MapState<W::MapWindowConfig, SM, HC>,
window: W,
}
impl<W, E, SM, HC> Map<W, E, SM, HC>
impl<W, SM, HC> Map<W, SM, HC>
where
MapState<W, SM, HC>: Runnable<E>,
W: MapWindow,
W: MapWindow + Runnable<W::MapWindowConfig, SM, HC>,
SM: ScheduleMethod,
HC: HTTPClient,
{
@ -51,118 +48,95 @@ where
}
pub fn run_with_optionally_max_frames(self, max_frames: Option<u64>) {
self.map_state.run(self.event_loop, max_frames);
self.window.run(self.map_state, max_frames);
}
}
pub struct UninitializedMap<W, E, SM, HC>
pub struct UninitializedMap<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HTTPClient,
{
window: W,
event_loop: E,
scheduler: Scheduler<SM>,
http_client: HC,
style: Style,
map_window_config: MWC,
}
impl<W, E, SM, HC> UninitializedMap<W, E, SM, HC>
impl<MWC, SM, HC> UninitializedMap<MWC, SM, HC>
where
W: MapWindow + raw_window_handle::HasRawWindowHandle,
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HTTPClient,
{
pub async fn initialize(self) -> Map<W, E, SM, HC> {
#[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<MWC::MapWindow, SM, HC> {
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<W, E, SM, HC> UninitializedMap<W, E, SM, HC>
where
W: MapWindow + raw_window_handle::HasRawWindowHandle,
MapState<W, SM, HC>: Runnable<E>,
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<u64>) {
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<W, E, SM, HC>
pub struct MapBuilder<MWC, SM, HC>
where
SM: ScheduleMethod,
{
window_factory: Box<WindowFactory<W, E>>,
schedule_method: Option<SM>,
scheduler: Option<Scheduler<SM>>,
http_client: Option<HC>,
style: Option<Style>,
map_window_config: Option<MWC>,
}
impl<W, E, SM, HC> MapBuilder<W, E, SM, HC>
impl<MWC, SM, HC> MapBuilder<MWC, SM, HC>
where
MapState<W, SM, HC>: Runnable<E>,
W: MapWindow + raw_window_handle::HasRawWindowHandle,
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HTTPClient,
{
pub fn new(create_window: Box<WindowFactory<W, E>>) -> Self {
pub fn new() -> Self {
Self {
window_factory: create_window,
schedule_method: None,
scheduler: None,
http_client: None,
style: None,
map_window_config: None,
}
}
pub fn with_map_window_config(mut self, map_window_config: MWC) -> Self {
self.map_window_config = Some(map_window_config);
self
}
pub fn with_schedule_method(mut self, schedule_method: SM) -> Self {
self.schedule_method = Some(schedule_method);
self
@ -183,20 +157,17 @@ where
self
}
pub fn build(self) -> UninitializedMap<W, E, SM, HC> {
let (window, event_loop) = (self.window_factory)();
pub fn build(self) -> UninitializedMap<MWC, SM, HC> {
let scheduler = self
.scheduler
.unwrap_or_else(|| Scheduler::new(self.schedule_method.unwrap()));
let style = self.style.unwrap_or_default();
UninitializedMap {
window,
event_loop,
scheduler,
http_client: self.http_client.unwrap(),
style,
map_window_config: self.map_window_config.unwrap(),
}
}
}

View File

@ -12,15 +12,10 @@ use crate::render::camera::{Camera, Perspective, ViewProjection};
use crate::render::render_state::RenderState;
use crate::style::Style;
use crate::util::ChangeObserver;
use crate::{MapWindow, ScheduleMethod, WindowSize};
use crate::{MapWindow, MapWindowConfig, ScheduleMethod, WindowSize};
use std::collections::HashSet;
use std::sync;
use std::sync::{mpsc, Arc, Mutex};
use wgpu::SurfaceError;
pub trait Runnable<E> {
fn run(self, event_loop: E, max_frames: Option<u64>);
}
use std::sync::{mpsc, Arc, Mutex};
pub struct ViewState {
zoom: ChangeObserver<Zoom>,
@ -47,13 +42,13 @@ impl ViewState {
}
}
pub struct MapState<W, SM, HC>
pub struct MapState<MWC, SM, HC>
where
W: MapWindow,
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HTTPClient,
{
window: W,
map_window_config: MWC,
view_state: ViewState,
@ -70,14 +65,14 @@ where
try_failed: bool,
}
impl<W, SM, HC> MapState<W, SM, HC>
impl<MWC, SM, HC> MapState<MWC, SM, HC>
where
W: MapWindow,
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HTTPClient,
{
pub fn new(
window: W,
map_window_config: MWC,
window_size: WindowSize,
render_state: Option<RenderState>,
scheduler: Scheduler<SM>,
@ -103,8 +98,7 @@ where
let (message_sender, message_receiver) = mpsc::channel();
Self {
window,
map_window_config,
view_state: ViewState {
zoom: ChangeObserver::default(),
camera: ChangeObserver::new(camera),
@ -129,7 +123,7 @@ where
}
}
pub fn update_and_redraw(&mut self) -> Result<(), SurfaceError> {
pub fn update_and_redraw(&mut self) -> Result<(), Error> {
// Get data from other threads
self.try_populate_cache();
@ -137,12 +131,12 @@ where
self.prepare_render();
// Render buffers
let result = self.render_state_mut().render();
self.render_state_mut().render()?;
#[cfg(all(feature = "enable-tracing", not(target_arch = "wasm32")))]
tracy_client::finish_continuous_frame!();
result
Ok(())
}
#[tracing::instrument(skip_all)]
@ -307,10 +301,6 @@ where
&self.scheduler
}
pub fn window(&self) -> &W {
&self.window
}
pub fn suspend(&mut self) {
self.render_state_mut().suspend();
}
@ -336,19 +326,12 @@ where
pub fn view_state_mut(&mut self) -> &mut ViewState {
&mut self.view_state
}
}
impl<W, SM, HC> MapState<W, SM, HC>
where
W: MapWindow + raw_window_handle::HasRawWindowHandle,
SM: ScheduleMethod,
HC: HTTPClient,
{
pub fn recreate_surface(&mut self) {
pub fn recreate_surface(&mut self, window: &MWC::MapWindow) {
self.render_state
.as_mut()
.expect("render state not yet initialized. Call reinitialize().")
.recreate_surface(&self.window);
.recreate_surface(window);
}
pub fn is_initialized(&self) -> bool {
@ -357,11 +340,24 @@ where
pub async fn reinitialize(&mut self) {
if self.render_state.is_none() {
let window_size = self
.window
.size()
.expect("Window size should be known when reinitializing.");
let render_state = RenderState::initialize(&self.window, window_size)
let instance = wgpu::Instance::new(wgpu::Backends::all());
//let instance = wgpu::Instance::new(wgpu::Backends::GL);
//let instance = wgpu::Instance::new(wgpu::Backends::VULKAN);
let window = MWC::MapWindow::create(&self.map_window_config);
let window_size = window.size();
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 _window_size = window.size();
let render_state = RenderState::initialize(instance, surface, surface_config)
.await
.unwrap();
self.render_state = Some(render_state)

View File

@ -39,6 +39,9 @@ pub mod schedule_method {
pub use super::noweb::schedule_method::*;
}
#[cfg(not(target_arch = "wasm32"))]
pub use noweb::run_multithreaded;
// 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,4 +1,20 @@
//! Module which is used target platform is not web related.
use std::future::Future;
pub mod http_client;
pub mod schedule_method;
pub fn run_multithreaded<F: Future>(future: F) -> F::Output {
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(future)
}

View File

@ -22,7 +22,7 @@ use crate::render::options::{
use crate::render::tile_view_pattern::{TileInView, TileViewPattern};
use crate::tessellation::IndexDataType;
use crate::util::FPSMeter;
use crate::{MapWindow, WindowSize};
use crate::MapWindow;
use super::piplines::*;
use super::shaders;
@ -65,26 +65,13 @@ pub struct RenderState {
}
impl RenderState {
pub async fn initialize<W: raw_window_handle::HasRawWindowHandle>(
window: &W,
window_size: WindowSize,
pub async fn initialize(
instance: wgpu::Instance,
surface: wgpu::Surface,
surface_config: wgpu::SurfaceConfiguration,
) -> Option<Self> {
let sample_count = 4;
let instance = wgpu::Instance::new(wgpu::Backends::all());
//let instance = wgpu::Instance::new(wgpu::Backends::GL);
//let instance = wgpu::Instance::new(wgpu::Backends::VULKAN);
let surface = unsafe { instance.create_surface(&window) };
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 adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
@ -280,11 +267,11 @@ impl RenderState {
})
}
pub fn recreate_surface<W: raw_window_handle::HasRawWindowHandle>(&mut self, window: &W) {
pub fn recreate_surface<W: MapWindow>(&mut self, window: &W) {
// We only create a new surface if we are currently suspended. On Android (and probably iOS)
// the surface gets invalid after the app has been suspended.
if self.suspended {
let surface = unsafe { self.instance.create_surface(window) };
let surface = unsafe { self.instance.create_surface(window.inner()) };
surface.configure(&self.device, &self.surface_config);
self.surface = surface;
}

View File

@ -188,6 +188,6 @@ mod tests {
}
"##;
let style: Style = serde_json::from_str(&style_json_str).unwrap();
let _style: Style = serde_json::from_str(style_json_str).unwrap();
}
}

View File

@ -1,7 +1,6 @@
use bytemuck::Pod;
use geozero::{FeatureProcessor, GeomProcessor, PropertyProcessor};
use lyon::geom;
use lyon::geom::point;
use lyon::lyon_tessellation::VertexBuffers;
use lyon::path::path::Builder;
use lyon::path::Path;
@ -85,7 +84,7 @@ impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> ZeroTesse
impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> GeomProcessor
for ZeroTessellator<I>
{
fn xy(&mut self, x: f64, y: f64, idx: usize) -> GeoResult<()> {
fn xy(&mut self, x: f64, y: f64, _idx: usize) -> GeoResult<()> {
// log::info!("xy");
if self.is_point {
@ -167,7 +166,7 @@ impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> GeomProce
Ok(())
}
fn multipolygon_begin(&mut self, size: usize, idx: usize) -> GeoResult<()> {
fn multipolygon_begin(&mut self, _size: usize, _idx: usize) -> GeoResult<()> {
// log::info!("multipolygon_begin");
Ok(())
}

View File

@ -132,7 +132,7 @@ mod tests {
}
"#;
let tilejson: TileJSON = serde_json::from_str(&tilejson_str).unwrap();
let tilejson: TileJSON = serde_json::from_str(tilejson_str).unwrap();
assert_eq!(
tilejson,

View File

@ -1,5 +1,28 @@
use crate::{HTTPClient, MapState, ScheduleMethod};
pub trait MapWindow {
fn size(&self) -> Option<WindowSize>;
type EventLoop;
type Window: raw_window_handle::HasRawWindowHandle;
type MapWindowConfig: MapWindowConfig<MapWindow = Self>;
fn create(map_window_config: &Self::MapWindowConfig) -> Self;
fn size(&self) -> WindowSize;
fn inner(&self) -> &Self::Window;
}
pub trait MapWindowConfig: 'static {
type MapWindow: MapWindow<MapWindowConfig = Self>;
}
pub trait Runnable<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HTTPClient,
{
fn run(self, map_state: MapState<MWC, SM, HC>, max_frames: Option<u64>);
}
#[derive(Clone, Copy)]
@ -24,25 +47,3 @@ impl WindowSize {
self.height
}
}
#[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.
impl Default for WindowSize {
fn default() -> Self {
WindowSize {
width: 100,
height: 100,
}
}
}
pub type WindowFactory<W, E> = dyn FnOnce() -> (W, E);
pub trait FromWindow {
fn from_window(title: &'static str) -> Self;
}
pub trait FromCanvas {
fn from_canvas(dom_id: &'static str) -> Self;
}

View File

@ -1,202 +0,0 @@
//! Main (platform-specific) main loop which handles:
//! * Input (Mouse/Keyboard)
//! * Platform Events like suspend/resume
//! * Render a new frame
use instant::Instant;
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::WindowBuilder;
use crate::input::{InputController, UpdateState};
use crate::map_state::{MapState, Runnable};
use crate::window::FromWindow;
use crate::{HTTPClient, MapBuilder, MapWindow, ScheduleMethod, WindowSize};
impl MapWindow for winit::window::Window {
fn size(&self) -> Option<WindowSize> {
let size = self.inner_size();
WindowSize::new(size.width, size.height)
}
}
impl<SM, HC> Runnable<winit::event_loop::EventLoop<()>> for MapState<winit::window::Window, SM, HC>
where
SM: ScheduleMethod,
HC: HTTPClient,
{
fn run(mut self, event_loop: winit::event_loop::EventLoop<()>, max_frames: Option<u64>) {
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);
event_loop.run(move |event, _, control_flow| {
#[cfg(target_os = "android")]
if !self.is_initialized() && event == Event::Resumed {
use tokio::runtime::Handle;
use tokio::task;
let state = task::block_in_place(|| {
Handle::current().block_on(async {
self.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.window().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) => {
self.resize(physical_size.width, physical_size.height);
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
self.resize(new_inner_size.width, new_inner_size.height);
}
_ => {}
}
}
}
Event::RedrawRequested(_) => {
let _span_ = tracing::span!(tracing::Level::TRACE, "redraw requested").entered();
let now = Instant::now();
let dt = now - last_render_time;
last_render_time = now;
input_controller.update_state(self.view_state_mut(), dt);
match self.update_and_redraw() {
Ok(_) => {}
Err(wgpu::SurfaceError::Lost) => {
log::error!("Surface Lost");
}
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
log::error!("Out of Memory");
*control_flow = ControlFlow::Exit;
}
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(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 => {
self.suspend();
}
Event::Resumed => {
self.recreate_surface();
let size = self.window().inner_size();
self.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
self.resume();
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
self.window().request_redraw();
}
_ => {}
}
});
}
}
#[cfg(not(target_arch = "wasm32"))]
impl<SM, HC> FromWindow
for MapBuilder<winit::window::Window, winit::event_loop::EventLoop<()>, SM, HC>
where
SM: ScheduleMethod,
HC: HTTPClient,
{
fn from_window(title: &'static str) -> Self {
let event_loop = EventLoop::new();
Self::new(Box::new(move || {
let window = WindowBuilder::new()
.with_title(title)
.build(&event_loop)
.unwrap();
(window, event_loop)
}))
}
}
#[cfg(target_arch = "wasm32")]
pub fn get_body_size() -> Option<winit::dpi::LogicalSize<i32>> {
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(),
})
}
#[cfg(target_arch = "wasm32")]
pub fn get_canvas(element_id: &'static 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::<web_sys::HtmlCanvasElement>()
.unwrap()
}
#[cfg(target_arch = "wasm32")]
impl<SM, HC> crate::window::FromCanvas
for MapBuilder<winit::window::Window, winit::event_loop::EventLoop<()>, SM, HC>
where
SM: ScheduleMethod,
HC: HTTPClient,
{
fn from_canvas(dom_id: &'static str) -> Self {
let event_loop = EventLoop::new();
Self::new(Box::new(move || {
use winit::platform::web::WindowBuilderExtWebSys;
let window: winit::window::Window = WindowBuilder::new()
.with_canvas(Some(get_canvas(dom_id)))
.build(&event_loop)
.unwrap();
let size = get_body_size().unwrap();
window.set_inner_size(size);
(window, event_loop)
}))
}
}

View File

@ -20,8 +20,8 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
async-trait = "0.1"
maplibre = { path = "../maplibre", features = ["no-thread-safe-futures"] }
winit = "*"
log = "*"
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
log = "0.4"
console_error_panic_hook = "0.1"
web-sys = { version = "0.3", features = [

View File

@ -28,6 +28,7 @@
"../lib": {
"name": "maplibre-rs",
"version": "0.0.1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"spectorjs": "^0.9.27",
@ -38,6 +39,7 @@
"@chialab/esbuild-plugin-meta-url": "^0.15.15",
"esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1",
"patch-package": "^6.4.7",
"ts-loader": "^9.2.8",
"typescript": "^4.5.4",
"wasm-pack": "^0.10.2"
@ -5893,6 +5895,7 @@
"@chialab/esbuild-plugin-meta-url": "^0.15.15",
"esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1",
"patch-package": "^6.4.7",
"spectorjs": "^0.9.27",
"ts-loader": "^9.2.8",
"typescript": "^4.5.4",

View File

@ -151,5 +151,3 @@ export const startMapLibre = async (wasmPath: string | undefined, workerPath: st
await run(schedulerPtr)
}
export default "test"

View File

@ -1,7 +1,7 @@
//! Errors which can happen in various parts of the library.
use js_sys::Error as JSError;
use wasm_bindgen::prelude::*;
use wasm_bindgen::{JsCast, JsValue};
#[derive(Debug)]

View File

@ -3,8 +3,8 @@ use crate::platform::schedule_method::WebWorkerPoolScheduleMethod;
use maplibre::io::scheduler::Scheduler;
use maplibre::window::FromCanvas;
use maplibre::MapBuilder;
use maplibre_winit::winit::{WinitMapWindow, WinitMapWindowConfig};
use std::panic;
use wasm_bindgen::prelude::*;
@ -54,7 +54,8 @@ pub async fn run(scheduler_ptr: *mut Scheduler<WebWorkerPoolScheduleMethod>) {
unsafe { Box::from_raw(scheduler_ptr) };
// Either call forget or the main loop to keep worker loop alive
MapBuilder::from_canvas("maplibre")
MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(WHATWGFetchHttpClient::new())
.with_existing_scheduler(*scheduler)
.build()