mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
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:
parent
30cdb3da61
commit
feff3201c9
1
.idea/maplibre-rs.iml
generated
1
.idea/maplibre-rs.iml
generated
@ -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" />
|
||||
|
||||
@ -4,6 +4,7 @@ resolver = "2"
|
||||
|
||||
members = [
|
||||
"maplibre",
|
||||
"maplibre-winit",
|
||||
"maplibre-build-tools",
|
||||
"maplibre-demo",
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -8,6 +8,8 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
maplibre = { path = "../maplibre" }
|
||||
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
|
||||
|
||||
env_logger = "0.9"
|
||||
|
||||
[lib]
|
||||
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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
25
maplibre-winit/Cargo.toml
Normal 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"
|
||||
@ -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);
|
||||
@ -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};
|
||||
|
||||
@ -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 {}
|
||||
@ -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};
|
||||
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
@ -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();
|
||||
2
maplibre-winit/src/lib.rs
Normal file
2
maplibre-winit/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod input;
|
||||
pub mod winit;
|
||||
178
maplibre-winit/src/winit/mod.rs
Normal file
178
maplibre-winit/src/winit/mod.rs
Normal 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();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
50
maplibre-winit/src/winit/noweb.rs
Normal file
50
maplibre-winit/src/winit/noweb.rs
Normal 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
|
||||
}
|
||||
}
|
||||
63
maplibre-winit/src/winit/web.rs
Normal file
63
maplibre-winit/src/winit/web.rs
Normal 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()
|
||||
}
|
||||
@ -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" }
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -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 = [
|
||||
|
||||
3
web/demo/package-lock.json
generated
3
web/demo/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -151,5 +151,3 @@ export const startMapLibre = async (wasmPath: string | undefined, workerPath: st
|
||||
|
||||
await run(schedulerPtr)
|
||||
}
|
||||
|
||||
export default "test"
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user