mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
Merge pull request #179 from maxammann/refactor-structure
Refactor structure
This commit is contained in:
commit
1be1476fac
3
.idea/maplibre-rs.iml
generated
3
.idea/maplibre-rs.iml
generated
@ -20,7 +20,8 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/docs/book" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/web/lib/.parcel-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/web/lib/src/wasm-pack" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/web/lib/src/wasm" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/web/lib/dist" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/maplibre-cache" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
20
.idea/runConfigurations/Build_Android.xml
generated
20
.idea/runConfigurations/Build_Android.xml
generated
@ -1,20 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build Android" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="+nightly-2022-02-26 apk build -p maplibre-android -Zbuild-std" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
||||
<option name="allFeatures" value="false" />
|
||||
<option name="emulateTerminal" value="false" />
|
||||
<option name="withSudo" value="false" />
|
||||
<option name="backtrace" value="SHORT" />
|
||||
<envs>
|
||||
<env name="ANDROID_NDK_ROOT" value="$NDK_PATH$" />
|
||||
</envs>
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@ -1,21 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Build WASM (single-threaded)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="build -p web --features web-webgl --lib --profile wasm-dev --target wasm32-unknown-unknown" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
||||
<option name="allFeatures" value="false" />
|
||||
<option name="emulateTerminal" value="false" />
|
||||
<option name="withSudo" value="false" />
|
||||
<option name="buildTarget" value="REMOTE" />
|
||||
<option name="backtrace" value="SHORT" />
|
||||
<envs>
|
||||
<env name="RUSTUP_TOOLCHAIN" value="nightly-2022-07-03-x86_64-unknown-linux-gnu" />
|
||||
</envs>
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@ -11,7 +11,7 @@ maplibre = { path = "../maplibre" }
|
||||
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
|
||||
env_logger = "0.9.0"
|
||||
log = "0.4.17"
|
||||
ndk-glue = "0.5.0" # version is required by winit
|
||||
ndk-glue = "0.7.0" # version is required by winit
|
||||
jni = "0.19.0"
|
||||
|
||||
[lib]
|
||||
|
||||
6
android/gradle/.idea/kotlinc.xml
generated
Normal file
6
android/gradle/.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.7.20" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.0.4' apply true
|
||||
id 'org.jetbrains.kotlin.android' version '1.6.21' apply true
|
||||
id 'com.android.application' version '7.2.0' apply true
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.20' apply true
|
||||
}
|
||||
|
||||
android {
|
||||
@ -34,6 +34,7 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
namespace 'com.example.demo'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.demo">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
|
||||
@ -10,6 +10,6 @@ class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
MapLibreRs.android_main()
|
||||
MapLibreRs.start()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'org.mozilla.rust-android-gradle.rust-android' version '0.9.3' apply true
|
||||
id 'com.android.library' version '7.0.4' apply true
|
||||
id 'org.jetbrains.kotlin.android' version '1.6.21' apply true
|
||||
id 'com.android.library' version '7.2.0' apply true
|
||||
id 'org.jetbrains.kotlin.android' version '1.7.20' apply true
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-android'
|
||||
@ -14,9 +14,7 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@ -27,6 +25,7 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
namespace 'org.maplibre_rs'
|
||||
}
|
||||
|
||||
cargo {
|
||||
@ -35,6 +34,8 @@ cargo {
|
||||
libname = "maplibre_android"
|
||||
targetDirectory = "${module}/../target"
|
||||
profile = "debug"
|
||||
rustupChannel = "nightly-2022-10-23"
|
||||
|
||||
|
||||
features {
|
||||
defaultAnd "foo", "bar"
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="org.maplibre_rs" />
|
||||
<manifest/>
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
package org.maplibre_rs;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
public class MapLibreRs {
|
||||
public static void start() {
|
||||
android_main();
|
||||
}
|
||||
|
||||
public static native void android_main();
|
||||
|
||||
static {
|
||||
|
||||
@ -5,9 +5,8 @@ use log::Level;
|
||||
use maplibre::{
|
||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||
render::settings::{Backends, WgpuSettings},
|
||||
MapBuilder,
|
||||
};
|
||||
use maplibre_winit::winit::{run_headed_map, WinitMapWindowConfig};
|
||||
use maplibre_winit::{run_headed_map, WinitMapWindowConfig};
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
compile_error!("android works only on android.");
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
use maplibre::{
|
||||
io::apc::SchedulerAsyncProcedureCall,
|
||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||
MapBuilder,
|
||||
};
|
||||
use maplibre_winit::winit::{
|
||||
use maplibre_winit::{
|
||||
run_headed_map, WinitEnvironment, WinitEventLoop, WinitMapWindow, WinitMapWindowConfig,
|
||||
WinitWindow,
|
||||
};
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
||||
|
||||
@ -4,7 +4,10 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use maplibre::{
|
||||
coords::{WorldTileCoords, ZoomLevel},
|
||||
error::Error,
|
||||
headless::{utils::HeadlessPipelineProcessor, HeadlessEnvironment, HeadlessMapWindowConfig},
|
||||
headless::{
|
||||
create_headless_renderer, environment::HeadlessEnvironment, map::HeadlessMap,
|
||||
window::HeadlessMapWindowConfig,
|
||||
},
|
||||
io::{
|
||||
apc::SchedulerAsyncProcedureCall,
|
||||
pipeline::{PipelineContext, Processable},
|
||||
@ -12,43 +15,32 @@ use maplibre::{
|
||||
tile_pipelines::build_vector_tile_pipeline,
|
||||
TileRequest,
|
||||
},
|
||||
kernel::{Kernel, KernelBuilder},
|
||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||
render::settings::{RendererSettings, TextureFormat},
|
||||
render::{
|
||||
builder::{InitializedRenderer, RendererBuilder},
|
||||
settings::{RendererSettings, TextureFormat},
|
||||
},
|
||||
style::Style,
|
||||
window::WindowSize,
|
||||
MapBuilder,
|
||||
};
|
||||
|
||||
fn headless_render(c: &mut Criterion) {
|
||||
c.bench_function("headless_render", |b| {
|
||||
let mut map = run_multithreaded(async {
|
||||
let client = ReqwestHttpClient::new(None);
|
||||
let (mut map, tile) = run_multithreaded(async {
|
||||
let (kernel, renderer) = create_headless_renderer(1000, None).await;
|
||||
let style = Style::default();
|
||||
let mut map = HeadlessMap::new(style, renderer, kernel).unwrap();
|
||||
|
||||
let mut map = MapBuilder::<
|
||||
HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>,
|
||||
>::new()
|
||||
.with_map_window_config(HeadlessMapWindowConfig {
|
||||
size: WindowSize::new(1000, 1000).unwrap(),
|
||||
})
|
||||
.with_http_client(client.clone())
|
||||
.with_apc(SchedulerAsyncProcedureCall::new(
|
||||
client,
|
||||
TokioScheduler::new(),
|
||||
))
|
||||
.with_scheduler(TokioScheduler::new())
|
||||
.with_renderer_settings(RendererSettings {
|
||||
texture_format: TextureFormat::Rgba8UnormSrgb,
|
||||
..RendererSettings::default()
|
||||
})
|
||||
.build()
|
||||
.initialize_headless()
|
||||
.await;
|
||||
|
||||
map.map_schedule
|
||||
.fetch_process(&WorldTileCoords::from((0, 0, ZoomLevel::default())))
|
||||
let tile = map
|
||||
.fetch_tile(
|
||||
WorldTileCoords::from((0, 0, ZoomLevel::default())),
|
||||
&["water"],
|
||||
)
|
||||
.await
|
||||
.expect("Failed to fetch and process!");
|
||||
|
||||
map
|
||||
(map, tile)
|
||||
});
|
||||
|
||||
b.to_async(
|
||||
@ -58,7 +50,7 @@ fn headless_render(c: &mut Criterion) {
|
||||
.unwrap(),
|
||||
)
|
||||
.iter(|| {
|
||||
match map.map_schedule_mut().update_and_redraw() {
|
||||
match map.render_tile(tile.clone()) {
|
||||
Ok(_) => {}
|
||||
Err(Error::Render(e)) => {
|
||||
eprintln!("{}", e);
|
||||
|
||||
12
justfile
12
justfile
@ -97,8 +97,16 @@ web-test FEATURES: nightly-toolchain
|
||||
#profile-bench:
|
||||
# cargo flamegraph --bench render -- --bench
|
||||
|
||||
build-android: nightly-toolchain print-android-env
|
||||
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew assembleDebug
|
||||
build-android: build-android-lib build-android-demo
|
||||
|
||||
build-android-lib: nightly-toolchain print-android-env
|
||||
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew :lib:assembleDebug
|
||||
|
||||
build-android-demo: nightly-toolchain print-android-env
|
||||
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew :demo:assembleDebug
|
||||
|
||||
install-android-demo: nightly-toolchain print-android-env
|
||||
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew :demo:installDebug
|
||||
|
||||
test-android TARGET: nightly-toolchain print-android-env
|
||||
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo test -p maplibre-android --target {{TARGET}} -Z build-std=std,panic_abort
|
||||
|
||||
@ -1,37 +1,33 @@
|
||||
use maplibre::{
|
||||
coords::{LatLon, WorldTileCoords},
|
||||
error::Error,
|
||||
headless::{HeadlessEnvironment, HeadlessMapWindowConfig},
|
||||
headless::{create_headless_renderer, map::HeadlessMap, window::HeadlessMapWindowConfig},
|
||||
io::apc::SchedulerAsyncProcedureCall,
|
||||
kernel::KernelBuilder,
|
||||
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
|
||||
render::settings::{RendererSettings, TextureFormat},
|
||||
render::{
|
||||
builder::RendererBuilder,
|
||||
settings::{RendererSettings, TextureFormat},
|
||||
},
|
||||
style::Style,
|
||||
util::grid::google_mercator,
|
||||
window::WindowSize,
|
||||
MapBuilder,
|
||||
};
|
||||
use maplibre_winit::winit::WinitEnvironment;
|
||||
use maplibre_winit::WinitEnvironment;
|
||||
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
|
||||
|
||||
pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
|
||||
let client = ReqwestHttpClient::new(None);
|
||||
let mut map =
|
||||
MapBuilder::<HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>>::new()
|
||||
.with_map_window_config(HeadlessMapWindowConfig {
|
||||
size: WindowSize::new(tile_size, tile_size).unwrap(),
|
||||
})
|
||||
.with_http_client(client.clone())
|
||||
.with_apc(SchedulerAsyncProcedureCall::new(
|
||||
client,
|
||||
TokioScheduler::new(),
|
||||
)) // FIXME (wasm-executor): avoid passing client and scheduler here
|
||||
.with_scheduler(TokioScheduler::new())
|
||||
.with_renderer_settings(RendererSettings {
|
||||
texture_format: TextureFormat::Rgba8UnormSrgb,
|
||||
..RendererSettings::default()
|
||||
})
|
||||
.build()
|
||||
.initialize_headless()
|
||||
.await;
|
||||
let (kernel, renderer) = create_headless_renderer(tile_size, None).await;
|
||||
|
||||
let style = Style::default();
|
||||
|
||||
let requested_layers = style
|
||||
.layers
|
||||
.iter()
|
||||
.map(|layer| layer.source_layer.as_ref().unwrap().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut map = HeadlessMap::new(style, renderer, kernel).unwrap();
|
||||
|
||||
let tile_limits = google_mercator().tile_limits(
|
||||
extent_wgs84_to_merc(&Extent {
|
||||
@ -46,18 +42,17 @@ pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
|
||||
for (z, x, y) in GridIterator::new(10, 10, tile_limits) {
|
||||
let coords = WorldTileCoords::from((x as i32, y as i32, z.into()));
|
||||
println!("Rendering {}", &coords);
|
||||
map.map_schedule
|
||||
.fetch_process(&coords)
|
||||
let tile = map
|
||||
.fetch_tile(
|
||||
coords,
|
||||
&requested_layers
|
||||
.iter()
|
||||
.map(|layer| layer.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to fetch and process!");
|
||||
.expect("Failed to fetch and process");
|
||||
|
||||
match map.map_schedule_mut().update_and_redraw() {
|
||||
Ok(_) => {}
|
||||
Err(Error::Render(e)) => {
|
||||
eprintln!("{}", e);
|
||||
if e.should_exit() {}
|
||||
}
|
||||
e => eprintln!("{:?}", e),
|
||||
};
|
||||
map.render_tile(tile).expect("Rendering failed");
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ use std::io::ErrorKind;
|
||||
|
||||
use clap::{builder::ValueParser, Parser, Subcommand};
|
||||
use maplibre::{coords::LatLon, platform::run_multithreaded};
|
||||
use maplibre_winit::winit::run_headed_map;
|
||||
use maplibre_winit::run_headed_map;
|
||||
|
||||
use crate::headless::run_headless;
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ wasm-bindgen = "0.2.81"
|
||||
wasm-bindgen-futures = "0.4.31"
|
||||
|
||||
[dependencies]
|
||||
maplibre = { path = "../maplibre", version = "0.0.2" }
|
||||
maplibre = { path = "../maplibre", version = "0.0.2", default-features = false }
|
||||
winit = { version = "0.27.2", default-features = false }
|
||||
cgmath = "0.18.0"
|
||||
instant = { version = "0.1.12", features = ["wasm-bindgen"] } # TODO: Untrusted dependency
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::Vector2;
|
||||
use maplibre::context::ViewState;
|
||||
use maplibre::world::ViewState;
|
||||
use winit::event::{DeviceEvent, KeyboardInput, TouchPhase, WindowEvent};
|
||||
|
||||
use crate::input::{
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::{EuclideanSpace, Point3, Vector2, Vector3, Zero};
|
||||
use maplibre::{context::ViewState, render::camera::Camera};
|
||||
use maplibre::{render::camera::Camera, world::ViewState};
|
||||
use winit::event::{ElementState, MouseButton};
|
||||
|
||||
use super::UpdateState;
|
||||
@ -45,17 +45,17 @@ impl UpdateState for PanHandler {
|
||||
};
|
||||
|
||||
if self.start_camera_position.is_none() {
|
||||
self.start_camera_position = Some(state.camera.position.to_vec());
|
||||
self.start_camera_position = Some(state.camera().position().to_vec());
|
||||
}
|
||||
|
||||
if let Some(start_camera_position) = self.start_camera_position {
|
||||
state.camera.position = Point3::from_vec(
|
||||
state.camera_mut().move_to(Point3::from_vec(
|
||||
start_camera_position + Vector3::new(delta.x, delta.y, 0.0),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.reference_camera = Some(state.camera.clone());
|
||||
self.reference_camera = Some(state.camera().clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use maplibre::context::ViewState;
|
||||
use maplibre::world::ViewState;
|
||||
|
||||
use super::UpdateState;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::Vector2;
|
||||
use maplibre::context::ViewState;
|
||||
use maplibre::world::ViewState;
|
||||
use winit::event::{ElementState, MouseButton};
|
||||
|
||||
use crate::input::UpdateState;
|
||||
@ -66,7 +66,7 @@ impl UpdateState for QueryHandler {
|
||||
let _z = state.visible_level(); // FIXME: can be wrong, if tiles of different z are visible
|
||||
let _zoom = state.zoom();
|
||||
|
||||
if let Some(_coordinates) = state.camera.window_to_world_at_ground(
|
||||
if let Some(_coordinates) = state.camera().window_to_world_at_ground(
|
||||
&window_position,
|
||||
&inverted_view_proj,
|
||||
false,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::{Vector3, Zero};
|
||||
use maplibre::context::ViewState;
|
||||
use maplibre::world::ViewState;
|
||||
|
||||
use super::UpdateState;
|
||||
|
||||
@ -17,7 +17,7 @@ impl UpdateState for ShiftHandler {
|
||||
let dt = dt.as_secs_f64() * (1.0 / self.speed);
|
||||
|
||||
let delta = self.camera_translate * dt;
|
||||
state.camera.position += delta;
|
||||
state.camera_mut().move_relative(delta);
|
||||
self.camera_translate -= delta;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::{Deg, Rad, Zero};
|
||||
use maplibre::context::ViewState;
|
||||
use maplibre::world::ViewState;
|
||||
|
||||
use super::UpdateState;
|
||||
|
||||
@ -17,7 +17,7 @@ impl UpdateState for TiltHandler {
|
||||
let dt = dt.as_secs_f64() * (1.0 / self.speed);
|
||||
|
||||
let delta = self.delta_pitch * dt;
|
||||
state.camera.pitch += Rad::from(delta);
|
||||
state.camera_mut().pitch_self(delta);
|
||||
self.delta_pitch -= delta;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use cgmath::{Vector2, Vector3};
|
||||
use maplibre::{context::ViewState, coords::Zoom};
|
||||
use maplibre::{coords::Zoom, world::ViewState};
|
||||
|
||||
use super::UpdateState;
|
||||
|
||||
@ -24,7 +24,7 @@ impl UpdateState for ZoomHandler {
|
||||
let view_proj = state.view_projection();
|
||||
let inverted_view_proj = view_proj.invert();
|
||||
|
||||
if let Some(cursor_position) = state.camera.window_to_world_at_ground(
|
||||
if let Some(cursor_position) = state.camera().window_to_world_at_ground(
|
||||
&window_position,
|
||||
&inverted_view_proj,
|
||||
false,
|
||||
@ -37,7 +37,7 @@ impl UpdateState for ZoomHandler {
|
||||
cursor_position.z,
|
||||
) - cursor_position;
|
||||
|
||||
state.camera.position += delta;
|
||||
state.camera_mut().move_relative(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,221 @@
|
||||
pub mod input;
|
||||
pub mod winit;
|
||||
|
||||
use std::{cell::RefCell, fmt::Debug, marker::PhantomData, ops::Deref, rc::Rc};
|
||||
|
||||
use instant::Instant;
|
||||
use log::info;
|
||||
use maplibre::{
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
event_loop::{EventLoop, EventLoopProxy},
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, Message},
|
||||
scheduler::Scheduler,
|
||||
source_client::HttpClient,
|
||||
transferables::{DefaultTransferables, Transferables},
|
||||
},
|
||||
map::Map,
|
||||
render::{
|
||||
builder::RendererBuilder,
|
||||
settings::{Backends, WgpuSettings},
|
||||
},
|
||||
window::{HeadedMapWindow, MapWindowConfig},
|
||||
};
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
};
|
||||
|
||||
use crate::input::{InputController, UpdateState};
|
||||
|
||||
pub type RawWinitWindow = winit::window::Window;
|
||||
pub type RawWinitEventLoop<ET> = winit::event_loop::EventLoop<ET>;
|
||||
pub type RawEventLoopProxy<ET> = winit::event_loop::EventLoopProxy<ET>;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod web;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod noweb;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use noweb::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web::*;
|
||||
|
||||
pub struct WinitMapWindow<ET: 'static> {
|
||||
window: RawWinitWindow,
|
||||
event_loop: Option<WinitEventLoop<ET>>,
|
||||
}
|
||||
|
||||
impl<ET> WinitMapWindow<ET> {
|
||||
pub fn take_event_loop(&mut self) -> Option<WinitEventLoop<ET>> {
|
||||
self.event_loop.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl<ET> HeadedMapWindow for WinitMapWindow<ET> {
|
||||
type RawWindow = RawWinitWindow;
|
||||
|
||||
fn raw(&self) -> &Self::RawWindow {
|
||||
&self.window
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
self.window.request_redraw()
|
||||
}
|
||||
|
||||
fn id(&self) -> u64 {
|
||||
self.window.id().into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WinitEventLoop<ET: 'static> {
|
||||
event_loop: RawWinitEventLoop<ET>,
|
||||
}
|
||||
|
||||
impl<ET: 'static + PartialEq + Debug> EventLoop<ET> for WinitEventLoop<ET> {
|
||||
type EventLoopProxy = WinitEventLoopProxy<ET>;
|
||||
|
||||
fn run<E>(mut self, mut map: Map<E>, max_frames: Option<u64>)
|
||||
where
|
||||
E: Environment,
|
||||
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
|
||||
{
|
||||
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.event_loop
|
||||
.run(move |event, window_target, control_flow| {
|
||||
#[cfg(target_os = "android")]
|
||||
if !map.has_renderer() && event == Event::Resumed {
|
||||
use tokio::{runtime::Handle, task};
|
||||
|
||||
task::block_in_place(|| {
|
||||
Handle::current().block_on(async {
|
||||
map.initialize_renderer(RendererBuilder::new()
|
||||
.with_wgpu_settings(WgpuSettings {
|
||||
backends: Some(Backends::VULKAN),
|
||||
..WgpuSettings::default()
|
||||
})).await.unwrap();
|
||||
})
|
||||
});
|
||||
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 == map.window().id().into() => {
|
||||
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) => {
|
||||
if let Ok(map_context) = map.context_mut() {
|
||||
map_context.resize(physical_size.width, physical_size.height);
|
||||
}
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
if let Ok(map_context) = map.context_mut() {
|
||||
map_context.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;
|
||||
|
||||
if let Ok(map_context) = map.context_mut() {
|
||||
input_controller.update_state(map_context.world.view_state_mut(), dt);
|
||||
}
|
||||
|
||||
match map.run_schedule() {
|
||||
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 => {
|
||||
// FIXME unimplemented!()
|
||||
}
|
||||
Event::Resumed => {
|
||||
// FIXME unimplemented!()
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
// RedrawRequested will only trigger once, unless we manually
|
||||
// request it.
|
||||
map.window().request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_proxy(&self) -> Self::EventLoopProxy {
|
||||
WinitEventLoopProxy {
|
||||
proxy: self.event_loop.create_proxy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct WinitEventLoopProxy<ET: 'static> {
|
||||
proxy: RawEventLoopProxy<ET>,
|
||||
}
|
||||
|
||||
impl<ET: 'static> EventLoopProxy<ET> for WinitEventLoopProxy<ET> {
|
||||
fn send_event(&self, event: ET) {
|
||||
self.proxy.send_event(event); // FIXME: Handle unwrap
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WinitEnvironment<S: Scheduler, HC: HttpClient, APC: AsyncProcedureCall<HC>, ET> {
|
||||
phantom_s: PhantomData<S>,
|
||||
phantom_hc: PhantomData<HC>,
|
||||
phantom_apc: PhantomData<APC>,
|
||||
phantom_et: PhantomData<ET>,
|
||||
}
|
||||
|
||||
impl<S: Scheduler, HC: HttpClient, APC: AsyncProcedureCall<HC>, ET: 'static> Environment
|
||||
for WinitEnvironment<S, HC, APC, ET>
|
||||
{
|
||||
type MapWindowConfig = WinitMapWindowConfig<ET>;
|
||||
type AsyncProcedureCall = APC;
|
||||
type Scheduler = S;
|
||||
type HttpClient = HC;
|
||||
}
|
||||
|
||||
@ -3,18 +3,42 @@
|
||||
//! * Platform Events like suspend/resume
|
||||
//! * Render a new frame
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use maplibre::{
|
||||
event_loop::EventLoop,
|
||||
io::apc::SchedulerAsyncProcedureCall,
|
||||
kernel::{Kernel, KernelBuilder},
|
||||
map::Map,
|
||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||
render::{
|
||||
builder::{InitializationResult, InitializedRenderer, RendererBuilder},
|
||||
settings::{Backends, RendererSettings, WgpuSettings},
|
||||
},
|
||||
style::Style,
|
||||
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
|
||||
MapBuilder,
|
||||
};
|
||||
use winit::window::WindowBuilder;
|
||||
|
||||
use super::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
|
||||
use crate::winit::WinitEnvironment;
|
||||
use super::{RawWinitEventLoop, RawWinitWindow, WinitMapWindow};
|
||||
use crate::{WinitEnvironment, WinitEventLoop};
|
||||
|
||||
impl MapWindow for WinitMapWindow {
|
||||
pub struct WinitMapWindowConfig<ET> {
|
||||
title: String,
|
||||
|
||||
phantom_et: PhantomData<ET>,
|
||||
}
|
||||
|
||||
impl<ET> WinitMapWindowConfig<ET> {
|
||||
pub fn new(title: String) -> Self {
|
||||
Self {
|
||||
title,
|
||||
phantom_et: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ET> MapWindow for WinitMapWindow<ET> {
|
||||
fn size(&self) -> WindowSize {
|
||||
let size = self.window.inner_size();
|
||||
#[cfg(target_os = "android")]
|
||||
@ -29,27 +53,22 @@ impl MapWindow for WinitMapWindow {
|
||||
window_size
|
||||
}
|
||||
}
|
||||
impl HeadedMapWindow for WinitMapWindow {
|
||||
type RawWindow = WinitWindow;
|
||||
|
||||
fn inner(&self) -> &Self::RawWindow {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindowConfig for WinitMapWindowConfig {
|
||||
type MapWindow = WinitMapWindow;
|
||||
impl<ET: 'static> MapWindowConfig for WinitMapWindowConfig<ET> {
|
||||
type MapWindow = WinitMapWindow<ET>;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
let raw_event_loop = winit::event_loop::EventLoopBuilder::<ET>::with_user_event().build();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(&self.title)
|
||||
.build(&event_loop)
|
||||
.build(&raw_event_loop)
|
||||
.unwrap();
|
||||
|
||||
Self::MapWindow {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
event_loop: Some(WinitEventLoop {
|
||||
event_loop: raw_event_loop,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,7 +76,7 @@ impl MapWindowConfig for WinitMapWindowConfig {
|
||||
pub fn run_headed_map(cache_path: Option<String>) {
|
||||
run_multithreaded(async {
|
||||
let client = ReqwestHttpClient::new(cache_path);
|
||||
MapBuilder::<WinitEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>>::new()
|
||||
let kernel: Kernel<WinitEnvironment<_, _, _, ()>> = KernelBuilder::new()
|
||||
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
|
||||
.with_http_client(client.clone())
|
||||
.with_apc(SchedulerAsyncProcedureCall::new(
|
||||
@ -65,9 +84,23 @@ pub fn run_headed_map(cache_path: Option<String>) {
|
||||
TokioScheduler::new(),
|
||||
))
|
||||
.with_scheduler(TokioScheduler::new())
|
||||
.build()
|
||||
.initialize()
|
||||
.build();
|
||||
|
||||
let mut map = Map::new(Style::default(), kernel).unwrap();
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
map.initialize_renderer(RendererBuilder::new().with_wgpu_settings(WgpuSettings {
|
||||
backends: Some(Backends::VULKAN),
|
||||
..WgpuSettings::default()
|
||||
}))
|
||||
.await
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
map.window_mut()
|
||||
.take_event_loop()
|
||||
.expect("Event loop is not available")
|
||||
.run(map, None)
|
||||
})
|
||||
}
|
||||
@ -1,42 +1,54 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use maplibre::window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};
|
||||
use winit::{platform::web::WindowBuilderExtWebSys, window::WindowBuilder};
|
||||
|
||||
use super::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
|
||||
use super::{RawWinitEventLoop, RawWinitWindow, WinitMapWindow};
|
||||
use crate::WinitEventLoop;
|
||||
|
||||
impl MapWindowConfig for WinitMapWindowConfig {
|
||||
type MapWindow = WinitMapWindow;
|
||||
pub struct WinitMapWindowConfig<ET> {
|
||||
canvas_id: String,
|
||||
phantom_et: PhantomData<ET>,
|
||||
}
|
||||
|
||||
impl<ET: 'static> WinitMapWindowConfig<ET> {
|
||||
pub fn new(canvas_id: String) -> Self {
|
||||
Self {
|
||||
canvas_id,
|
||||
phantom_et: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<ET: 'static> MapWindowConfig for WinitMapWindowConfig<ET> {
|
||||
type MapWindow = WinitMapWindow<ET>;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
let raw_event_loop = winit::event_loop::EventLoopBuilder::<ET>::with_user_event().build();
|
||||
|
||||
let window: winit::window::Window = WindowBuilder::new()
|
||||
.with_canvas(Some(get_canvas(&self.canvas_id)))
|
||||
.build(&event_loop)
|
||||
.build(&raw_event_loop)
|
||||
.unwrap();
|
||||
|
||||
let size = get_body_size().unwrap();
|
||||
window.set_inner_size(size);
|
||||
Self::MapWindow {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
event_loop: Some(WinitEventLoop {
|
||||
event_loop: raw_event_loop,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindow for WinitMapWindow {
|
||||
impl<ET: 'static> MapWindow for WinitMapWindow<ET> {
|
||||
fn size(&self) -> WindowSize {
|
||||
let size = self.window.inner_size();
|
||||
|
||||
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.")
|
||||
}
|
||||
}
|
||||
impl HeadedMapWindow for WinitMapWindow {
|
||||
type RawWindow = WinitWindow;
|
||||
|
||||
fn inner(&self) -> &Self::RawWindow {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_body_size() -> Option<winit::dpi::LogicalSize<i32>> {
|
||||
let web_window: web_sys::Window = web_sys::window().unwrap();
|
||||
@ -1,205 +0,0 @@
|
||||
use std::{cell::RefCell, marker::PhantomData, ops::Deref, rc::Rc};
|
||||
|
||||
use instant::Instant;
|
||||
use maplibre::{
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, Message},
|
||||
scheduler::Scheduler,
|
||||
source_client::HttpClient,
|
||||
transferables::{DefaultTransferables, Transferables},
|
||||
},
|
||||
map_schedule::InteractiveMapSchedule,
|
||||
window::{EventLoop, HeadedMapWindow, MapWindowConfig},
|
||||
};
|
||||
use winit::{
|
||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
};
|
||||
|
||||
use crate::input::{InputController, UpdateState};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod web;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod noweb;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use noweb::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use web::*;
|
||||
|
||||
#[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 }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WinitMapWindow {
|
||||
window: WinitWindow,
|
||||
event_loop: Option<WinitEventLoop>,
|
||||
}
|
||||
|
||||
impl WinitMapWindow {
|
||||
pub fn take_event_loop(&mut self) -> Option<WinitEventLoop> {
|
||||
self.event_loop.take()
|
||||
}
|
||||
}
|
||||
|
||||
pub type WinitWindow = winit::window::Window;
|
||||
pub type WinitEventLoop = winit::event_loop::EventLoop<()>;
|
||||
|
||||
pub struct WinitEnvironment<
|
||||
S: Scheduler,
|
||||
HC: HttpClient,
|
||||
T: Transferables,
|
||||
APC: AsyncProcedureCall<T, HC>,
|
||||
> {
|
||||
phantom_s: PhantomData<S>,
|
||||
phantom_hc: PhantomData<HC>,
|
||||
phantom_t: PhantomData<T>,
|
||||
phantom_apc: PhantomData<APC>,
|
||||
}
|
||||
|
||||
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
|
||||
for WinitEnvironment<S, HC, T, APC>
|
||||
{
|
||||
type MapWindowConfig = WinitMapWindowConfig;
|
||||
type AsyncProcedureCall = APC;
|
||||
type Scheduler = S;
|
||||
type HttpClient = HC;
|
||||
type Transferables = T;
|
||||
}
|
||||
|
||||
///Main (platform-specific) main loop which handles:
|
||||
///* Input (Mouse/Keyboard)
|
||||
///* Platform Events like suspend/resume
|
||||
///* Render a new frame
|
||||
impl<E: Environment> EventLoop<E> for WinitMapWindow
|
||||
where
|
||||
E::MapWindowConfig: MapWindowConfig<MapWindow = WinitMapWindow>,
|
||||
{
|
||||
fn run(
|
||||
mut self,
|
||||
map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
|
||||
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| {
|
||||
let mut map_schedule = map_schedule.deref().borrow_mut();
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
if !map_schedule.is_initialized() && event == Event::Resumed {
|
||||
use tokio::{runtime::Handle, task};
|
||||
|
||||
task::block_in_place(|| {
|
||||
Handle::current().block_on(async {
|
||||
map_schedule.late_init().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_schedule.resize(physical_size.width, physical_size.height);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
map_schedule.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_schedule.view_state_mut(), dt);
|
||||
|
||||
match map_schedule.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_schedule.suspend();
|
||||
}
|
||||
Event::Resumed => {
|
||||
map_schedule.resume(&self);
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
// RedrawRequested will only trigger once, unless we manually
|
||||
// request it.
|
||||
self.inner().request_redraw();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -9,10 +9,11 @@ description = "Native Maps for Web, Mobile and Desktop"
|
||||
readme = "../README.md"
|
||||
|
||||
[features]
|
||||
default = ["thread-safe-futures"]
|
||||
web-webgl = ["wgpu/webgl"]
|
||||
# Enable tracing using tracy on desktop/mobile and the chrome profiler on web
|
||||
trace = ["tracing-subscriber", "tracing-tracy", "tracy-client"]
|
||||
no-thread-safe-futures = []
|
||||
thread-safe-futures = []
|
||||
embed-static-tiles = ["maplibre-build-tools/sqlite"]
|
||||
headless = ["png"]
|
||||
|
||||
|
||||
@ -1,93 +1,19 @@
|
||||
use std::ops::Div;
|
||||
|
||||
use cgmath::Angle;
|
||||
|
||||
use crate::{
|
||||
coords::{LatLon, ViewRegion, WorldCoords, Zoom, ZoomLevel, TILE_SIZE},
|
||||
io::tile_repository::TileRepository,
|
||||
render::camera::{Camera, Perspective, ViewProjection},
|
||||
util::ChangeObserver,
|
||||
Renderer, Style, WindowSize,
|
||||
render::Renderer,
|
||||
style::Style,
|
||||
world::{ViewState, World},
|
||||
};
|
||||
|
||||
/// Stores the camera configuration.
|
||||
pub struct ViewState {
|
||||
pub zoom: ChangeObserver<Zoom>,
|
||||
pub camera: ChangeObserver<Camera>,
|
||||
pub perspective: Perspective,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
pub fn new<P: Into<cgmath::Rad<f64>>>(
|
||||
window_size: &WindowSize,
|
||||
position: WorldCoords,
|
||||
zoom: Zoom,
|
||||
pitch: f64,
|
||||
fovy: P,
|
||||
) -> Self {
|
||||
let tile_center = TILE_SIZE / 2.0;
|
||||
let fovy = fovy.into();
|
||||
let height = tile_center / (fovy / 2.0).tan();
|
||||
|
||||
let camera = Camera::new(
|
||||
(position.x, position.y, height),
|
||||
cgmath::Deg(-90.0),
|
||||
cgmath::Deg(pitch),
|
||||
window_size.width(),
|
||||
window_size.height(),
|
||||
);
|
||||
|
||||
let perspective = Perspective::new(
|
||||
window_size.width(),
|
||||
window_size.height(),
|
||||
cgmath::Deg(110.0),
|
||||
// in tile.vertex.wgsl we are setting each layer's final `z` in ndc space to `z_index`.
|
||||
// This means that regardless of the `znear` value all layers will be rendered as part
|
||||
// of the near plane.
|
||||
// These values have been selected experimentally:
|
||||
// https://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
|
||||
1024.0,
|
||||
2048.0,
|
||||
);
|
||||
|
||||
Self {
|
||||
zoom: ChangeObserver::new(zoom),
|
||||
camera: ChangeObserver::new(camera),
|
||||
perspective,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_view_region(&self) -> Option<ViewRegion> {
|
||||
self.camera
|
||||
.view_region_bounding_box(&self.view_projection().invert())
|
||||
.map(|bounding_box| {
|
||||
ViewRegion::new(bounding_box, 0, 32, *self.zoom, self.visible_level())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_projection(&self) -> ViewProjection {
|
||||
self.camera.calc_view_proj(&self.perspective)
|
||||
}
|
||||
|
||||
pub fn visible_level(&self) -> ZoomLevel {
|
||||
self.zoom.level()
|
||||
}
|
||||
|
||||
pub fn zoom(&self) -> Zoom {
|
||||
*self.zoom
|
||||
}
|
||||
|
||||
pub fn update_zoom(&mut self, new_zoom: Zoom) {
|
||||
*self.zoom = new_zoom;
|
||||
log::info!("zoom: {}", new_zoom);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the context of the map.
|
||||
pub struct MapContext {
|
||||
pub view_state: ViewState,
|
||||
pub style: Style,
|
||||
|
||||
pub tile_repository: TileRepository,
|
||||
pub world: World,
|
||||
pub renderer: Renderer,
|
||||
}
|
||||
|
||||
impl MapContext {
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.world.view_state.resize(width, height);
|
||||
self.renderer.resize(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,31 @@
|
||||
use crate::{
|
||||
event_loop::EventLoopConfig,
|
||||
io::{
|
||||
apc::AsyncProcedureCall,
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||
transferables::{
|
||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, Transferables,
|
||||
},
|
||||
},
|
||||
HttpClient, MapWindowConfig, Scheduler,
|
||||
kernel::Kernel,
|
||||
window::MapWindowConfig,
|
||||
};
|
||||
|
||||
/// The environment defines which types must be injected into maplibre at compile time.
|
||||
/// Essentially, this trait implements the
|
||||
/// [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) design pattern.
|
||||
/// By instantiating this trait at compile time with concrete types, it is possible to create
|
||||
/// different compile-time instances of maplibre.
|
||||
///
|
||||
/// For example it is possible to change the way tasks are scheduled. It is also possible to change
|
||||
/// the HTTP implementation for fetching tiles over the network.
|
||||
pub trait Environment: 'static {
|
||||
type MapWindowConfig: MapWindowConfig;
|
||||
|
||||
type AsyncProcedureCall: AsyncProcedureCall<Self::Transferables, Self::HttpClient>;
|
||||
type Scheduler: Scheduler;
|
||||
type HttpClient: HttpClient;
|
||||
type AsyncProcedureCall: AsyncProcedureCall<Self::HttpClient>;
|
||||
|
||||
type Transferables: Transferables;
|
||||
type Scheduler: Scheduler;
|
||||
|
||||
type HttpClient: HttpClient;
|
||||
}
|
||||
|
||||
@ -1,45 +1,28 @@
|
||||
//! Errors which can happen in various parts of the library.
|
||||
|
||||
use std::{fmt, fmt::Formatter, sync::mpsc::SendError};
|
||||
use std::{borrow::Cow, fmt, fmt::Formatter, sync::mpsc::SendError};
|
||||
|
||||
use lyon::tessellation::TessellationError;
|
||||
|
||||
#[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 {
|
||||
wgpu::SurfaceError::OutOfMemory => true,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::render::{error::RenderError, graph::RenderGraphError};
|
||||
|
||||
/// Enumeration of errors which can happen during the operation of the library.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Schedule,
|
||||
APC,
|
||||
Scheduler,
|
||||
Network(String),
|
||||
Tesselation(TessellationError),
|
||||
Render(RenderError),
|
||||
Generic(Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl From<wgpu::SurfaceError> for Error {
|
||||
fn from(e: wgpu::SurfaceError) -> Self {
|
||||
Error::Render(RenderError::Surface(e))
|
||||
impl<E> From<E> for Error
|
||||
where
|
||||
E: Into<RenderError>,
|
||||
{
|
||||
fn from(e: E) -> Self {
|
||||
Error::Render(e.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +34,6 @@ impl From<TessellationError> for Error {
|
||||
|
||||
impl<T> From<SendError<T>> for Error {
|
||||
fn from(_e: SendError<T>) -> Self {
|
||||
Error::Schedule
|
||||
Error::Scheduler
|
||||
}
|
||||
}
|
||||
|
||||
27
maplibre/src/event_loop.rs
Normal file
27
maplibre/src/event_loop.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use crate::{
|
||||
environment::Environment,
|
||||
map::Map,
|
||||
window::{HeadedMapWindow, MapWindowConfig},
|
||||
};
|
||||
|
||||
pub trait EventLoopConfig {
|
||||
type EventType: 'static;
|
||||
type EventLoopProxy: EventLoopProxy<Self::EventType>;
|
||||
|
||||
fn create_proxy() -> Self::EventLoopProxy;
|
||||
}
|
||||
|
||||
pub trait EventLoopProxy<T: 'static> {
|
||||
fn send_event(&self, event: T);
|
||||
}
|
||||
|
||||
pub trait EventLoop<ET: 'static + PartialEq> {
|
||||
type EventLoopProxy: EventLoopProxy<ET>;
|
||||
|
||||
fn run<E>(self, map: Map<E>, max_frames: Option<u64>)
|
||||
where
|
||||
E: Environment,
|
||||
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow;
|
||||
|
||||
fn create_proxy(&self) -> Self::EventLoopProxy;
|
||||
}
|
||||
@ -1,350 +0,0 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
future::Future,
|
||||
io::Write,
|
||||
iter,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, Range},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use tokio::{runtime::Handle, task};
|
||||
use wgpu::{BufferAsyncError, BufferSlice};
|
||||
|
||||
use crate::{
|
||||
context::{MapContext, ViewState},
|
||||
coords::{LatLon, ViewRegion, WorldCoords, WorldTileCoords, Zoom, TILE_SIZE},
|
||||
error::Error,
|
||||
headless::utils::HeadlessPipelineProcessor,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, SchedulerAsyncProcedureCall},
|
||||
pipeline::{PipelineContext, Processable},
|
||||
source_client::HttpSourceClient,
|
||||
tile_pipelines::build_vector_tile_pipeline,
|
||||
tile_repository::{StoredLayer, TileRepository},
|
||||
transferables::{DefaultTransferables, Transferables},
|
||||
TileRequest,
|
||||
},
|
||||
render::{
|
||||
camera::ViewProjection,
|
||||
create_default_render_graph, draw_graph,
|
||||
eventually::Eventually,
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
register_default_render_stages,
|
||||
resource::{BufferDimensions, BufferedTextureHead, Head, IndexEntry, TrackedRenderPass},
|
||||
stages::RenderStageLabel,
|
||||
RenderState,
|
||||
},
|
||||
schedule::{Schedule, Stage},
|
||||
Environment, HttpClient, MapWindow, MapWindowConfig, Renderer, Scheduler, Style, WindowSize,
|
||||
};
|
||||
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
pub size: WindowSize,
|
||||
}
|
||||
|
||||
impl MapWindowConfig for HeadlessMapWindowConfig {
|
||||
type MapWindow = HeadlessMapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
Self::MapWindow { size: self.size }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessMapWindow {
|
||||
size: WindowSize,
|
||||
}
|
||||
|
||||
impl MapWindow for HeadlessMapWindow {
|
||||
fn size(&self) -> WindowSize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessEnvironment<
|
||||
S: Scheduler,
|
||||
HC: HttpClient,
|
||||
T: Transferables,
|
||||
APC: AsyncProcedureCall<T, HC>,
|
||||
> {
|
||||
phantom_s: PhantomData<S>,
|
||||
phantom_hc: PhantomData<HC>,
|
||||
phantom_t: PhantomData<T>,
|
||||
phantom_apc: PhantomData<APC>,
|
||||
}
|
||||
|
||||
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
|
||||
for HeadlessEnvironment<S, HC, T, APC>
|
||||
{
|
||||
type MapWindowConfig = HeadlessMapWindowConfig;
|
||||
type AsyncProcedureCall = APC;
|
||||
type Scheduler = S;
|
||||
type HttpClient = HC;
|
||||
type Transferables = T;
|
||||
}
|
||||
|
||||
pub struct HeadlessMap<E: Environment> {
|
||||
pub map_schedule: HeadlessMapSchedule<E>,
|
||||
pub window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
|
||||
}
|
||||
|
||||
impl<E: Environment> HeadlessMap<E> {
|
||||
pub fn map_schedule_mut(&mut self) -> &mut HeadlessMapSchedule<E> {
|
||||
&mut self.map_schedule
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
|
||||
pub struct HeadlessMapSchedule<E: Environment> {
|
||||
map_window_config: E::MapWindowConfig,
|
||||
|
||||
pub map_context: MapContext,
|
||||
|
||||
schedule: Schedule,
|
||||
scheduler: E::Scheduler,
|
||||
http_client: E::HttpClient,
|
||||
}
|
||||
|
||||
impl<E: Environment> HeadlessMapSchedule<E> {
|
||||
pub fn new(
|
||||
map_window_config: E::MapWindowConfig,
|
||||
window_size: WindowSize,
|
||||
renderer: Renderer,
|
||||
scheduler: E::Scheduler,
|
||||
http_client: E::HttpClient,
|
||||
style: Style,
|
||||
) -> Self {
|
||||
let view_state = ViewState::new(
|
||||
&window_size,
|
||||
WorldCoords::from((TILE_SIZE / 2., TILE_SIZE / 2.)),
|
||||
Zoom::default(),
|
||||
0.0,
|
||||
cgmath::Deg(110.0),
|
||||
);
|
||||
let tile_repository = TileRepository::new();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
let mut graph = create_default_render_graph().unwrap(); // TODO: remove unwrap
|
||||
let draw_graph = graph.get_sub_graph_mut(draw_graph::NAME).unwrap(); // TODO: remove unwrap
|
||||
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
|
||||
draw_graph
|
||||
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
|
||||
.unwrap(); // TODO: remove unwrap
|
||||
|
||||
register_default_render_stages(graph, &mut schedule);
|
||||
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
WriteSurfaceBufferStage::default(),
|
||||
);
|
||||
|
||||
Self {
|
||||
map_window_config,
|
||||
map_context: MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_repository,
|
||||
renderer,
|
||||
},
|
||||
schedule,
|
||||
scheduler,
|
||||
http_client,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "update_and_redraw", skip_all)]
|
||||
pub fn update_and_redraw(&mut self) -> Result<(), Error> {
|
||||
self.schedule.run(&mut self.map_context);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn schedule(&self) -> &Schedule {
|
||||
&self.schedule
|
||||
}
|
||||
pub fn scheduler(&self) -> &E::Scheduler {
|
||||
&self.scheduler
|
||||
}
|
||||
pub fn http_client(&self) -> &E::HttpClient {
|
||||
&self.http_client
|
||||
}
|
||||
|
||||
pub async fn fetch_process(&mut self, coords: &WorldTileCoords) -> Option<()> {
|
||||
let source_layers: HashSet<String> = self
|
||||
.map_context
|
||||
.style
|
||||
.layers
|
||||
.iter()
|
||||
.filter_map(|layer| layer.source_layer.clone())
|
||||
.collect();
|
||||
|
||||
let http_source_client: HttpSourceClient<E::HttpClient> =
|
||||
HttpSourceClient::new(self.http_client.clone());
|
||||
|
||||
let data = http_source_client
|
||||
.fetch(&coords)
|
||||
.await
|
||||
.unwrap() // TODO: remove unwrap
|
||||
.into_boxed_slice();
|
||||
|
||||
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
|
||||
let request = TileRequest {
|
||||
coords: WorldTileCoords::default(),
|
||||
layers: source_layers,
|
||||
};
|
||||
|
||||
pipeline.process((request, data), &mut pipeline_context);
|
||||
|
||||
let mut processor = pipeline_context
|
||||
.take_processor::<HeadlessPipelineProcessor>()
|
||||
.unwrap(); // TODO: remove unwrap
|
||||
|
||||
if let Eventually::Initialized(pool) = self.map_context.renderer.state.buffer_pool_mut() {
|
||||
pool.clear();
|
||||
}
|
||||
|
||||
self.map_context.tile_repository.clear();
|
||||
|
||||
while let Some(layer) = processor.layers.pop() {
|
||||
self.map_context
|
||||
.tile_repository
|
||||
.create_tile(&layer.get_coords());
|
||||
self.map_context
|
||||
.tile_repository
|
||||
.put_tessellated_layer(layer);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Node which copies the contents of the GPU-side texture in [`BufferedTextureHead`] to an
|
||||
/// unmapped GPU-side buffer. This buffer will be mapped in
|
||||
/// [`crate::render::stages::write_surface_buffer_stage::WriteSurfaceBufferStage`].
|
||||
#[derive(Default)]
|
||||
pub struct CopySurfaceBufferNode {}
|
||||
|
||||
impl CopySurfaceBufferNode {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for CopySurfaceBufferNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
RenderContext {
|
||||
command_encoder, ..
|
||||
}: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let size = surface.size();
|
||||
command_encoder.copy_texture_to_buffer(
|
||||
buffered_texture.texture.as_image_copy(),
|
||||
wgpu::ImageCopyBuffer {
|
||||
buffer: &buffered_texture.output_buffer,
|
||||
layout: wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(
|
||||
std::num::NonZeroU32::new(
|
||||
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
|
||||
)
|
||||
.unwrap(), // TODO: remove unwrap
|
||||
),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: size.width() as u32,
|
||||
height: size.height() as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Stage which writes the current contents of the GPU/CPU buffer in [`BufferedTextureHead`]
|
||||
/// to disk as PNG.
|
||||
#[derive(Default)]
|
||||
pub struct WriteSurfaceBufferStage {
|
||||
frame: u64,
|
||||
}
|
||||
|
||||
impl Stage for WriteSurfaceBufferStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, device, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let buffered_texture: Arc<BufferedTextureHead> = buffered_texture.clone();
|
||||
|
||||
let device = device.clone();
|
||||
let current_frame = self.frame;
|
||||
|
||||
task::block_in_place(|| {
|
||||
Handle::current().block_on(async {
|
||||
buffered_texture
|
||||
.create_png(&device, format!("frame_{}.png", current_frame).as_str())
|
||||
.await;
|
||||
})
|
||||
});
|
||||
|
||||
self.frame += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod utils {
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
io::{pipeline::PipelineProcessor, tile_repository::StoredLayer, RawLayer},
|
||||
render::ShaderVertex,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HeadlessPipelineProcessor {
|
||||
pub layers: Vec<StoredLayer>,
|
||||
}
|
||||
|
||||
impl PipelineProcessor for HeadlessPipelineProcessor {
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: RawLayer,
|
||||
) {
|
||||
self.layers.push(StoredLayer::TessellatedLayer {
|
||||
coords: *coords,
|
||||
layer_name: layer_data.name,
|
||||
buffer,
|
||||
feature_indices,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
15
maplibre/src/headless/environment.rs
Normal file
15
maplibre/src/headless/environment.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use crate::{
|
||||
environment::Environment,
|
||||
headless::window::HeadlessMapWindowConfig,
|
||||
io::apc::SchedulerAsyncProcedureCall,
|
||||
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
|
||||
};
|
||||
|
||||
pub struct HeadlessEnvironment;
|
||||
|
||||
impl Environment for HeadlessEnvironment {
|
||||
type MapWindowConfig = HeadlessMapWindowConfig;
|
||||
type AsyncProcedureCall = SchedulerAsyncProcedureCall<Self::HttpClient, Self::Scheduler>;
|
||||
type Scheduler = TokioScheduler;
|
||||
type HttpClient = ReqwestHttpClient;
|
||||
}
|
||||
65
maplibre/src/headless/graph_node.rs
Normal file
65
maplibre/src/headless/graph_node.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use crate::render::{
|
||||
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
|
||||
resource::Head,
|
||||
RenderState,
|
||||
};
|
||||
|
||||
/// Node which copies the contents of the GPU-side texture in [`BufferedTextureHead`] to an
|
||||
/// unmapped GPU-side buffer. This buffer will be mapped in
|
||||
/// [`crate::render::stages::write_surface_buffer_stage::WriteSurfaceBufferStage`].
|
||||
#[derive(Default)]
|
||||
pub struct CopySurfaceBufferNode {}
|
||||
|
||||
impl CopySurfaceBufferNode {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for CopySurfaceBufferNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn update(&mut self, _state: &mut RenderState) {}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
RenderContext {
|
||||
command_encoder, ..
|
||||
}: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let size = surface.size();
|
||||
command_encoder.copy_texture_to_buffer(
|
||||
buffered_texture.texture.as_image_copy(),
|
||||
wgpu::ImageCopyBuffer {
|
||||
buffer: &buffered_texture.output_buffer,
|
||||
layout: wgpu::ImageDataLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(
|
||||
std::num::NonZeroU32::new(
|
||||
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
|
||||
)
|
||||
.unwrap(), // TODO: remove unwrap
|
||||
),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: size.width() as u32,
|
||||
height: size.height() as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
151
maplibre/src/headless/map.rs
Normal file
151
maplibre/src/headless/map.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::{WorldCoords, WorldTileCoords, Zoom, TILE_SIZE},
|
||||
error::Error,
|
||||
headless::{
|
||||
environment::HeadlessEnvironment, graph_node::CopySurfaceBufferNode,
|
||||
stage::WriteSurfaceBufferStage,
|
||||
},
|
||||
io::{
|
||||
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
||||
tile_pipelines::build_vector_tile_pipeline,
|
||||
tile_repository::{StoredLayer, StoredTile, TileStatus},
|
||||
RawLayer, TileRequest,
|
||||
},
|
||||
kernel::Kernel,
|
||||
render::{
|
||||
builder::UninitializedRenderer, create_default_render_graph, draw_graph,
|
||||
error::RenderError, eventually::Eventually, register_default_render_stages, resource::Head,
|
||||
stages::RenderStageLabel, Renderer, ShaderVertex,
|
||||
},
|
||||
schedule::{Schedule, Stage},
|
||||
style::Style,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
window::WindowSize,
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub struct HeadlessMap {
|
||||
kernel: Kernel<HeadlessEnvironment>,
|
||||
schedule: Schedule,
|
||||
map_context: MapContext,
|
||||
}
|
||||
|
||||
impl HeadlessMap {
|
||||
pub fn new(
|
||||
style: Style,
|
||||
renderer: Renderer,
|
||||
kernel: Kernel<HeadlessEnvironment>,
|
||||
) -> Result<Self, Error> {
|
||||
let window_size = renderer.state().surface().size();
|
||||
|
||||
let world = World::new(
|
||||
window_size,
|
||||
WorldCoords::from((TILE_SIZE / 2., TILE_SIZE / 2.)),
|
||||
Zoom::default(),
|
||||
cgmath::Deg(0.0),
|
||||
);
|
||||
|
||||
let mut graph = create_default_render_graph()?;
|
||||
let draw_graph = graph
|
||||
.get_sub_graph_mut(draw_graph::NAME)
|
||||
.expect("Subgraph does not exist");
|
||||
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
|
||||
draw_graph
|
||||
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
|
||||
.unwrap(); // TODO: remove unwrap
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
register_default_render_stages(graph, &mut schedule);
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
WriteSurfaceBufferStage::default(),
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
kernel,
|
||||
map_context: MapContext {
|
||||
style,
|
||||
world,
|
||||
renderer,
|
||||
},
|
||||
schedule,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn render_tile(&mut self, tile: StoredTile) -> Result<(), Error> {
|
||||
let context = &mut self.map_context;
|
||||
|
||||
if let Eventually::Initialized(pool) = context.renderer.state.buffer_pool_mut() {
|
||||
pool.clear();
|
||||
} else {
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
context.world.tile_repository.clear();
|
||||
|
||||
context.world.tile_repository.put_tile(tile);
|
||||
|
||||
self.schedule.run(&mut self.map_context);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fetch_tile(
|
||||
&self,
|
||||
coords: WorldTileCoords,
|
||||
source_layers: &[&str],
|
||||
) -> Result<StoredTile, Error> {
|
||||
let source_client = self.kernel.source_client();
|
||||
|
||||
let data = source_client.fetch(&coords).await?.into_boxed_slice();
|
||||
|
||||
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
|
||||
let target_coords = WorldTileCoords::default(); // load to 0,0,0
|
||||
pipeline.process(
|
||||
(
|
||||
TileRequest {
|
||||
coords: target_coords,
|
||||
layers: source_layers
|
||||
.iter()
|
||||
.map(|layer| layer.to_string())
|
||||
.collect::<HashSet<String>>(),
|
||||
},
|
||||
data,
|
||||
),
|
||||
&mut pipeline_context,
|
||||
);
|
||||
|
||||
let mut processor = pipeline_context
|
||||
.take_processor::<HeadlessPipelineProcessor>()
|
||||
.expect("Unable to get processor");
|
||||
|
||||
Ok(StoredTile::success(target_coords, processor.layers))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HeadlessPipelineProcessor {
|
||||
pub layers: Vec<StoredLayer>,
|
||||
}
|
||||
|
||||
impl PipelineProcessor for HeadlessPipelineProcessor {
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: RawLayer,
|
||||
) -> Result<(), Error> {
|
||||
self.layers.push(StoredLayer::TessellatedLayer {
|
||||
coords: *coords,
|
||||
layer_name: layer_data.name,
|
||||
buffer,
|
||||
feature_indices,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
50
maplibre/src/headless/mod.rs
Normal file
50
maplibre/src/headless/mod.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use crate::{
|
||||
headless::{
|
||||
environment::HeadlessEnvironment,
|
||||
window::{HeadlessMapWindow, HeadlessMapWindowConfig},
|
||||
},
|
||||
io::apc::SchedulerAsyncProcedureCall,
|
||||
kernel::{Kernel, KernelBuilder},
|
||||
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
|
||||
render::{
|
||||
builder::{InitializedRenderer, RendererBuilder},
|
||||
Renderer,
|
||||
},
|
||||
window::{MapWindowConfig, WindowSize},
|
||||
};
|
||||
|
||||
mod graph_node;
|
||||
mod stage;
|
||||
|
||||
pub mod environment;
|
||||
pub mod map;
|
||||
pub mod window;
|
||||
|
||||
pub async fn create_headless_renderer(
|
||||
tile_size: u32,
|
||||
cache_path: Option<String>,
|
||||
) -> (Kernel<HeadlessEnvironment>, Renderer) {
|
||||
let client = ReqwestHttpClient::new(cache_path);
|
||||
let kernel = KernelBuilder::new()
|
||||
.with_map_window_config(HeadlessMapWindowConfig::new(
|
||||
WindowSize::new(tile_size, tile_size).unwrap(),
|
||||
))
|
||||
.with_http_client(client.clone())
|
||||
.with_apc(SchedulerAsyncProcedureCall::new(
|
||||
client,
|
||||
TokioScheduler::new(),
|
||||
))
|
||||
.with_scheduler(TokioScheduler::new())
|
||||
.build();
|
||||
|
||||
let mwc: &HeadlessMapWindowConfig = kernel.map_window_config();
|
||||
let window: HeadlessMapWindow = mwc.create();
|
||||
|
||||
let renderer = RendererBuilder::new()
|
||||
.build()
|
||||
.initialize_headless::<HeadlessMapWindowConfig>(&window)
|
||||
.await
|
||||
.expect("Failed to initialize renderer");
|
||||
|
||||
(kernel, renderer)
|
||||
}
|
||||
50
maplibre/src/headless/stage.rs
Normal file
50
maplibre/src/headless/stage.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::{runtime::Handle, task};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
resource::{BufferedTextureHead, Head},
|
||||
Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
};
|
||||
|
||||
/// Stage which writes the current contents of the GPU/CPU buffer in [`BufferedTextureHead`]
|
||||
/// to disk as PNG.
|
||||
#[derive(Default)]
|
||||
pub struct WriteSurfaceBufferStage {
|
||||
frame: u64,
|
||||
}
|
||||
|
||||
impl Stage for WriteSurfaceBufferStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, device, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let surface = state.surface();
|
||||
match surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let buffered_texture: Arc<BufferedTextureHead> = buffered_texture.clone();
|
||||
|
||||
let device = device.clone();
|
||||
let current_frame = self.frame;
|
||||
|
||||
task::block_in_place(|| {
|
||||
Handle::current().block_on(async {
|
||||
buffered_texture
|
||||
.create_png(&device, format!("frame_{}.png", current_frame).as_str())
|
||||
.await;
|
||||
})
|
||||
});
|
||||
|
||||
self.frame += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
maplibre/src/headless/window.rs
Normal file
29
maplibre/src/headless/window.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::window::{MapWindow, MapWindowConfig, WindowSize};
|
||||
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
size: WindowSize,
|
||||
}
|
||||
|
||||
impl HeadlessMapWindowConfig {
|
||||
pub fn new(size: WindowSize) -> Self {
|
||||
Self { size }
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindowConfig for HeadlessMapWindowConfig {
|
||||
type MapWindow = HeadlessMapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
Self::MapWindow { size: self.size }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessMapWindow {
|
||||
size: WindowSize,
|
||||
}
|
||||
|
||||
impl MapWindow for HeadlessMapWindow {
|
||||
fn size(&self) -> WindowSize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
@ -12,17 +12,21 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
error::Error,
|
||||
io::{
|
||||
source_client::{HttpSourceClient, SourceClient},
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||
transferables::{DefaultTransferables, Transferables},
|
||||
TileRequest,
|
||||
},
|
||||
Environment, HttpClient, Scheduler,
|
||||
};
|
||||
|
||||
/// The result of the tessellation of a tile.
|
||||
/// `TessellatedLayer` contains the result of the tessellation for a specific layer, otherwise
|
||||
/// `UnavailableLayer` if the layer doesn't exist.
|
||||
/// The result of the tessellation of a tile. This is sent as a message from a worker to the caller
|
||||
/// of an [`AsyncProcedure`].
|
||||
///
|
||||
/// * `TessellatedLayer` contains the result of the tessellation for a specific layer.
|
||||
/// * `UnavailableLayer` is sent if a requested layer is not found.
|
||||
/// * `TileTessellated` is sent if processing of a tile finished.
|
||||
#[derive(Clone)]
|
||||
pub enum Message<T: Transferables> {
|
||||
TileTessellated(T::TileTessellated),
|
||||
@ -30,30 +34,82 @@ pub enum Message<T: Transferables> {
|
||||
TessellatedLayer(T::TessellatedLayer),
|
||||
}
|
||||
|
||||
/// Inputs for an [`AsyncProcedure`]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub enum Input {
|
||||
TileRequest(TileRequest),
|
||||
}
|
||||
|
||||
/// Allows sending messages from workers to back to the caller.
|
||||
pub trait Context<T: Transferables, HC: HttpClient>: Send + 'static {
|
||||
fn send(&self, data: Message<T>);
|
||||
/// Send a message back to the caller.
|
||||
// FIXME (wasm-executor): handle results send() calls
|
||||
fn send(&self, data: Message<T>) -> Result<(), Error>;
|
||||
|
||||
fn source_client(&self) -> &SourceClient<HC>;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no-thread-safe-futures"))]
|
||||
#[cfg(feature = "thread-safe-futures")]
|
||||
pub type AsyncProcedureFuture = Pin<Box<(dyn Future<Output = ()> + Send + 'static)>>;
|
||||
#[cfg(feature = "no-thread-safe-futures")]
|
||||
#[cfg(not(feature = "thread-safe-futures"))]
|
||||
pub type AsyncProcedureFuture = Pin<Box<(dyn Future<Output = ()> + 'static)>>;
|
||||
|
||||
/// Type definitions for asynchronous procedure calls. These functions can be called in an
|
||||
/// [`AsyncProcedureCall`]. Functions of this type are required to be statically available at
|
||||
/// compile time. It is explicitly not possible to use closures, as they would require special
|
||||
/// serialization which is currently not supported.
|
||||
pub type AsyncProcedure<C> = fn(input: Input, context: C) -> AsyncProcedureFuture;
|
||||
|
||||
pub trait AsyncProcedureCall<T: Transferables, HC: HttpClient>: 'static {
|
||||
type Context: Context<T, HC> + Send;
|
||||
/// APCs define an interface for performing work asynchronously.
|
||||
/// This work can be implemented through procedures which can be called asynchronously, hence the
|
||||
/// name AsyncProcedureCall or APC for short.
|
||||
///
|
||||
/// APCs serve as an abstraction for doing work on a separate thread, and then getting responses
|
||||
/// back. An asynchronous procedure call can for example be performed by using message passing. In
|
||||
/// fact this could theoretically work over a network socket.
|
||||
///
|
||||
/// It is possible to schedule work on a remote host by calling [`AsyncProcedureCall::call()`]
|
||||
/// and getting the results back by calling the non-blocking function
|
||||
/// [`AsyncProcedureCall::receive()`]. The [`AsyncProcedureCall::receive()`] function returns a
|
||||
/// struct which implements [`Transferables`].
|
||||
///
|
||||
/// ## Transferables
|
||||
///
|
||||
/// Based on whether the current platform supports shared-memory or not, the implementation of APCs
|
||||
/// might want to send the whole data from the worker to the caller back or just pointers to that
|
||||
/// data. The [`Transferables`] trait allows developers to define that and use different data
|
||||
/// layouts for different platforms.
|
||||
///
|
||||
/// ## Message Passing vs APC
|
||||
///
|
||||
/// One might wonder why this is called [`AsyncProcedureCall`] instead of `MessagePassingInterface`.
|
||||
/// The reason for this is quite simple. We are actually referencing and calling procedures which
|
||||
/// are defined in different threads, processes or hosts. That means, that an [`AsyncProcedureCall`]
|
||||
/// is actually distinct from a `MessagePassingInterface`.
|
||||
///
|
||||
///
|
||||
/// ## Current Implementations
|
||||
///
|
||||
/// We currently have two implementation for APCs. One uses the Tokio async runtime on native
|
||||
/// targets in [`SchedulerAsyncProcedureCall`].
|
||||
/// For the web we implemented an alternative way to call APCs which is called
|
||||
/// [`PassingAsyncProcedureCall`]. This implementation does not depend on shared-memory compared to
|
||||
/// [`SchedulerAsyncProcedureCall`]. In fact, on the web we are currently not depending on
|
||||
/// shared-memory because that feature is hidden behind feature flags in browsers
|
||||
/// (see [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).
|
||||
///
|
||||
///
|
||||
// TODO: Rename to AsyncProcedureCaller?
|
||||
pub trait AsyncProcedureCall<HC: HttpClient>: 'static {
|
||||
type Context: Context<Self::Transferables, HC> + Send;
|
||||
type Transferables: Transferables;
|
||||
|
||||
fn receive(&mut self) -> Option<Message<T>>;
|
||||
/// Try to receive a message non-blocking.
|
||||
fn receive(&self) -> Option<Message<Self::Transferables>>;
|
||||
|
||||
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>);
|
||||
/// Call an [`AsyncProcedure`] using some [`Input`]. This function is non-blocking and
|
||||
/// returns immediately.
|
||||
fn call(&self, input: Input, procedure: AsyncProcedure<Self::Context>);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -63,8 +119,8 @@ pub struct SchedulerContext<T: Transferables, HC: HttpClient> {
|
||||
}
|
||||
|
||||
impl<T: Transferables, HC: HttpClient> Context<T, HC> for SchedulerContext<T, HC> {
|
||||
fn send(&self, data: Message<T>) {
|
||||
self.sender.send(data).unwrap(); // FIXME (wasm-executor): Remove unwrap
|
||||
fn send(&self, data: Message<T>) -> Result<(), Error> {
|
||||
self.sender.send(data).map_err(|e| Error::APC)
|
||||
}
|
||||
|
||||
fn source_client(&self) -> &SourceClient<HC> {
|
||||
@ -91,19 +147,18 @@ impl<HC: HttpClient, S: Scheduler> SchedulerAsyncProcedureCall<HC, S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<DefaultTransferables, HC>
|
||||
for SchedulerAsyncProcedureCall<HC, S>
|
||||
{
|
||||
type Context = SchedulerContext<DefaultTransferables, HC>;
|
||||
impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<HC> for SchedulerAsyncProcedureCall<HC, S> {
|
||||
type Context = SchedulerContext<Self::Transferables, HC>;
|
||||
type Transferables = DefaultTransferables;
|
||||
|
||||
fn receive(&mut self) -> Option<Message<DefaultTransferables>> {
|
||||
fn receive(&self) -> Option<Message<DefaultTransferables>> {
|
||||
let transferred = self.channel.1.try_recv().ok()?;
|
||||
Some(transferred)
|
||||
}
|
||||
|
||||
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
|
||||
fn call(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
|
||||
let sender = self.channel.0.clone();
|
||||
let client = self.http_client.clone(); // FIXME (wasm-executor): do not clone each time
|
||||
let client = self.http_client.clone(); // TODO (perf): do not clone each time
|
||||
|
||||
self.scheduler
|
||||
.schedule(move || async move {
|
||||
@ -111,7 +166,7 @@ impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<DefaultTransferables, HC>
|
||||
input,
|
||||
SchedulerContext {
|
||||
sender,
|
||||
source_client: SourceClient::Http(HttpSourceClient::new(client)),
|
||||
source_client: SourceClient::new(HttpSourceClient::new(client)),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -5,28 +5,40 @@ use geozero::mvt::tile;
|
||||
|
||||
use crate::{
|
||||
coords::WorldTileCoords,
|
||||
error::Error,
|
||||
io::geometry_index::IndexedGeometry,
|
||||
render::ShaderVertex,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
};
|
||||
|
||||
/// Processes events which happen during the pipeline execution
|
||||
// FIXME (wasm-executor): handle results for messages below
|
||||
pub trait PipelineProcessor: Downcast {
|
||||
fn tile_finished(&mut self, _coords: &WorldTileCoords) {}
|
||||
fn layer_unavailable(&mut self, _coords: &WorldTileCoords, _layer_name: &str) {}
|
||||
fn tile_finished(&mut self, _coords: &WorldTileCoords) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn layer_unavailable(
|
||||
&mut self,
|
||||
_coords: &WorldTileCoords,
|
||||
_layer_name: &str,
|
||||
) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
_coords: &WorldTileCoords,
|
||||
_buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
_feature_indices: Vec<u32>,
|
||||
_layer_data: tile::Layer,
|
||||
) {
|
||||
) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn layer_indexing_finished(
|
||||
&mut self,
|
||||
_coords: &WorldTileCoords,
|
||||
_geometries: Vec<IndexedGeometry<f64>>,
|
||||
) {
|
||||
) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ use crate::error::Error;
|
||||
/// Async/await scheduler.
|
||||
/// Can schedule a task from a future factory and a shared state.
|
||||
pub trait Scheduler: 'static {
|
||||
#[cfg(not(feature = "no-thread-safe-futures"))]
|
||||
#[cfg(feature = "thread-safe-futures")]
|
||||
fn schedule<T>(
|
||||
&self,
|
||||
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
||||
@ -15,7 +15,7 @@ pub trait Scheduler: 'static {
|
||||
where
|
||||
T: Future<Output = ()> + Send + 'static;
|
||||
|
||||
#[cfg(feature = "no-thread-safe-futures")]
|
||||
#[cfg(not(feature = "thread-safe-futures"))]
|
||||
fn schedule<T>(
|
||||
&self,
|
||||
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
||||
@ -31,6 +31,6 @@ impl Scheduler for NopScheduler {
|
||||
where
|
||||
T: Future<Output = ()> + 'static,
|
||||
{
|
||||
Err(Error::Schedule)
|
||||
Err(Error::Scheduler)
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,9 @@ pub type HTTPClientFactory<HC> = dyn Fn() -> HC;
|
||||
/// [https://github.com/dtolnay/async-trait/blob/b70720c4c1cc0d810b7446efda44f81310ee7bf2/README.md#non-threadsafe-futures](https://github.com/dtolnay/async-trait/blob/b70720c4c1cc0d810b7446efda44f81310ee7bf2/README.md#non-threadsafe-futures)
|
||||
///
|
||||
/// Users of this library can decide whether futures from the HTTPClient are thread-safe or not via
|
||||
/// the future "no-thread-safe-futures". Tokio futures are thread-safe.
|
||||
#[cfg_attr(feature = "no-thread-safe-futures", async_trait(?Send))]
|
||||
#[cfg_attr(not(feature = "no-thread-safe-futures"), async_trait)]
|
||||
/// the future "thread-safe-futures". Tokio futures are thread-safe.
|
||||
#[cfg_attr(not(feature = "thread-safe-futures"), async_trait(?Send))]
|
||||
#[cfg_attr(feature = "thread-safe-futures", async_trait)]
|
||||
pub trait HttpClient: Clone + Sync + Send + 'static {
|
||||
async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error>;
|
||||
}
|
||||
@ -32,25 +32,23 @@ where
|
||||
/// Defines the different types of HTTP clients such as basic HTTP and Mbtiles.
|
||||
/// More types might be coming such as S3 and other cloud http clients.
|
||||
#[derive(Clone)]
|
||||
pub enum SourceClient<HC>
|
||||
pub struct SourceClient<HC>
|
||||
where
|
||||
HC: HttpClient,
|
||||
{
|
||||
Http(HttpSourceClient<HC>),
|
||||
Mbtiles {
|
||||
// TODO
|
||||
},
|
||||
http: HttpSourceClient<HC>, // TODO: mbtiles: Mbtiles
|
||||
}
|
||||
|
||||
impl<HC> SourceClient<HC>
|
||||
where
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub fn new(http: HttpSourceClient<HC>) -> Self {
|
||||
Self { http }
|
||||
}
|
||||
|
||||
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
|
||||
match self {
|
||||
SourceClient::Http(client) => client.fetch(coords).await,
|
||||
SourceClient::Mbtiles { .. } => unimplemented!(),
|
||||
}
|
||||
self.http.fetch(coords).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +45,7 @@ impl Processable for IndexLayer {
|
||||
) -> Self::Output {
|
||||
let index = IndexProcessor::new();
|
||||
|
||||
// FIXME: Handle result
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_indexing_finished(&tile_request.coords, index.get_geometries());
|
||||
@ -78,6 +79,7 @@ impl Processable for TessellateLayer {
|
||||
|
||||
let mut tessellator = ZeroTessellator::<IndexDataType>::default();
|
||||
if let Err(e) = layer.process(&mut tessellator) {
|
||||
// FIXME: Handle result
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_unavailable(coords, layer_name);
|
||||
@ -89,12 +91,13 @@ impl Processable for TessellateLayer {
|
||||
e
|
||||
);
|
||||
} else {
|
||||
// FIXME: Handle result
|
||||
context.processor_mut().layer_tesselation_finished(
|
||||
coords,
|
||||
tessellator.buffer.into(),
|
||||
tessellator.feature_indices,
|
||||
cloned_layer,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +108,7 @@ impl Processable for TessellateLayer {
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for missing_layer in tile_request.layers.difference(&available_layers) {
|
||||
// FIXME: Handle result
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_unavailable(coords, missing_layer);
|
||||
@ -118,6 +122,7 @@ impl Processable for TessellateLayer {
|
||||
|
||||
tracing::info!("tile tessellated at {} finished", &tile_request.coords);
|
||||
|
||||
// FIXME: Handle result
|
||||
context.processor_mut().tile_finished(&tile_request.coords);
|
||||
|
||||
(tile_request, tile)
|
||||
|
||||
@ -11,6 +11,7 @@ use crate::{
|
||||
};
|
||||
|
||||
/// A layer which is stored for future use.
|
||||
#[derive(Clone)]
|
||||
pub enum StoredLayer {
|
||||
UnavailableLayer {
|
||||
coords: WorldTileCoords,
|
||||
@ -41,7 +42,7 @@ impl StoredLayer {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum TileStatus {
|
||||
Pending,
|
||||
Failed,
|
||||
@ -49,18 +50,37 @@ pub enum TileStatus {
|
||||
}
|
||||
|
||||
/// Stores multiple [StoredLayers](StoredLayer).
|
||||
#[derive(Clone)]
|
||||
pub struct StoredTile {
|
||||
coords: WorldTileCoords,
|
||||
layers: Vec<StoredLayer>,
|
||||
status: TileStatus,
|
||||
}
|
||||
|
||||
impl StoredTile {
|
||||
pub fn new() -> Self {
|
||||
pub fn pending(coords: WorldTileCoords) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
layers: vec![],
|
||||
status: TileStatus::Pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn success(coords: WorldTileCoords, layers: Vec<StoredLayer>) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
layers,
|
||||
status: TileStatus::Success,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn failed(coords: WorldTileCoords) -> Self {
|
||||
Self {
|
||||
coords,
|
||||
layers: vec![],
|
||||
status: TileStatus::Failed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores and provides access to a quad tree of cached tiles with world tile coords.
|
||||
@ -82,10 +102,10 @@ impl TileRepository {
|
||||
|
||||
/// Inserts a tessellated layer into the quad tree at its world tile coords.
|
||||
/// If the space is vacant, the tessellated layer is inserted into a new
|
||||
/// [crate::io::tile_repository::CachedTile].
|
||||
/// [crate::io::tile_repository::StoredLayer].
|
||||
/// If the space is occupied, the tessellated layer is added to the current
|
||||
/// [crate::io::tile_repository::CachedTile].
|
||||
pub fn put_tessellated_layer(&mut self, layer: StoredLayer) {
|
||||
/// [crate::io::tile_repository::StoredLayer].
|
||||
pub fn put_layer(&mut self, layer: StoredLayer) {
|
||||
if let Some(entry) = layer
|
||||
.get_coords()
|
||||
.build_quad_key()
|
||||
@ -102,9 +122,15 @@ impl TileRepository {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_tile(&mut self, tile: StoredTile) {
|
||||
if let Some(key) = tile.coords.build_quad_key() {
|
||||
self.tree.insert(key, tile);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of tessellated layers at the given world tile coords. None if tile is
|
||||
/// missing from the cache.
|
||||
pub fn iter_tessellated_layers_at(
|
||||
pub fn iter_layers_at(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
) -> Option<impl Iterator<Item = &StoredLayer> + '_> {
|
||||
@ -115,11 +141,11 @@ impl TileRepository {
|
||||
}
|
||||
|
||||
/// Create a new tile.
|
||||
pub fn create_tile(&mut self, coords: &WorldTileCoords) -> bool {
|
||||
pub fn create_tile(&mut self, coords: WorldTileCoords) -> bool {
|
||||
if let Some(entry) = coords.build_quad_key().map(|key| self.tree.entry(key)) {
|
||||
match entry {
|
||||
btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(StoredTile::new());
|
||||
entry.insert(StoredTile::pending(coords));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -128,14 +154,14 @@ impl TileRepository {
|
||||
}
|
||||
|
||||
/// Checks if a layer has been fetched.
|
||||
pub fn needs_fetching(&self, coords: &WorldTileCoords) -> bool {
|
||||
pub fn has_tile(&self, coords: &WorldTileCoords) -> bool {
|
||||
if let Some(_) = coords.build_quad_key().and_then(|key| self.tree.get(&key)) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn success(&mut self, coords: &WorldTileCoords) {
|
||||
pub fn mark_tile_succeeded(&mut self, coords: &WorldTileCoords) {
|
||||
if let Some(cached_tile) = coords
|
||||
.build_quad_key()
|
||||
.and_then(|key| self.tree.get_mut(&key))
|
||||
@ -145,7 +171,7 @@ impl TileRepository {
|
||||
}
|
||||
|
||||
/// Checks if a layer has been fetched.
|
||||
pub fn fail(&mut self, coords: &WorldTileCoords) {
|
||||
pub fn mark_tile_failed(&mut self, coords: &WorldTileCoords) {
|
||||
if let Some(cached_tile) = coords
|
||||
.build_quad_key()
|
||||
.and_then(|key| self.tree.get_mut(&key))
|
||||
@ -153,42 +179,4 @@ impl TileRepository {
|
||||
cached_tile.status = TileStatus::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all the cached tessellate layers that are not contained within the given
|
||||
/// layers hashset.
|
||||
pub fn retain_missing_layer_names(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
layers: &mut HashSet<String>,
|
||||
) {
|
||||
if let Some(cached_tile) = coords.build_quad_key().and_then(|key| self.tree.get(&key)) {
|
||||
let tessellated_set: HashSet<String> = cached_tile
|
||||
.layers
|
||||
.iter()
|
||||
.map(|tessellated_layer| tessellated_layer.layer_name().to_string())
|
||||
.collect();
|
||||
|
||||
layers.retain(|layer| !tessellated_set.contains(layer));
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if a layer is missing from the given layers set at the given coords.
|
||||
pub fn is_layers_missing(&self, coords: &WorldTileCoords, layers: &HashSet<String>) -> bool {
|
||||
if let Some(cached_tile) = coords.build_quad_key().and_then(|key| self.tree.get(&key)) {
|
||||
let tessellated_set: HashSet<&str> = cached_tile
|
||||
.layers
|
||||
.iter()
|
||||
.map(|tessellated_layer| tessellated_layer.layer_name())
|
||||
.collect();
|
||||
|
||||
for layer in layers {
|
||||
if !tessellated_set.contains(layer.as_str()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ pub struct DefaultTessellatedLayer {
|
||||
pub buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
/// Holds for each feature the count of indices.
|
||||
pub feature_indices: Vec<u32>,
|
||||
pub layer_data: Layer, // FIXME (wasm-executor): Introduce a better structure for this
|
||||
pub layer_data: Layer, // FIXME (perf): Introduce a better structure for this
|
||||
}
|
||||
|
||||
impl TessellatedLayer for DefaultTessellatedLayer {
|
||||
|
||||
85
maplibre/src/kernel.rs
Normal file
85
maplibre/src/kernel.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use crate::{
|
||||
environment::Environment,
|
||||
io::source_client::{HttpSourceClient, SourceClient},
|
||||
};
|
||||
|
||||
/// Holds references to core constructs of maplibre. Based on the compile-time initialization
|
||||
/// different implementations for handling windows, asynchronous work, or data sources are provided
|
||||
/// through a [`Kernel`].
|
||||
///
|
||||
/// An [`Environment`] defines the types which are used.
|
||||
///
|
||||
/// A Kernel lives as long as a [Map](crate::map::Map) usually. It is shared through out various
|
||||
/// components of the maplibre library.
|
||||
pub struct Kernel<E: Environment> {
|
||||
map_window_config: E::MapWindowConfig,
|
||||
apc: E::AsyncProcedureCall,
|
||||
scheduler: E::Scheduler,
|
||||
source_client: SourceClient<E::HttpClient>,
|
||||
}
|
||||
|
||||
impl<E: Environment> Kernel<E> {
|
||||
pub fn map_window_config(&self) -> &E::MapWindowConfig {
|
||||
&self.map_window_config
|
||||
}
|
||||
|
||||
pub fn apc(&self) -> &E::AsyncProcedureCall {
|
||||
&self.apc
|
||||
}
|
||||
|
||||
pub fn scheduler(&self) -> &E::Scheduler {
|
||||
&self.scheduler
|
||||
}
|
||||
|
||||
pub fn source_client(&self) -> &SourceClient<E::HttpClient> {
|
||||
&self.source_client
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenient builder for [Kernels](Kernel).
|
||||
pub struct KernelBuilder<E: Environment> {
|
||||
map_window_config: Option<E::MapWindowConfig>,
|
||||
apc: Option<E::AsyncProcedureCall>,
|
||||
scheduler: Option<E::Scheduler>,
|
||||
http_client: Option<E::HttpClient>,
|
||||
}
|
||||
|
||||
impl<E: Environment> KernelBuilder<E> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scheduler: None,
|
||||
apc: None,
|
||||
http_client: None,
|
||||
map_window_config: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_map_window_config(mut self, map_window_config: E::MapWindowConfig) -> Self {
|
||||
self.map_window_config = Some(map_window_config);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_scheduler(mut self, scheduler: E::Scheduler) -> Self {
|
||||
self.scheduler = Some(scheduler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_apc(mut self, apc: E::AsyncProcedureCall) -> Self {
|
||||
self.apc = Some(apc);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_http_client(mut self, http_client: E::HttpClient) -> Self {
|
||||
self.http_client = Some(http_client);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Kernel<E> {
|
||||
Kernel {
|
||||
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
|
||||
apc: self.apc.unwrap(), // TODO: Remove unwrap
|
||||
source_client: SourceClient::new(HttpSourceClient::new(self.http_client.unwrap())), // TODO: Remove unwrap
|
||||
map_window_config: self.map_window_config.unwrap(), // TODO: Remove unwrap
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,23 +16,8 @@
|
||||
//! maplibre = "0.0.2"
|
||||
//! ```
|
||||
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
environment::Environment,
|
||||
io::{scheduler::Scheduler, source_client::HttpClient},
|
||||
map_schedule::InteractiveMapSchedule,
|
||||
render::{
|
||||
settings::{RendererSettings, WgpuSettings},
|
||||
RenderState, Renderer,
|
||||
},
|
||||
style::Style,
|
||||
window::{EventLoop, HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
|
||||
};
|
||||
// Internal modules
|
||||
pub(crate) mod tessellation;
|
||||
|
||||
pub mod context;
|
||||
pub mod coords;
|
||||
@ -40,10 +25,8 @@ pub mod error;
|
||||
#[cfg(feature = "headless")]
|
||||
pub mod headless;
|
||||
pub mod io;
|
||||
// Exposed because of input handlers in maplibre-winit
|
||||
pub mod map_schedule;
|
||||
pub mod platform;
|
||||
// Exposed because of camera
|
||||
// TODO: Exposed because of camera
|
||||
pub mod render;
|
||||
pub mod style;
|
||||
pub mod util;
|
||||
@ -54,209 +37,15 @@ pub mod schedule;
|
||||
// Exposed because of SharedThreadState
|
||||
pub mod stages;
|
||||
|
||||
pub mod environment;
|
||||
|
||||
// Used for benchmarking
|
||||
pub mod benchmarking;
|
||||
|
||||
// Internal modules
|
||||
pub(crate) mod tessellation;
|
||||
|
||||
pub mod environment;
|
||||
pub mod event_loop;
|
||||
pub mod kernel;
|
||||
pub mod map;
|
||||
pub mod world;
|
||||
|
||||
// Export tile format
|
||||
pub use geozero::mvt::tile;
|
||||
|
||||
/// The [`Map`] defines the public interface of the map renderer.
|
||||
// DO NOT IMPLEMENT INTERNALS ON THIS STRUCT.
|
||||
pub struct Map<E: Environment> {
|
||||
// FIXME (wasm-executor): Avoid RefCell, change ownership model!
|
||||
map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
|
||||
window: RefCell<Option<<E::MapWindowConfig as MapWindowConfig>::MapWindow>>,
|
||||
}
|
||||
|
||||
impl<E: Environment> Map<E>
|
||||
where
|
||||
<E::MapWindowConfig as MapWindowConfig>::MapWindow: EventLoop<E>,
|
||||
{
|
||||
/// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
|
||||
pub fn run(&self) {
|
||||
self.run_with_optionally_max_frames(None);
|
||||
}
|
||||
|
||||
/// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `max_frames` - Maximum number of frames per second.
|
||||
pub fn run_with_max_frames(&self, max_frames: u64) {
|
||||
self.run_with_optionally_max_frames(Some(max_frames));
|
||||
}
|
||||
|
||||
/// Starts the MapState Runnable with the configured event loop.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `max_frames` - Optional maximum number of frames per second.
|
||||
pub fn run_with_optionally_max_frames(&self, max_frames: Option<u64>) {
|
||||
self.window
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
||||
.run(self.map_schedule.clone(), max_frames);
|
||||
}
|
||||
|
||||
pub fn map_schedule(&self) -> Rc<RefCell<InteractiveMapSchedule<E>>> {
|
||||
self.map_schedule.clone()
|
||||
}
|
||||
|
||||
/* pub fn map_schedule_mut(&mut self) -> &mut InteractiveMapSchedule<E> {
|
||||
&mut self.map_schedule
|
||||
}*/
|
||||
}
|
||||
|
||||
/// Stores the map configuration before the map's state has been fully initialized.
|
||||
pub struct UninitializedMap<E: Environment> {
|
||||
scheduler: E::Scheduler,
|
||||
apc: E::AsyncProcedureCall,
|
||||
http_client: E::HttpClient,
|
||||
style: Style,
|
||||
|
||||
wgpu_settings: WgpuSettings,
|
||||
renderer_settings: RendererSettings,
|
||||
map_window_config: E::MapWindowConfig,
|
||||
}
|
||||
|
||||
impl<E: Environment> UninitializedMap<E>
|
||||
where
|
||||
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
|
||||
{
|
||||
/// Initializes the whole rendering pipeline for the given configuration.
|
||||
/// Returns the initialized map, ready to be run.
|
||||
pub async fn initialize(self) -> Map<E> {
|
||||
let window = self.map_window_config.create();
|
||||
let window_size = window.size();
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
let renderer = None;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
let renderer = Renderer::initialize(
|
||||
&window,
|
||||
self.wgpu_settings.clone(),
|
||||
self.renderer_settings.clone(),
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
Map {
|
||||
map_schedule: Rc::new(RefCell::new(InteractiveMapSchedule::new(
|
||||
self.map_window_config,
|
||||
window_size,
|
||||
renderer,
|
||||
self.scheduler,
|
||||
self.apc,
|
||||
self.http_client,
|
||||
self.style,
|
||||
self.wgpu_settings,
|
||||
self.renderer_settings,
|
||||
))),
|
||||
window: RefCell::new(Some(window)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
impl<E: Environment> UninitializedMap<E> {
|
||||
pub async fn initialize_headless(self) -> headless::HeadlessMap<E> {
|
||||
let window = self.map_window_config.create();
|
||||
let window_size = window.size();
|
||||
|
||||
let renderer = Renderer::initialize_headless(
|
||||
&window,
|
||||
self.wgpu_settings.clone(),
|
||||
self.renderer_settings.clone(),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to initialize renderer");
|
||||
headless::HeadlessMap {
|
||||
map_schedule: headless::HeadlessMapSchedule::new(
|
||||
self.map_window_config,
|
||||
window_size,
|
||||
renderer,
|
||||
self.scheduler,
|
||||
self.http_client,
|
||||
self.style,
|
||||
),
|
||||
window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MapBuilder<E: Environment> {
|
||||
scheduler: Option<E::Scheduler>,
|
||||
apc: Option<E::AsyncProcedureCall>,
|
||||
http_client: Option<E::HttpClient>,
|
||||
style: Option<Style>,
|
||||
|
||||
map_window_config: Option<E::MapWindowConfig>,
|
||||
wgpu_settings: Option<WgpuSettings>,
|
||||
renderer_settings: Option<RendererSettings>,
|
||||
}
|
||||
|
||||
impl<E: Environment> MapBuilder<E> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
scheduler: None,
|
||||
apc: None,
|
||||
http_client: None,
|
||||
style: None,
|
||||
map_window_config: None,
|
||||
wgpu_settings: None,
|
||||
renderer_settings: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_map_window_config(mut self, map_window_config: E::MapWindowConfig) -> Self {
|
||||
self.map_window_config = Some(map_window_config);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_renderer_settings(mut self, renderer_settings: RendererSettings) -> Self {
|
||||
self.renderer_settings = Some(renderer_settings);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_wgpu_settings(mut self, wgpu_settings: WgpuSettings) -> Self {
|
||||
self.wgpu_settings = Some(wgpu_settings);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_scheduler(mut self, scheduler: E::Scheduler) -> Self {
|
||||
self.scheduler = Some(scheduler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_apc(mut self, apc: E::AsyncProcedureCall) -> Self {
|
||||
self.apc = Some(apc);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_http_client(mut self, http_client: E::HttpClient) -> Self {
|
||||
self.http_client = Some(http_client);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_style(mut self, style: Style) -> Self {
|
||||
self.style = Some(style);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the UninitializedMap with the given configuration.
|
||||
pub fn build(self) -> UninitializedMap<E> {
|
||||
UninitializedMap {
|
||||
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
|
||||
apc: self.apc.unwrap(), // TODO: Remove unwrap
|
||||
http_client: self.http_client.unwrap(), // TODO: Remove unwrap
|
||||
style: self.style.unwrap_or_default(),
|
||||
wgpu_settings: self.wgpu_settings.unwrap_or_default(),
|
||||
renderer_settings: self.renderer_settings.unwrap_or_default(),
|
||||
map_window_config: self.map_window_config.unwrap(), // TODO: Remove unwrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
149
maplibre/src/map.rs
Normal file
149
maplibre/src/map.rs
Normal file
@ -0,0 +1,149 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
kernel::Kernel,
|
||||
render::{
|
||||
builder::{
|
||||
InitializationResult, InitializedRenderer, RendererBuilder, UninitializedRenderer,
|
||||
},
|
||||
create_default_render_graph, register_default_render_stages,
|
||||
settings::{RendererSettings, WgpuSettings},
|
||||
Renderer,
|
||||
},
|
||||
schedule::{Schedule, Stage},
|
||||
stages::register_stages,
|
||||
style::Style,
|
||||
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub enum MapContextState {
|
||||
Ready(MapContext),
|
||||
Pending { style: Style },
|
||||
}
|
||||
|
||||
pub struct Map<E: Environment> {
|
||||
kernel: Rc<Kernel<E>>,
|
||||
schedule: Schedule,
|
||||
map_context: MapContextState,
|
||||
window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
|
||||
}
|
||||
|
||||
impl<E: Environment> Map<E>
|
||||
where
|
||||
<<E as Environment>::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
|
||||
{
|
||||
pub fn new(style: Style, kernel: Kernel<E>) -> Result<Self, Error> {
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
let graph = create_default_render_graph().unwrap(); // TODO: Remove unwrap
|
||||
register_default_render_stages(graph, &mut schedule);
|
||||
|
||||
let kernel = Rc::new(kernel);
|
||||
|
||||
register_stages::<E>(&mut schedule, kernel.clone());
|
||||
|
||||
let mut window = kernel.map_window_config().create();
|
||||
|
||||
let map = Self {
|
||||
kernel,
|
||||
map_context: MapContextState::Pending { style },
|
||||
schedule,
|
||||
window,
|
||||
};
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
pub async fn initialize_renderer(
|
||||
&mut self,
|
||||
render_builder: RendererBuilder,
|
||||
) -> Result<(), Error> {
|
||||
let result = render_builder
|
||||
.build()
|
||||
.initialize_renderer::<E::MapWindowConfig>(&self.window)
|
||||
.await
|
||||
.expect("Failed to initialize renderer");
|
||||
|
||||
match &mut self.map_context {
|
||||
MapContextState::Ready(_) => Err(Error::Generic("Renderer is already set".into())),
|
||||
MapContextState::Pending { style } => {
|
||||
let window_size = self.window.size();
|
||||
|
||||
let center = style.center.unwrap_or_default();
|
||||
|
||||
let world = World::new_at(
|
||||
window_size,
|
||||
LatLon::new(center[0], center[1]),
|
||||
style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default(),
|
||||
cgmath::Deg::<f64>(style.pitch.unwrap_or_default()),
|
||||
);
|
||||
|
||||
match result {
|
||||
InitializationResult::Initialized(InitializedRenderer { renderer, .. }) => {
|
||||
*&mut self.map_context = MapContextState::Ready(MapContext {
|
||||
world,
|
||||
style: std::mem::take(style),
|
||||
renderer,
|
||||
});
|
||||
}
|
||||
InitializationResult::Uninizalized(UninitializedRenderer { .. }) => {}
|
||||
_ => panic!("Rendering context gone"),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_mut(&mut self) -> &mut <E::MapWindowConfig as MapWindowConfig>::MapWindow {
|
||||
&mut self.window
|
||||
}
|
||||
pub fn window(&self) -> &<E::MapWindowConfig as MapWindowConfig>::MapWindow {
|
||||
&self.window
|
||||
}
|
||||
|
||||
pub fn has_renderer(&self) -> bool {
|
||||
match &self.map_context {
|
||||
MapContextState::Ready(_) => true,
|
||||
MapContextState::Pending { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "update_and_redraw", skip_all)]
|
||||
pub fn run_schedule(&mut self) -> Result<(), Error> {
|
||||
match &mut self.map_context {
|
||||
MapContextState::Ready(map_context) => {
|
||||
self.schedule.run(map_context);
|
||||
Ok(())
|
||||
}
|
||||
MapContextState::Pending { .. } => {
|
||||
Err(Error::Generic("Renderer is already set".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Result<&MapContext, Error> {
|
||||
match &self.map_context {
|
||||
MapContextState::Ready(map_context) => Ok(map_context),
|
||||
MapContextState::Pending { .. } => {
|
||||
Err(Error::Generic("Renderer is already set".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context_mut(&mut self) -> Result<&mut MapContext, Error> {
|
||||
match &mut self.map_context {
|
||||
MapContextState::Ready(map_context) => Ok(map_context),
|
||||
MapContextState::Pending { .. } => {
|
||||
Err(Error::Generic("Renderer is already set".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kernel(&self) -> &Rc<Kernel<E>> {
|
||||
&self.kernel
|
||||
}
|
||||
}
|
||||
@ -1,207 +1,26 @@
|
||||
use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc};
|
||||
/* pub fn resume(&mut self, window: &<E::MapWindowConfig as MapWindowConfig>::MapWindow) {
|
||||
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
|
||||
let renderer = &mut map_context.renderer;
|
||||
renderer.state.recreate_surface(window, &renderer.instance);
|
||||
}
|
||||
}*/
|
||||
|
||||
use crate::{
|
||||
context::{MapContext, ViewState},
|
||||
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
|
||||
error::Error,
|
||||
io::{
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, HttpSourceClient},
|
||||
tile_repository::TileRepository,
|
||||
},
|
||||
render::{create_default_render_graph, register_default_render_stages},
|
||||
schedule::{Schedule, Stage},
|
||||
stages::register_stages,
|
||||
style::Style,
|
||||
Environment, HeadedMapWindow, MapWindowConfig, Renderer, RendererSettings, WgpuSettings,
|
||||
WindowSize,
|
||||
};
|
||||
|
||||
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
|
||||
pub struct InteractiveMapSchedule<E: Environment> {
|
||||
map_window_config: E::MapWindowConfig,
|
||||
|
||||
// FIXME (wasm-executor): avoid RefCell, change ownership model
|
||||
pub apc: Rc<RefCell<E::AsyncProcedureCall>>,
|
||||
|
||||
map_context: EventuallyMapContext,
|
||||
|
||||
schedule: Schedule,
|
||||
|
||||
suspended: bool,
|
||||
}
|
||||
|
||||
impl<E: Environment> InteractiveMapSchedule<E> {
|
||||
pub fn new(
|
||||
map_window_config: E::MapWindowConfig,
|
||||
window_size: WindowSize,
|
||||
renderer: Option<Renderer>,
|
||||
scheduler: E::Scheduler, // TODO: unused
|
||||
apc: E::AsyncProcedureCall,
|
||||
http_client: E::HttpClient,
|
||||
style: Style,
|
||||
wgpu_settings: WgpuSettings,
|
||||
renderer_settings: RendererSettings,
|
||||
) -> Self {
|
||||
let zoom = style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default();
|
||||
let position = style
|
||||
.center
|
||||
.map(|center| WorldCoords::from_lat_lon(LatLon::new(center[0], center[1]), zoom))
|
||||
.unwrap_or_default();
|
||||
let pitch = style.pitch.unwrap_or_default();
|
||||
let view_state = ViewState::new(&window_size, position, zoom, pitch, cgmath::Deg(110.0));
|
||||
|
||||
let tile_repository = TileRepository::new();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
let apc = Rc::new(RefCell::new(apc));
|
||||
|
||||
let http_source_client: HttpSourceClient<E::HttpClient> =
|
||||
HttpSourceClient::new(http_client);
|
||||
register_stages::<E>(&mut schedule, http_source_client, apc.clone());
|
||||
|
||||
let graph = create_default_render_graph().unwrap(); // TODO: Remove unwrap
|
||||
register_default_render_stages(graph, &mut schedule);
|
||||
|
||||
Self {
|
||||
apc,
|
||||
map_window_config,
|
||||
map_context: match renderer {
|
||||
None => EventuallyMapContext::Premature(PrematureMapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_repository,
|
||||
wgpu_settings,
|
||||
renderer_settings,
|
||||
}),
|
||||
Some(renderer) => EventuallyMapContext::Full(MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_repository,
|
||||
renderer,
|
||||
}),
|
||||
},
|
||||
schedule,
|
||||
suspended: false,
|
||||
/* pub async fn late_init(&mut self) -> bool {
|
||||
match &self.map_context {
|
||||
EventuallyMapContext::Full(_) => false,
|
||||
EventuallyMapContext::Uninizalized(PrematureMapContext {
|
||||
wgpu_settings,
|
||||
renderer_settings,
|
||||
..
|
||||
}) => {
|
||||
let window = self.map_window_config.create();
|
||||
let renderer =
|
||||
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
|
||||
.await
|
||||
.unwrap(); // TODO: Remove unwrap
|
||||
self.map_context.make_full(renderer);
|
||||
true
|
||||
}
|
||||
EventuallyMapContext::_Uninitialized => false,
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "update_and_redraw", skip_all)]
|
||||
pub fn update_and_redraw(&mut self) -> Result<(), Error> {
|
||||
if self.suspended {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
|
||||
self.schedule.run(map_context)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
|
||||
let view_state = &mut map_context.view_state;
|
||||
view_state.perspective.resize(width, height);
|
||||
view_state.camera.resize(width, height);
|
||||
|
||||
map_context.renderer.resize(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
match &self.map_context {
|
||||
EventuallyMapContext::Full(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_state_mut(&mut self) -> &mut ViewState {
|
||||
match &mut self.map_context {
|
||||
EventuallyMapContext::Full(MapContext { view_state, .. }) => view_state,
|
||||
EventuallyMapContext::Premature(PrematureMapContext { view_state, .. }) => view_state,
|
||||
_ => panic!("should not happen"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apc(&self) -> &Rc<RefCell<E::AsyncProcedureCall>> {
|
||||
&self.apc
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Environment> InteractiveMapSchedule<E>
|
||||
where
|
||||
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
|
||||
{
|
||||
pub fn suspend(&mut self) {
|
||||
self.suspended = true;
|
||||
}
|
||||
|
||||
pub fn resume(&mut self, window: &<E::MapWindowConfig as MapWindowConfig>::MapWindow) {
|
||||
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
|
||||
let renderer = &mut map_context.renderer;
|
||||
renderer.state.recreate_surface(window, &renderer.instance);
|
||||
self.suspended = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn late_init(&mut self) -> bool {
|
||||
match &self.map_context {
|
||||
EventuallyMapContext::Full(_) => false,
|
||||
EventuallyMapContext::Premature(PrematureMapContext {
|
||||
wgpu_settings,
|
||||
renderer_settings,
|
||||
..
|
||||
}) => {
|
||||
let window = self.map_window_config.create();
|
||||
let renderer =
|
||||
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
|
||||
.await
|
||||
.unwrap(); // TODO: Remove unwrap
|
||||
self.map_context.make_full(renderer);
|
||||
true
|
||||
}
|
||||
EventuallyMapContext::_Uninitialized => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrematureMapContext {
|
||||
view_state: ViewState,
|
||||
style: Style,
|
||||
|
||||
tile_repository: TileRepository,
|
||||
|
||||
wgpu_settings: WgpuSettings,
|
||||
renderer_settings: RendererSettings,
|
||||
}
|
||||
|
||||
pub enum EventuallyMapContext {
|
||||
Full(MapContext),
|
||||
Premature(PrematureMapContext),
|
||||
_Uninitialized,
|
||||
}
|
||||
|
||||
impl EventuallyMapContext {
|
||||
pub fn make_full(&mut self, renderer: Renderer) {
|
||||
let context = mem::replace(self, EventuallyMapContext::_Uninitialized);
|
||||
|
||||
match context {
|
||||
EventuallyMapContext::Full(_) => {}
|
||||
EventuallyMapContext::Premature(PrematureMapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_repository,
|
||||
..
|
||||
}) => {
|
||||
*self = EventuallyMapContext::Full(MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_repository,
|
||||
renderer,
|
||||
});
|
||||
}
|
||||
EventuallyMapContext::_Uninitialized => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
@ -3,7 +3,7 @@ use reqwest::{Client, StatusCode};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use reqwest_middleware_cache::{managers::CACacheManager, Cache, CacheMode};
|
||||
|
||||
use crate::{error::Error, HttpClient};
|
||||
use crate::{error::Error, io::source_client::HttpClient};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReqwestHttpClient {
|
||||
@ -23,6 +23,7 @@ impl From<reqwest_middleware::Error> for Error {
|
||||
|
||||
impl ReqwestHttpClient {
|
||||
/// cache_path: Under which path should we cache requests.
|
||||
// TODO: Use Into<Path> instead of String
|
||||
pub fn new(cache_path: Option<String>) -> Self {
|
||||
let mut builder = reqwest_middleware::ClientBuilder::new(Client::new());
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use std::future::Future;
|
||||
|
||||
use crate::{error::Error, Scheduler};
|
||||
use crate::{error::Error, io::scheduler::Scheduler};
|
||||
|
||||
/// Multi-threading with Tokio.
|
||||
pub struct TokioScheduler;
|
||||
|
||||
126
maplibre/src/render/builder.rs
Normal file
126
maplibre/src/render/builder.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
kernel::Kernel,
|
||||
render::{
|
||||
settings::{RendererSettings, WgpuSettings},
|
||||
Renderer,
|
||||
},
|
||||
style::Style,
|
||||
window::{HeadedMapWindow, MapWindow, MapWindowConfig},
|
||||
};
|
||||
|
||||
pub struct RendererBuilder {
|
||||
wgpu_settings: Option<WgpuSettings>,
|
||||
renderer_settings: Option<RendererSettings>,
|
||||
}
|
||||
|
||||
impl RendererBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
wgpu_settings: None,
|
||||
renderer_settings: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_renderer_settings(mut self, renderer_settings: RendererSettings) -> Self {
|
||||
self.renderer_settings = Some(renderer_settings);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_wgpu_settings(mut self, wgpu_settings: WgpuSettings) -> Self {
|
||||
self.wgpu_settings = Some(wgpu_settings);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> UninitializedRenderer {
|
||||
UninitializedRenderer {
|
||||
wgpu_settings: self.wgpu_settings.unwrap_or_default(),
|
||||
renderer_settings: self.renderer_settings.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum InitializationResult {
|
||||
Initialized(InitializedRenderer),
|
||||
Uninizalized(UninitializedRenderer),
|
||||
Gone,
|
||||
}
|
||||
|
||||
impl Default for InitializationResult {
|
||||
fn default() -> Self {
|
||||
Self::Gone
|
||||
}
|
||||
}
|
||||
|
||||
impl InitializationResult {
|
||||
pub fn unwarp_renderer(self) -> InitializedRenderer {
|
||||
match self {
|
||||
InitializationResult::Initialized(renderer) => renderer,
|
||||
InitializationResult::Uninizalized(_) => panic!("Renderer is not initialized"),
|
||||
InitializationResult::Gone => panic!("Initialization context is gone"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_option(self) -> Option<Renderer> {
|
||||
match self {
|
||||
InitializationResult::Initialized(InitializedRenderer { renderer, .. }) => {
|
||||
Some(renderer)
|
||||
}
|
||||
InitializationResult::Uninizalized(_) => None,
|
||||
InitializationResult::Gone => panic!("Initialization context is gone"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UninitializedRenderer {
|
||||
pub wgpu_settings: WgpuSettings,
|
||||
pub renderer_settings: RendererSettings,
|
||||
}
|
||||
|
||||
impl UninitializedRenderer {
|
||||
/// Initializes the whole rendering pipeline for the given configuration.
|
||||
/// Returns the initialized map, ready to be run.
|
||||
pub async fn initialize_renderer<MWC>(
|
||||
self,
|
||||
existing_window: &MWC::MapWindow,
|
||||
) -> Result<InitializationResult, Error>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
|
||||
{
|
||||
let renderer = Renderer::initialize(
|
||||
existing_window,
|
||||
self.wgpu_settings.clone(),
|
||||
self.renderer_settings.clone(),
|
||||
)
|
||||
.await?;
|
||||
Ok(InitializationResult::Initialized(InitializedRenderer {
|
||||
renderer,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
impl UninitializedRenderer {
|
||||
pub(crate) async fn initialize_headless<MWC>(
|
||||
self,
|
||||
existing_window: &MWC::MapWindow,
|
||||
) -> Result<Renderer, Error>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
{
|
||||
Ok(Renderer::initialize_headless(
|
||||
existing_window,
|
||||
self.wgpu_settings.clone(),
|
||||
self.renderer_settings.clone(),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InitializedRenderer {
|
||||
pub renderer: Renderer,
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
//! Main camera
|
||||
|
||||
use cgmath::{prelude::*, AbsDiffEq, Matrix4, Point2, Point3, Vector2, Vector3, Vector4};
|
||||
use cgmath::{prelude::*, AbsDiffEq, Matrix4, Point2, Point3, Rad, Vector2, Vector3, Vector4};
|
||||
|
||||
use crate::util::{
|
||||
math::{bounds_from_points, Aabb2, Aabb3, Plane},
|
||||
@ -68,12 +68,12 @@ impl ModelViewProjection {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Camera {
|
||||
pub position: Point3<f64>, // The z axis never changes, the zoom is used instead
|
||||
pub yaw: cgmath::Rad<f64>,
|
||||
pub pitch: cgmath::Rad<f64>,
|
||||
position: Point3<f64>, // The z axis never changes, the zoom is used instead
|
||||
yaw: cgmath::Rad<f64>,
|
||||
pitch: cgmath::Rad<f64>,
|
||||
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
width: f64,
|
||||
height: f64,
|
||||
}
|
||||
|
||||
impl SignificantlyDifferent for Camera {
|
||||
@ -103,6 +103,14 @@ impl Camera {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_to(&mut self, point: Point3<f64>) {
|
||||
self.position = point;
|
||||
}
|
||||
|
||||
pub fn move_relative(&mut self, delta: Vector3<f64>) {
|
||||
self.position += delta;
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.width = width as f64;
|
||||
self.height = height as f64;
|
||||
@ -347,6 +355,26 @@ impl Camera {
|
||||
Point2::new(max_x, max_y),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn position(&self) -> Point3<f64> {
|
||||
self.position
|
||||
}
|
||||
|
||||
pub fn yaw(&self) -> cgmath::Rad<f64> {
|
||||
self.yaw
|
||||
}
|
||||
|
||||
pub fn yaw_self<P: Into<Rad<f64>>>(&mut self, delta: P) {
|
||||
self.yaw += delta.into();
|
||||
}
|
||||
|
||||
pub fn pitch(&self) -> cgmath::Rad<f64> {
|
||||
self.pitch
|
||||
}
|
||||
|
||||
pub fn pitch_self<P: Into<Rad<f64>>>(&mut self, delta: P) {
|
||||
self.pitch += delta.into();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Perspective {
|
||||
|
||||
50
maplibre/src/render/error.rs
Normal file
50
maplibre/src/render/error.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::{error::Error, render::graph::RenderGraphError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RenderError {
|
||||
Surface(wgpu::SurfaceError),
|
||||
Graph(RenderGraphError),
|
||||
Device(wgpu::RequestDeviceError),
|
||||
}
|
||||
|
||||
impl fmt::Display for RenderError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RenderError::Surface(e) => write!(f, "{}", e),
|
||||
RenderError::Graph(e) => write!(f, "{:?}", e),
|
||||
RenderError::Device(e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderError {
|
||||
pub fn should_exit(&self) -> bool {
|
||||
match self {
|
||||
RenderError::Surface(e) => match e {
|
||||
wgpu::SurfaceError::OutOfMemory => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RenderGraphError> for RenderError {
|
||||
fn from(e: RenderGraphError) -> Self {
|
||||
RenderError::Graph(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::SurfaceError> for Error {
|
||||
fn from(e: wgpu::SurfaceError) -> Self {
|
||||
Error::Render(RenderError::Surface(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<wgpu::RequestDeviceError> for Error {
|
||||
fn from(e: wgpu::RequestDeviceError) -> Self {
|
||||
Error::Render(RenderError::Device(e))
|
||||
}
|
||||
}
|
||||
@ -574,8 +574,8 @@ mod tests {
|
||||
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
|
||||
SlotInfo,
|
||||
};
|
||||
use crate::{
|
||||
render::graph::{RenderContext, SlotType},
|
||||
use crate::render::{
|
||||
graph::{RenderContext, SlotType},
|
||||
RenderState,
|
||||
};
|
||||
|
||||
|
||||
@ -32,7 +32,6 @@ use crate::{
|
||||
tile_view_pattern::{TileInView, TileShape, TileViewPattern},
|
||||
},
|
||||
tessellation::IndexDataType,
|
||||
HeadedMapWindow, MapWindow,
|
||||
};
|
||||
|
||||
pub mod graph;
|
||||
@ -49,19 +48,24 @@ mod tile_pipeline;
|
||||
mod tile_view_pattern;
|
||||
|
||||
// Public API
|
||||
pub mod builder;
|
||||
pub mod camera;
|
||||
pub mod error;
|
||||
pub mod eventually;
|
||||
pub mod settings;
|
||||
|
||||
pub use shaders::ShaderVertex;
|
||||
pub use stages::register_default_render_stages;
|
||||
|
||||
use crate::render::{
|
||||
graph::{EmptyNode, RenderGraph, RenderGraphError},
|
||||
main_pass::{MainPassDriverNode, MainPassNode},
|
||||
use crate::{
|
||||
render::{
|
||||
graph::{EmptyNode, RenderGraph, RenderGraphError},
|
||||
main_pass::{MainPassDriverNode, MainPassNode},
|
||||
},
|
||||
window::{HeadedMapWindow, MapWindow},
|
||||
};
|
||||
|
||||
pub const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
|
||||
const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
|
||||
|
||||
pub struct RenderState {
|
||||
render_target: Eventually<TextureView>,
|
||||
@ -411,7 +415,10 @@ impl Renderer {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{MapWindow, MapWindowConfig, WindowSize};
|
||||
use crate::{
|
||||
render::{settings::RendererSettings, RenderState},
|
||||
window::{MapWindow, MapWindowConfig, WindowSize},
|
||||
};
|
||||
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
size: WindowSize,
|
||||
@ -440,9 +447,8 @@ mod tests {
|
||||
async fn test_render() {
|
||||
use log::LevelFilter;
|
||||
|
||||
use crate::{
|
||||
render::{graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface},
|
||||
RenderState, RendererSettings,
|
||||
use crate::render::{
|
||||
graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface,
|
||||
};
|
||||
|
||||
let _ = env_logger::builder()
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
//! Specifies the instructions which are going to be sent to the GPU. Render commands can be concatenated
|
||||
//! into a new render command which executes multiple instruction sets.
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
eventually::Eventually::Initialized,
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
||||
resource::{Globals, IndexEntry, TrackedRenderPass},
|
||||
tile_view_pattern::{TileInView, TileShape},
|
||||
INDEX_FORMAT,
|
||||
},
|
||||
RenderState,
|
||||
use crate::render::{
|
||||
eventually::Eventually::Initialized,
|
||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
||||
resource::{Globals, IndexEntry, TrackedRenderPass},
|
||||
tile_view_pattern::{TileInView, TileShape},
|
||||
RenderState, INDEX_FORMAT,
|
||||
};
|
||||
|
||||
impl PhaseItem for TileInView {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::{render::resource::TrackedRenderPass, RenderState};
|
||||
use crate::render::{resource::TrackedRenderPass, RenderState};
|
||||
|
||||
/// A draw function which is used to draw a specific [`PhaseItem`].
|
||||
///
|
||||
|
||||
@ -7,8 +7,7 @@ use wgpu::CompositeAlphaMode;
|
||||
|
||||
use crate::{
|
||||
render::{eventually::HasChanged, resource::texture::TextureView, settings::RendererSettings},
|
||||
window::HeadedMapWindow,
|
||||
MapWindow, WindowSize,
|
||||
window::{HeadedMapWindow, MapWindow, WindowSize},
|
||||
};
|
||||
|
||||
pub struct BufferDimensions {
|
||||
@ -53,7 +52,7 @@ impl WindowHead {
|
||||
where
|
||||
MW: MapWindow + HeadedMapWindow,
|
||||
{
|
||||
self.surface = unsafe { instance.create_surface(window.inner()) };
|
||||
self.surface = unsafe { instance.create_surface(window.raw()) };
|
||||
}
|
||||
pub fn surface(&self) -> &wgpu::Surface {
|
||||
&self.surface
|
||||
@ -144,7 +143,7 @@ impl Surface {
|
||||
present_mode: wgpu::PresentMode::Fifo, // VSync
|
||||
};
|
||||
|
||||
let surface = unsafe { instance.create_surface(window.inner()) };
|
||||
let surface = unsafe { instance.create_surface(window.raw()) };
|
||||
|
||||
Self {
|
||||
size,
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
//! Extracts data from the current state.
|
||||
|
||||
use crate::{
|
||||
context::MapContext, coords::ViewRegion, render::eventually::Eventually::Initialized,
|
||||
schedule::Stage, RenderState, Renderer,
|
||||
context::MapContext,
|
||||
coords::ViewRegion,
|
||||
render::{eventually::Eventually::Initialized, RenderState, Renderer},
|
||||
schedule::Stage,
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@ -12,7 +15,7 @@ impl Stage for ExtractStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state,
|
||||
world: World { view_state, .. },
|
||||
renderer:
|
||||
Renderer {
|
||||
state:
|
||||
|
||||
@ -6,9 +6,9 @@ use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
eventually::Eventually::Initialized, graph::RenderGraph, graph_runner::RenderGraphRunner,
|
||||
Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
Renderer,
|
||||
};
|
||||
|
||||
/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
//! Sorts items of the [RenderPhases](RenderPhase).
|
||||
|
||||
use crate::{context::MapContext, render::render_phase::RenderPhase, schedule::Stage, Renderer};
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
render::{render_phase::RenderPhase, Renderer},
|
||||
schedule::Stage,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PhaseSortStage;
|
||||
|
||||
@ -4,9 +4,9 @@ use crate::{
|
||||
context::MapContext,
|
||||
render::{
|
||||
eventually::Eventually::Initialized, resource::IndexEntry, tile_view_pattern::TileInView,
|
||||
RenderState, Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
RenderState, Renderer,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@ -17,7 +17,6 @@ impl Stage for QueueStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state: _,
|
||||
renderer:
|
||||
Renderer {
|
||||
state:
|
||||
|
||||
@ -10,9 +10,9 @@ use crate::{
|
||||
shaders::{Shader, ShaderTileMetadata},
|
||||
tile_pipeline::TilePipeline,
|
||||
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_SIZE},
|
||||
Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
Renderer,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
@ -10,9 +10,11 @@ use crate::{
|
||||
camera::ViewProjection,
|
||||
eventually::Eventually::Initialized,
|
||||
shaders::{ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32},
|
||||
RenderState, Renderer,
|
||||
},
|
||||
schedule::Stage,
|
||||
RenderState, Renderer, Style,
|
||||
style::Style,
|
||||
world::World,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@ -23,9 +25,12 @@ impl Stage for UploadStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state,
|
||||
world:
|
||||
World {
|
||||
tile_repository,
|
||||
view_state,
|
||||
},
|
||||
style,
|
||||
tile_repository,
|
||||
renderer: Renderer { queue, state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
@ -40,8 +45,8 @@ impl Stage for UploadStage {
|
||||
bytemuck::cast_slice(&[ShaderGlobals::new(ShaderCamera::new(
|
||||
view_proj.downcast().into(),
|
||||
view_state
|
||||
.camera
|
||||
.position
|
||||
.camera()
|
||||
.position()
|
||||
.to_homogeneous()
|
||||
.cast::<f32>()
|
||||
.unwrap() // TODO: Remove unwrap
|
||||
@ -159,9 +164,8 @@ impl UploadStage {
|
||||
let loaded_layers = buffer_pool
|
||||
.get_loaded_layers_at(&world_coords)
|
||||
.unwrap_or_default();
|
||||
if let Some(available_layers) = tile_repository
|
||||
.iter_tessellated_layers_at(&world_coords)
|
||||
.map(|layers| {
|
||||
if let Some(available_layers) =
|
||||
tile_repository.iter_layers_at(&world_coords).map(|layers| {
|
||||
layers
|
||||
.filter(|result| !loaded_layers.contains(&result.layer_name()))
|
||||
.collect::<Vec<_>>()
|
||||
|
||||
@ -6,10 +6,9 @@ use crate::{
|
||||
platform::MIN_WEBGL_BUFFER_SIZE,
|
||||
render::{
|
||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||
settings::Msaa,
|
||||
settings::{Msaa, RendererSettings},
|
||||
shaders::ShaderGlobals,
|
||||
},
|
||||
RendererSettings,
|
||||
};
|
||||
|
||||
pub struct TilePipeline {
|
||||
|
||||
@ -12,12 +12,13 @@ use request_stage::RequestStage;
|
||||
|
||||
use crate::{
|
||||
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, Context, Message},
|
||||
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
|
||||
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
||||
source_client::HttpSourceClient,
|
||||
source_client::{HttpClient, HttpSourceClient},
|
||||
tile_pipelines::build_vector_tile_pipeline,
|
||||
transferables::{
|
||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
|
||||
@ -26,27 +27,20 @@ use crate::{
|
||||
},
|
||||
TileRequest,
|
||||
},
|
||||
kernel::Kernel,
|
||||
render::ShaderVertex,
|
||||
schedule::Schedule,
|
||||
stages::populate_tile_store_stage::PopulateTileStore,
|
||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
Environment, HttpClient, Scheduler,
|
||||
};
|
||||
|
||||
mod populate_tile_store_stage;
|
||||
mod request_stage;
|
||||
|
||||
/// Register stages required for requesting and preparing new tiles.
|
||||
pub fn register_stages<E: Environment>(
|
||||
schedule: &mut Schedule,
|
||||
http_source_client: HttpSourceClient<E::HttpClient>,
|
||||
apc: Rc<RefCell<E::AsyncProcedureCall>>,
|
||||
) {
|
||||
schedule.add_stage(
|
||||
"request",
|
||||
RequestStage::<E>::new(http_source_client, apc.clone()),
|
||||
);
|
||||
schedule.add_stage("populate_tile_store", PopulateTileStore::<E>::new(apc));
|
||||
pub fn register_stages<E: Environment>(schedule: &mut Schedule, kernel: Rc<Kernel<E>>) {
|
||||
schedule.add_stage("request", RequestStage::<E>::new(kernel.clone()));
|
||||
schedule.add_stage("populate_tile_store", PopulateTileStore::<E>::new(kernel));
|
||||
}
|
||||
|
||||
pub struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<T, HC>> {
|
||||
@ -58,12 +52,16 @@ pub struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<
|
||||
impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
|
||||
for HeadedPipelineProcessor<T, HC, C>
|
||||
{
|
||||
fn tile_finished(&mut self, coords: &WorldTileCoords) {
|
||||
fn tile_finished(&mut self, coords: &WorldTileCoords) -> Result<(), Error> {
|
||||
self.context
|
||||
.send(Message::TileTessellated(T::TileTessellated::new(*coords)))
|
||||
}
|
||||
|
||||
fn layer_unavailable(&mut self, coords: &WorldTileCoords, layer_name: &str) {
|
||||
fn layer_unavailable(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
layer_name: &str,
|
||||
) -> Result<(), Error> {
|
||||
self.context
|
||||
.send(Message::UnavailableLayer(T::UnavailableLayer::new(
|
||||
*coords,
|
||||
@ -77,7 +75,7 @@ impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: tile::Layer,
|
||||
) {
|
||||
) -> Result<(), Error> {
|
||||
self.context
|
||||
.send(Message::TessellatedLayer(T::TessellatedLayer::new(
|
||||
*coords,
|
||||
@ -91,11 +89,12 @@ impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
geometries: Vec<IndexedGeometry<f64>>,
|
||||
) {
|
||||
) -> Result<(), Error> {
|
||||
// FIXME (wasm-executor): Readd
|
||||
/* if let Ok(mut geometry_index) = self.state.geometry_index.lock() {
|
||||
geometry_index.index_tile(coords, TileIndex::Linear { list: geometries })
|
||||
}*/
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,22 +4,24 @@ use std::{borrow::BorrowMut, cell::RefCell, ops::Deref, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
environment::Environment,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, Message},
|
||||
tile_repository::StoredLayer,
|
||||
transferables::{TessellatedLayer, TileTessellated, UnavailableLayer},
|
||||
},
|
||||
kernel::Kernel,
|
||||
schedule::Stage,
|
||||
Environment,
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub struct PopulateTileStore<E: Environment> {
|
||||
apc: Rc<RefCell<E::AsyncProcedureCall>>,
|
||||
kernel: Rc<Kernel<E>>,
|
||||
}
|
||||
|
||||
impl<E: Environment> PopulateTileStore<E> {
|
||||
pub fn new(apc: Rc<RefCell<E::AsyncProcedureCall>>) -> Self {
|
||||
Self { apc }
|
||||
pub fn new(kernel: Rc<Kernel<E>>) -> Self {
|
||||
Self { kernel }
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,42 +29,43 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
tile_repository, ..
|
||||
world: World {
|
||||
tile_repository, ..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
if let Ok(mut apc) = self.apc.deref().try_borrow_mut() {
|
||||
if let Some(result) = apc.receive() {
|
||||
match result {
|
||||
Message::TileTessellated(tranferred) => {
|
||||
let coords = tranferred.coords();
|
||||
tile_repository.success(coords);
|
||||
tracing::trace!("Tile at {} finished loading", coords);
|
||||
log::warn!("Tile at {} finished loading", coords);
|
||||
}
|
||||
// FIXME: deduplicate
|
||||
Message::UnavailableLayer(tranferred) => {
|
||||
let layer: StoredLayer = tranferred.to_stored_layer();
|
||||
tracing::debug!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
tile_repository.put_tessellated_layer(layer);
|
||||
}
|
||||
Message::TessellatedLayer(data) => {
|
||||
let layer: StoredLayer = data.to_stored_layer();
|
||||
tracing::debug!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
log::warn!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
tile_repository.put_tessellated_layer(layer);
|
||||
}
|
||||
if let Some(result) = self.kernel.apc().receive() {
|
||||
match result {
|
||||
Message::TileTessellated(tranferred) => {
|
||||
let coords = tranferred.coords();
|
||||
tile_repository.mark_tile_succeeded(coords);
|
||||
tracing::trace!("Tile at {} finished loading", coords);
|
||||
log::warn!("Tile at {} finished loading", coords);
|
||||
}
|
||||
// FIXME: deduplicate
|
||||
Message::UnavailableLayer(tranferred) => {
|
||||
let layer: StoredLayer = tranferred.to_stored_layer();
|
||||
tracing::debug!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
tile_repository.put_layer(layer);
|
||||
}
|
||||
Message::TessellatedLayer(data) => {
|
||||
let layer: StoredLayer = data.to_stored_layer();
|
||||
tracing::debug!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
log::warn!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
tile_repository.put_layer(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ use std::{
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::{ViewRegion, WorldTileCoords, ZoomLevel},
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
io::{
|
||||
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
|
||||
@ -25,25 +26,20 @@ use crate::{
|
||||
transferables::{Transferables, UnavailableLayer},
|
||||
TileRequest,
|
||||
},
|
||||
kernel::Kernel,
|
||||
schedule::Stage,
|
||||
stages::HeadedPipelineProcessor,
|
||||
Environment, HttpClient, Scheduler, Style,
|
||||
style::Style,
|
||||
world::World,
|
||||
};
|
||||
|
||||
pub struct RequestStage<E: Environment> {
|
||||
apc: Rc<RefCell<E::AsyncProcedureCall>>,
|
||||
http_source_client: HttpSourceClient<E::HttpClient>,
|
||||
kernel: Rc<Kernel<E>>,
|
||||
}
|
||||
|
||||
impl<E: Environment> RequestStage<E> {
|
||||
pub fn new(
|
||||
http_source_client: HttpSourceClient<E::HttpClient>,
|
||||
apc: Rc<RefCell<E::AsyncProcedureCall>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
apc,
|
||||
http_source_client,
|
||||
}
|
||||
pub fn new(kernel: Rc<Kernel<E>>) -> Self {
|
||||
Self { kernel }
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,27 +47,35 @@ impl<E: Environment> Stage for RequestStage<E> {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state,
|
||||
world:
|
||||
World {
|
||||
tile_repository,
|
||||
view_state,
|
||||
},
|
||||
style,
|
||||
tile_repository,
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let view_region = view_state.create_view_region();
|
||||
|
||||
if view_state.camera.did_change(0.05) || view_state.zoom.did_change(0.05) {
|
||||
if view_state.did_camera_change() || view_state.did_zoom_change() {
|
||||
if let Some(view_region) = &view_region {
|
||||
// FIXME: We also need to request tiles from layers above if we are over the maximum zoom level
|
||||
self.request_tiles_in_view(tile_repository, style, view_region);
|
||||
}
|
||||
}
|
||||
|
||||
view_state.camera.update_reference();
|
||||
view_state.zoom.update_reference();
|
||||
view_state.update_references();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule<E: Environment, C: Context<E::Transferables, E::HttpClient>>(
|
||||
pub fn schedule<
|
||||
E: Environment,
|
||||
C: Context<
|
||||
<E::AsyncProcedureCall as AsyncProcedureCall<E::HttpClient>>::Transferables,
|
||||
E::HttpClient,
|
||||
>,
|
||||
>(
|
||||
input: Input,
|
||||
context: C,
|
||||
) -> AsyncProcedureFuture {
|
||||
@ -102,12 +106,15 @@ pub fn schedule<E: Environment, C: Context<E::Transferables, E::HttpClient>>(
|
||||
log::error!("{:?}", &e);
|
||||
for to_load in &input.layers {
|
||||
tracing::warn!("layer {} at {} unavailable", to_load, coords);
|
||||
context.send(Message::UnavailableLayer(
|
||||
<E::Transferables as Transferables>::UnavailableLayer::new(
|
||||
// FIXME: Handle result
|
||||
context.send(
|
||||
Message::UnavailableLayer(<<E::AsyncProcedureCall as AsyncProcedureCall<
|
||||
E::HttpClient,
|
||||
>>::Transferables as Transferables>::UnavailableLayer::new(
|
||||
input.coords,
|
||||
to_load.to_string(),
|
||||
),
|
||||
));
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,7 +139,7 @@ impl<E: Environment> RequestStage<E> {
|
||||
for coords in view_region.iter() {
|
||||
if coords.build_quad_key().is_some() {
|
||||
// TODO: Make tesselation depend on style?
|
||||
self.request_tile(tile_repository, &coords, &source_layers)
|
||||
self.request_tile(tile_repository, coords, &source_layers)
|
||||
.unwrap(); // TODO: Remove unwrap
|
||||
}
|
||||
}
|
||||
@ -141,26 +148,26 @@ impl<E: Environment> RequestStage<E> {
|
||||
fn request_tile(
|
||||
&self,
|
||||
tile_repository: &mut TileRepository,
|
||||
coords: &WorldTileCoords,
|
||||
coords: WorldTileCoords,
|
||||
layers: &HashSet<String>,
|
||||
) -> Result<(), Error> {
|
||||
/* if !tile_repository.is_layers_missing(coords, layers) {
|
||||
/* TODO: is this still required?
|
||||
if !tile_repository.is_layers_missing(coords, layers) {
|
||||
return Ok(false);
|
||||
}*/
|
||||
|
||||
if tile_repository.needs_fetching(&coords) {
|
||||
if tile_repository.has_tile(&coords) {
|
||||
tile_repository.create_tile(coords);
|
||||
|
||||
tracing::info!("new tile request: {}", &coords);
|
||||
self.apc.deref().borrow().schedule(
|
||||
self.kernel.apc().call(
|
||||
Input::TileRequest(TileRequest {
|
||||
coords: *coords,
|
||||
coords,
|
||||
layers: layers.clone(),
|
||||
}),
|
||||
schedule::<
|
||||
E,
|
||||
<E::AsyncProcedureCall as AsyncProcedureCall<
|
||||
E::Transferables,
|
||||
E::HttpClient,
|
||||
>>::Context,
|
||||
>,
|
||||
|
||||
@ -62,7 +62,6 @@ impl<V, I> OverAlignedVertexBuffer<V, I> {
|
||||
V: Copy,
|
||||
I: Copy,
|
||||
{
|
||||
// FIXME (wasm-executor), make this fn not needed
|
||||
let mut buffers = VertexBuffers::with_capacity(0, 0);
|
||||
buffers.vertices = Vec::from(vertices);
|
||||
buffers.indices = Vec::from(indices);
|
||||
|
||||
@ -4,7 +4,7 @@ use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
||||
|
||||
use crate::{Environment, HttpClient, InteractiveMapSchedule};
|
||||
use crate::environment::Environment;
|
||||
|
||||
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
|
||||
pub trait MapWindow {
|
||||
@ -16,7 +16,12 @@ pub trait MapWindow {
|
||||
pub trait HeadedMapWindow: MapWindow {
|
||||
type RawWindow: HasRawWindowHandle + HasRawDisplayHandle;
|
||||
|
||||
fn inner(&self) -> &Self::RawWindow;
|
||||
fn raw(&self) -> &Self::RawWindow;
|
||||
|
||||
// TODO: Can we avoid this?
|
||||
fn request_redraw(&self);
|
||||
|
||||
fn id(&self) -> u64;
|
||||
}
|
||||
|
||||
/// A configuration for a window which determines the corresponding implementation of a
|
||||
@ -27,13 +32,6 @@ pub trait MapWindowConfig: 'static {
|
||||
fn create(&self) -> Self::MapWindow;
|
||||
}
|
||||
|
||||
/// The event loop is responsible for processing events and propagating them to the map renderer.
|
||||
/// Only non-headless windows use an [`EventLoop`].
|
||||
pub trait EventLoop<E: Environment> {
|
||||
// FIXME (wasm-executor): Avoid Rc, change ownership model
|
||||
fn run(self, map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>, max_frames: Option<u64>);
|
||||
}
|
||||
|
||||
/// Window size with a width and an height in pixels.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct WindowSize {
|
||||
|
||||
163
maplibre/src/world.rs
Normal file
163
maplibre/src/world.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use cgmath::{Angle, Point3};
|
||||
|
||||
use crate::{
|
||||
context::MapContext,
|
||||
coords::{LatLon, ViewRegion, WorldCoords, Zoom, ZoomLevel, TILE_SIZE},
|
||||
io::tile_repository::TileRepository,
|
||||
render::camera::{Camera, Perspective, ViewProjection},
|
||||
util::ChangeObserver,
|
||||
window::WindowSize,
|
||||
};
|
||||
|
||||
pub struct World {
|
||||
pub view_state: ViewState,
|
||||
pub tile_repository: TileRepository,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new_at<P: Into<cgmath::Deg<f64>>>(
|
||||
window_size: WindowSize,
|
||||
initial_center: LatLon,
|
||||
initial_zoom: Zoom,
|
||||
pitch: P,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
window_size,
|
||||
WorldCoords::from_lat_lon(initial_center, initial_zoom),
|
||||
initial_zoom,
|
||||
pitch,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new<P: Into<cgmath::Deg<f64>>>(
|
||||
window_size: WindowSize,
|
||||
initial_center: WorldCoords,
|
||||
initial_zoom: Zoom,
|
||||
pitch: P,
|
||||
) -> Self {
|
||||
let position = initial_center;
|
||||
let view_state = ViewState::new(
|
||||
window_size,
|
||||
position,
|
||||
initial_zoom,
|
||||
pitch,
|
||||
cgmath::Deg(110.0),
|
||||
);
|
||||
|
||||
let tile_repository = TileRepository::new();
|
||||
|
||||
World {
|
||||
view_state,
|
||||
tile_repository,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_state(&self) -> &ViewState {
|
||||
&self.view_state
|
||||
}
|
||||
|
||||
pub fn view_state_mut(&mut self) -> &mut ViewState {
|
||||
&mut self.view_state
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the camera configuration.
|
||||
pub struct ViewState {
|
||||
zoom: ChangeObserver<Zoom>,
|
||||
camera: ChangeObserver<Camera>,
|
||||
perspective: Perspective,
|
||||
}
|
||||
|
||||
impl ViewState {
|
||||
pub fn new<F: Into<cgmath::Rad<f64>>, P: Into<cgmath::Deg<f64>>>(
|
||||
window_size: WindowSize,
|
||||
position: WorldCoords,
|
||||
zoom: Zoom,
|
||||
pitch: P,
|
||||
fovy: F,
|
||||
) -> Self {
|
||||
let tile_center = TILE_SIZE / 2.0;
|
||||
let fovy = fovy.into();
|
||||
let height = tile_center / (fovy / 2.0).tan();
|
||||
|
||||
let camera = Camera::new(
|
||||
(position.x, position.y, height),
|
||||
cgmath::Deg(-90.0),
|
||||
pitch.into(),
|
||||
window_size.width(),
|
||||
window_size.height(),
|
||||
);
|
||||
|
||||
let perspective = Perspective::new(
|
||||
window_size.width(),
|
||||
window_size.height(),
|
||||
cgmath::Deg(110.0),
|
||||
// in tile.vertex.wgsl we are setting each layer's final `z` in ndc space to `z_index`.
|
||||
// This means that regardless of the `znear` value all layers will be rendered as part
|
||||
// of the near plane.
|
||||
// These values have been selected experimentally:
|
||||
// https://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
|
||||
1024.0,
|
||||
2048.0,
|
||||
);
|
||||
|
||||
Self {
|
||||
zoom: ChangeObserver::new(zoom),
|
||||
camera: ChangeObserver::new(camera),
|
||||
perspective,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.perspective.resize(width, height);
|
||||
self.camera.resize(width, height);
|
||||
}
|
||||
|
||||
pub fn create_view_region(&self) -> Option<ViewRegion> {
|
||||
self.camera
|
||||
.view_region_bounding_box(&self.view_projection().invert())
|
||||
.map(|bounding_box| {
|
||||
ViewRegion::new(bounding_box, 0, 32, *self.zoom, self.visible_level())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn view_projection(&self) -> ViewProjection {
|
||||
self.camera.calc_view_proj(&self.perspective)
|
||||
}
|
||||
|
||||
pub fn visible_level(&self) -> ZoomLevel {
|
||||
self.zoom.level()
|
||||
}
|
||||
|
||||
pub fn zoom(&self) -> Zoom {
|
||||
*self.zoom
|
||||
}
|
||||
|
||||
pub fn did_zoom_change(&self) -> bool {
|
||||
self.zoom.did_change(0.05)
|
||||
}
|
||||
|
||||
pub fn update_zoom(&mut self, new_zoom: Zoom) {
|
||||
*self.zoom = new_zoom;
|
||||
log::info!("zoom: {}", new_zoom);
|
||||
}
|
||||
|
||||
pub fn camera(&self) -> &Camera {
|
||||
self.camera.deref()
|
||||
}
|
||||
|
||||
pub fn camera_mut(&mut self) -> &mut Camera {
|
||||
self.camera.deref_mut()
|
||||
}
|
||||
|
||||
pub fn did_camera_change(&self) -> bool {
|
||||
self.camera.did_change(0.05)
|
||||
}
|
||||
|
||||
pub fn update_references(&mut self) {
|
||||
self.camera.update_reference();
|
||||
self.zoom.update_reference();
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,7 @@ crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.56"
|
||||
maplibre = { path = "../maplibre", features = ["no-thread-safe-futures"] }
|
||||
maplibre = { path = "../maplibre", default-features = false, features = [] }
|
||||
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
|
||||
|
||||
log = "0.4.17"
|
||||
|
||||
@ -121,7 +121,7 @@ const emitTypeScript = () => {
|
||||
});
|
||||
|
||||
if (child.status !== 0) {
|
||||
console.error("Failed to execute tsc")
|
||||
throw new Error("Failed to execute tsc")
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +154,7 @@ const wasmPack = () => {
|
||||
});
|
||||
|
||||
if (cargo.status !== 0) {
|
||||
console.error("Failed to execute cargo build")
|
||||
throw new Error("Failed to execute cargo build")
|
||||
}
|
||||
|
||||
let wasmbindgen = spawnSync('wasm-bindgen', [
|
||||
@ -170,7 +170,7 @@ const wasmPack = () => {
|
||||
});
|
||||
|
||||
if (wasmbindgen.status !== 0) {
|
||||
console.error("Failed to execute wasm-bindgen")
|
||||
throw new Error("Failed to execute wasm-bindgen")
|
||||
}
|
||||
|
||||
if (release) {
|
||||
@ -186,10 +186,9 @@ const wasmPack = () => {
|
||||
});
|
||||
|
||||
if (wasmOpt.status !== 0) {
|
||||
console.error("Failed to execute wasm-opt")
|
||||
throw new Error("Failed to execute wasm-opt")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const watchResult = async (result) => {
|
||||
@ -238,6 +237,7 @@ const esbuild = async (name, globalName = undefined) => {
|
||||
}
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
console.log("Creating WASM...")
|
||||
wasmPack();
|
||||
|
||||
@ -258,6 +258,9 @@ const start = async () => {
|
||||
|
||||
console.log("Emitting TypeScript types...")
|
||||
emitTypeScript();
|
||||
} catch (e) {
|
||||
console.error("Failed to start building: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
const _ = start()
|
||||
|
||||
@ -31,39 +31,29 @@ export const startMapLibre = async (wasmPath: string | undefined, workerPath: st
|
||||
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY / PAGES, shared: true})
|
||||
await maplibre.default(wasmPath, memory)
|
||||
|
||||
maplibre.run(await maplibre.create_map(() => {
|
||||
await maplibre.run_maplibre(() => {
|
||||
return workerPath ? new Worker(workerPath, {
|
||||
type: 'module'
|
||||
}) : MultithreadedPoolWorker();
|
||||
}))
|
||||
});
|
||||
} else {
|
||||
const memory = new WebAssembly.Memory({initial: 1024, shared: false})
|
||||
await maplibre.default(wasmPath, memory);
|
||||
|
||||
let callbacks: {worker_callback?: (message: MessageEvent) => void} = {}
|
||||
|
||||
let map = await maplibre.create_map(() => {
|
||||
await maplibre.run_maplibre((ptr) => {
|
||||
let worker: Worker = workerPath ? new Worker(workerPath, {
|
||||
type: 'module'
|
||||
}) : PoolWorker();
|
||||
|
||||
worker.onmessage = (message: MessageEvent) => {
|
||||
callbacks.worker_callback(message)
|
||||
let tag = message.data[0];
|
||||
let data = new Uint8Array(message.data[1]);
|
||||
|
||||
// @ts-ignore TODO singlethreaded_main_entry may not be defined
|
||||
maplibre.singlethreaded_main_entry(ptr, tag, data)
|
||||
}
|
||||
|
||||
return worker;
|
||||
})
|
||||
|
||||
let clonedMap = maplibre.clone_map(map)
|
||||
|
||||
callbacks.worker_callback = (message) => {
|
||||
let tag = message.data[0];
|
||||
let data = new Uint8Array(message.data[1]);
|
||||
|
||||
// @ts-ignore TODO unsync_main_entry may not be defined
|
||||
maplibre.singlethreaded_main_entry(clonedMap, tag, data)
|
||||
}
|
||||
|
||||
maplibre.run(map)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
#![feature(allocator_api, new_uninit)]
|
||||
|
||||
use std::{borrow::BorrowMut, cell::RefCell, mem, ops::Deref, panic, rc::Rc};
|
||||
use std::{borrow::BorrowMut, cell::RefCell, mem, ops::Deref, rc::Rc};
|
||||
|
||||
use maplibre::{io::scheduler::NopScheduler, Map, MapBuilder};
|
||||
use maplibre_winit::winit::{WinitEnvironment, WinitMapWindowConfig};
|
||||
use maplibre::{
|
||||
event_loop::EventLoop,
|
||||
io::{apc::SchedulerAsyncProcedureCall, scheduler::NopScheduler},
|
||||
kernel::{Kernel, KernelBuilder},
|
||||
map::Map,
|
||||
render::builder::{InitializedRenderer, RendererBuilder},
|
||||
style::Style,
|
||||
window::MapWindowConfig,
|
||||
};
|
||||
use maplibre_winit::{WinitEnvironment, WinitMapWindowConfig};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::platform::http_client::WHATWGFetchHttpClient;
|
||||
@ -32,45 +40,42 @@ pub fn wasm_bindgen_start() {
|
||||
if console_log::init_with_level(log::Level::Info).is_err() {
|
||||
// Failed to initialize logging. No need to log a message.
|
||||
}
|
||||
panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
#[cfg(any(feature = "trace"))]
|
||||
enable_tracing();
|
||||
}
|
||||
|
||||
#[cfg(not(target_feature = "atomics"))]
|
||||
pub type MapType = Map<
|
||||
WinitEnvironment<
|
||||
NopScheduler,
|
||||
WHATWGFetchHttpClient,
|
||||
platform::singlethreaded::transferables::LinearTransferables,
|
||||
platform::singlethreaded::apc::PassingAsyncProcedureCall,
|
||||
>,
|
||||
type CurrentEnvironment = WinitEnvironment<
|
||||
NopScheduler,
|
||||
WHATWGFetchHttpClient,
|
||||
platform::singlethreaded::apc::PassingAsyncProcedureCall,
|
||||
(),
|
||||
>;
|
||||
|
||||
#[cfg(target_feature = "atomics")]
|
||||
pub type MapType = Map<
|
||||
WinitEnvironment<
|
||||
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
|
||||
type CurrentEnvironment = WinitEnvironment<
|
||||
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
|
||||
WHATWGFetchHttpClient,
|
||||
maplibre::io::apc::SchedulerAsyncProcedureCall<
|
||||
WHATWGFetchHttpClient,
|
||||
maplibre::io::transferables::DefaultTransferables,
|
||||
maplibre::io::apc::SchedulerAsyncProcedureCall<
|
||||
WHATWGFetchHttpClient,
|
||||
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
|
||||
>,
|
||||
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
|
||||
>,
|
||||
(),
|
||||
>;
|
||||
|
||||
pub type MapType = Map<CurrentEnvironment>;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn create_map(new_worker: js_sys::Function) -> u32 {
|
||||
// Either call forget or the main loop to keep worker loop alive
|
||||
let mut builder = MapBuilder::new()
|
||||
pub async fn run_maplibre(new_worker: js_sys::Function) {
|
||||
let mut kernel_builder = KernelBuilder::new()
|
||||
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
|
||||
.with_http_client(WHATWGFetchHttpClient::new());
|
||||
|
||||
#[cfg(target_feature = "atomics")]
|
||||
{
|
||||
builder = builder
|
||||
kernel_builder = kernel_builder
|
||||
.with_apc(maplibre::io::apc::SchedulerAsyncProcedureCall::new(
|
||||
WHATWGFetchHttpClient::new(),
|
||||
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler::new(
|
||||
@ -84,30 +89,22 @@ pub async fn create_map(new_worker: js_sys::Function) -> u32 {
|
||||
|
||||
#[cfg(not(target_feature = "atomics"))]
|
||||
{
|
||||
builder = builder
|
||||
kernel_builder = kernel_builder
|
||||
.with_apc(platform::singlethreaded::apc::PassingAsyncProcedureCall::new(new_worker, 4))
|
||||
.with_scheduler(NopScheduler);
|
||||
}
|
||||
|
||||
let map: MapType = builder.build().initialize().await;
|
||||
let kernel: Kernel<WinitEnvironment<_, _, _, ()>> = kernel_builder.build();
|
||||
|
||||
Rc::into_raw(Rc::new(RefCell::new(map))) as u32
|
||||
}
|
||||
let mut map: MapType = Map::new(Style::default(), kernel).unwrap();
|
||||
map.initialize_renderer(RendererBuilder::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub unsafe fn clone_map(map_ptr: *const RefCell<MapType>) -> *const RefCell<MapType> {
|
||||
let mut map = Rc::from_raw(map_ptr);
|
||||
let rc = map.clone();
|
||||
let cloned = Rc::into_raw(rc);
|
||||
mem::forget(map);
|
||||
cloned
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub unsafe fn run(map_ptr: *const RefCell<MapType>) {
|
||||
let mut map = Rc::from_raw(map_ptr);
|
||||
|
||||
map.deref().borrow().run();
|
||||
map.window_mut()
|
||||
.take_event_loop()
|
||||
.expect("Event loop is not available")
|
||||
.run(map, None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -6,6 +6,7 @@ use wasm_bindgen::{prelude::*, JsCast};
|
||||
use web_sys::Worker;
|
||||
|
||||
use super::pool::WorkerPool;
|
||||
use crate::CurrentEnvironment;
|
||||
|
||||
pub struct WebWorkerPoolScheduler {
|
||||
pool: WorkerPool,
|
||||
@ -13,19 +14,19 @@ pub struct WebWorkerPoolScheduler {
|
||||
|
||||
impl WebWorkerPoolScheduler {
|
||||
pub fn new(new_worker: js_sys::Function) -> Self {
|
||||
Self {
|
||||
pool: WorkerPool::new(
|
||||
1,
|
||||
Box::new(move || {
|
||||
new_worker
|
||||
.call0(&JsValue::undefined())
|
||||
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
||||
.dyn_into::<Worker>()
|
||||
.unwrap() // FIXME (wasm-executor): remove unwrap
|
||||
}),
|
||||
)
|
||||
.unwrap(), // FIXME (wasm-executor): Remove unwrap
|
||||
}
|
||||
// TODO: Are expects here oke?
|
||||
let pool = WorkerPool::new(
|
||||
1,
|
||||
Box::new(move || {
|
||||
new_worker
|
||||
.call0(&JsValue::undefined())
|
||||
.expect("Unable to call new_worker function")
|
||||
.dyn_into::<Worker>()
|
||||
.expect("new_worker function did not return a Worker")
|
||||
}),
|
||||
)
|
||||
.expect("Unable to create WorkerPool");
|
||||
Self { pool }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,29 +16,33 @@ use std::{
|
||||
};
|
||||
|
||||
use js_sys::Uint8Array;
|
||||
use log::info;
|
||||
use maplibre::{
|
||||
environment::Environment,
|
||||
error::Error,
|
||||
io::{
|
||||
apc::{AsyncProcedure, AsyncProcedureCall, Context, Input, Message},
|
||||
scheduler::Scheduler,
|
||||
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||
transferables::Transferables,
|
||||
},
|
||||
kernel::Kernel,
|
||||
};
|
||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||
use web_sys::{DedicatedWorkerGlobalScope, Worker};
|
||||
use web_sys::{console::info, DedicatedWorkerGlobalScope, Worker};
|
||||
|
||||
use crate::{
|
||||
platform::singlethreaded::transferables::{
|
||||
InnerData, LinearTessellatedLayer, LinearTransferables,
|
||||
},
|
||||
MapType, WHATWGFetchHttpClient,
|
||||
CurrentEnvironment, MapType, WHATWGFetchHttpClient,
|
||||
};
|
||||
|
||||
type UsedTransferables = LinearTransferables;
|
||||
type UsedHttpClient = WHATWGFetchHttpClient;
|
||||
type UsedContext = PassingContext;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SerializedMessageTag {
|
||||
TileTessellated = 1,
|
||||
UnavailableLayer = 2,
|
||||
@ -120,7 +124,7 @@ pub struct PassingContext {
|
||||
}
|
||||
|
||||
impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
|
||||
fn send(&self, data: Message<UsedTransferables>) {
|
||||
fn send(&self, data: Message<UsedTransferables>) -> Result<(), Error> {
|
||||
let tag = data.tag();
|
||||
let serialized = data.serialize();
|
||||
|
||||
@ -130,11 +134,12 @@ impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
|
||||
serialized_array.set(&Uint8Array::view(serialized), 0);
|
||||
}
|
||||
|
||||
let global = js_sys::global().unchecked_into::<DedicatedWorkerGlobalScope>(); // FIXME (wasm-executor): Remove unchecked
|
||||
let global: DedicatedWorkerGlobalScope =
|
||||
js_sys::global().dyn_into().map_err(|e| Error::APC)?;
|
||||
let array = js_sys::Array::new();
|
||||
array.push(&JsValue::from(tag as u32));
|
||||
array.push(&serialized_array_buffer);
|
||||
global.post_message(&array).unwrap(); // FIXME (wasm-executor) Remove unwrap
|
||||
global.post_message(&array).map_err(|e| Error::APC)
|
||||
}
|
||||
|
||||
fn source_client(&self) -> &SourceClient<UsedHttpClient> {
|
||||
@ -142,18 +147,26 @@ impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
|
||||
}
|
||||
}
|
||||
|
||||
type ReceivedType = RefCell<Vec<Message<UsedTransferables>>>;
|
||||
|
||||
pub struct PassingAsyncProcedureCall {
|
||||
new_worker: Box<dyn Fn() -> Worker>,
|
||||
workers: Vec<Worker>,
|
||||
|
||||
received: Vec<Message<UsedTransferables>>,
|
||||
received: Rc<ReceivedType>, // FIXME (wasm-executor): Is RefCell fine?
|
||||
}
|
||||
|
||||
impl PassingAsyncProcedureCall {
|
||||
pub fn new(new_worker: js_sys::Function, initial_workers: u8) -> Self {
|
||||
let received = Rc::new(RefCell::new(vec![]));
|
||||
let received_ref = received.clone();
|
||||
|
||||
let create_new_worker = Box::new(move || {
|
||||
new_worker
|
||||
.call0(&JsValue::undefined())
|
||||
.call1(
|
||||
&JsValue::undefined(),
|
||||
&JsValue::from(Rc::into_raw(received_ref.clone()) as u32),
|
||||
)
|
||||
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
||||
.dyn_into::<Worker>()
|
||||
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
||||
@ -173,19 +186,20 @@ impl PassingAsyncProcedureCall {
|
||||
Self {
|
||||
new_worker: create_new_worker,
|
||||
workers,
|
||||
received: vec![],
|
||||
received,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncProcedureCall<UsedTransferables, UsedHttpClient> for PassingAsyncProcedureCall {
|
||||
impl AsyncProcedureCall<UsedHttpClient> for PassingAsyncProcedureCall {
|
||||
type Context = UsedContext;
|
||||
type Transferables = UsedTransferables;
|
||||
|
||||
fn receive(&mut self) -> Option<Message<UsedTransferables>> {
|
||||
self.received.pop()
|
||||
fn receive(&self) -> Option<Message<UsedTransferables>> {
|
||||
self.received.borrow_mut().pop()
|
||||
}
|
||||
|
||||
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
|
||||
fn call(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
|
||||
let procedure_ptr = procedure as *mut AsyncProcedure<Self::Context> as u32; // FIXME (wasm-executor): is u32 fine, define an overflow safe function?
|
||||
let input = serde_json::to_string(&input).unwrap(); // FIXME (wasm-executor): Remove unwrap
|
||||
|
||||
@ -205,7 +219,7 @@ pub async fn singlethreaded_worker_entry(procedure_ptr: u32, input: String) -> R
|
||||
let input = serde_json::from_str::<Input>(&input).unwrap(); // FIXME (wasm-executor): Remove unwrap
|
||||
|
||||
let context = PassingContext {
|
||||
source_client: SourceClient::Http(HttpSourceClient::new(WHATWGFetchHttpClient::new())),
|
||||
source_client: SourceClient::new(HttpSourceClient::new(WHATWGFetchHttpClient::new())),
|
||||
};
|
||||
|
||||
(procedure)(input, context).await;
|
||||
@ -216,30 +230,24 @@ pub async fn singlethreaded_worker_entry(procedure_ptr: u32, input: String) -> R
|
||||
/// Entry point invoked by the main thread.
|
||||
#[wasm_bindgen]
|
||||
pub unsafe fn singlethreaded_main_entry(
|
||||
map_ptr: *const RefCell<MapType>,
|
||||
received_ptr: *const ReceivedType,
|
||||
type_id: u32,
|
||||
data: Uint8Array,
|
||||
) -> Result<(), JsValue> {
|
||||
// FIXME (wasm-executor): Can we make this call safe? check if it was cloned before?
|
||||
let mut map = Rc::from_raw(map_ptr);
|
||||
let mut received: Rc<ReceivedType> = Rc::from_raw(received_ptr);
|
||||
|
||||
let message = Message::<UsedTransferables>::deserialize(
|
||||
SerializedMessageTag::from_u32(type_id).unwrap(),
|
||||
data,
|
||||
);
|
||||
|
||||
map.deref()
|
||||
.borrow()
|
||||
.map_schedule()
|
||||
.deref()
|
||||
.borrow()
|
||||
.apc
|
||||
.deref()
|
||||
.borrow_mut()
|
||||
.received
|
||||
.push(message);
|
||||
info!("singlethreaded_main_entry {:?}", message.tag());
|
||||
|
||||
mem::forget(map); // FIXME (wasm-executor): Enforce this somehow
|
||||
// MAJOR FIXME: Fix mutability
|
||||
received.borrow_mut().push(message);
|
||||
|
||||
mem::forget(received); // FIXME (wasm-executor): Enforce this somehow
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use bytemuck::{TransparentWrapper, Zeroable};
|
||||
use bytemuck_derive::{Pod, Zeroable};
|
||||
use log::warn;
|
||||
use maplibre::{
|
||||
benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||
coords::WorldTileCoords,
|
||||
@ -122,6 +123,75 @@ impl TessellatedLayer for LinearTessellatedLayer {
|
||||
feature_indices_len: feature_indices.len(),
|
||||
});
|
||||
|
||||
if buffer.buffer.vertices.len() > 15000 {
|
||||
warn!("vertices too large");
|
||||
return Self {
|
||||
data: Box::new(InnerData {
|
||||
coords: WrapperWorldTileCoords::wrap(coords),
|
||||
|
||||
layer_name: [0; 32],
|
||||
layer_name_len: 0,
|
||||
|
||||
vertices: LongVertexShader::wrap([ShaderVertex::zeroed(); 15000]),
|
||||
vertices_len: 0,
|
||||
|
||||
indices: LongIndices::wrap([IndexDataType::zeroed(); 40000]),
|
||||
indices_len: 0,
|
||||
|
||||
usable_indices: 0,
|
||||
|
||||
feature_indices: [0u32; 2048],
|
||||
feature_indices_len: 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if buffer.buffer.indices.len() > 40000 {
|
||||
warn!("indices too large");
|
||||
return Self {
|
||||
data: Box::new(InnerData {
|
||||
coords: WrapperWorldTileCoords::wrap(coords),
|
||||
|
||||
layer_name: [0; 32],
|
||||
layer_name_len: 0,
|
||||
|
||||
vertices: LongVertexShader::wrap([ShaderVertex::zeroed(); 15000]),
|
||||
vertices_len: 0,
|
||||
|
||||
indices: LongIndices::wrap([IndexDataType::zeroed(); 40000]),
|
||||
indices_len: 0,
|
||||
|
||||
usable_indices: 0,
|
||||
|
||||
feature_indices: [0u32; 2048],
|
||||
feature_indices_len: 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if feature_indices.len() > 2048 {
|
||||
warn!("feature_indices too large");
|
||||
return Self {
|
||||
data: Box::new(InnerData {
|
||||
coords: WrapperWorldTileCoords::wrap(coords),
|
||||
|
||||
layer_name: [0; 32],
|
||||
layer_name_len: 0,
|
||||
|
||||
vertices: LongVertexShader::wrap([ShaderVertex::zeroed(); 15000]),
|
||||
vertices_len: 0,
|
||||
|
||||
indices: LongIndices::wrap([IndexDataType::zeroed(); 40000]),
|
||||
indices_len: 0,
|
||||
|
||||
usable_indices: 0,
|
||||
|
||||
feature_indices: [0u32; 2048],
|
||||
feature_indices_len: 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
data.vertices.0[0..buffer.buffer.vertices.len()].clone_from_slice(&buffer.buffer.vertices);
|
||||
data.indices.0[0..buffer.buffer.indices.len()].clone_from_slice(&buffer.buffer.indices);
|
||||
data.feature_indices[0..feature_indices.len()].clone_from_slice(&feature_indices);
|
||||
@ -131,7 +201,8 @@ impl TessellatedLayer for LinearTessellatedLayer {
|
||||
}
|
||||
|
||||
fn to_stored_layer(self) -> StoredLayer {
|
||||
let layer = StoredLayer::TessellatedLayer {
|
||||
// TODO: Avoid copies here
|
||||
StoredLayer::TessellatedLayer {
|
||||
coords: WrapperWorldTileCoords::peel(self.data.coords),
|
||||
layer_name: String::from_utf8(Vec::from(
|
||||
&self.data.layer_name[..self.data.layer_name_len],
|
||||
@ -143,9 +214,7 @@ impl TessellatedLayer for LinearTessellatedLayer {
|
||||
self.data.usable_indices,
|
||||
),
|
||||
feature_indices: Vec::from(&self.data.feature_indices[..self.data.feature_indices_len]),
|
||||
};
|
||||
|
||||
layer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user