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$/target" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/docs/book" />
|
<excludeFolder url="file://$MODULE_DIR$/docs/book" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/web/lib/.parcel-cache" />
|
<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" />
|
<excludeFolder url="file://$MODULE_DIR$/maplibre-cache" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<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" }
|
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
log = "0.4.17"
|
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"
|
jni = "0.19.0"
|
||||||
|
|
||||||
[lib]
|
[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 {
|
plugins {
|
||||||
id 'com.android.application' version '7.0.4' apply true
|
id 'com.android.application' version '7.2.0' apply true
|
||||||
id 'org.jetbrains.kotlin.android' version '1.6.21' apply true
|
id 'org.jetbrains.kotlin.android' version '1.7.20' apply true
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -34,6 +34,7 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '1.8'
|
||||||
}
|
}
|
||||||
|
namespace 'com.example.demo'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="com.example.demo">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
|||||||
@ -10,6 +10,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
MapLibreRs.android_main()
|
MapLibreRs.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'org.mozilla.rust-android-gradle.rust-android' version '0.9.3' apply true
|
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 'com.android.library' version '7.2.0' apply true
|
||||||
id 'org.jetbrains.kotlin.android' version '1.6.21' apply true
|
id 'org.jetbrains.kotlin.android' version '1.7.20' apply true
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
@ -14,9 +14,7 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
|
|
||||||
versionCode 1
|
|
||||||
versionName "1.0"
|
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@ -27,6 +25,7 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
namespace 'org.maplibre_rs'
|
||||||
}
|
}
|
||||||
|
|
||||||
cargo {
|
cargo {
|
||||||
@ -35,6 +34,8 @@ cargo {
|
|||||||
libname = "maplibre_android"
|
libname = "maplibre_android"
|
||||||
targetDirectory = "${module}/../target"
|
targetDirectory = "${module}/../target"
|
||||||
profile = "debug"
|
profile = "debug"
|
||||||
|
rustupChannel = "nightly-2022-10-23"
|
||||||
|
|
||||||
|
|
||||||
features {
|
features {
|
||||||
defaultAnd "foo", "bar"
|
defaultAnd "foo", "bar"
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest package="org.maplibre_rs" />
|
<manifest/>
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
package org.maplibre_rs;
|
package org.maplibre_rs;
|
||||||
|
|
||||||
|
import android.os.Environment;
|
||||||
|
|
||||||
public class MapLibreRs {
|
public class MapLibreRs {
|
||||||
|
public static void start() {
|
||||||
|
android_main();
|
||||||
|
}
|
||||||
|
|
||||||
public static native void android_main();
|
public static native void android_main();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
|||||||
@ -5,9 +5,8 @@ use log::Level;
|
|||||||
use maplibre::{
|
use maplibre::{
|
||||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||||
render::settings::{Backends, WgpuSettings},
|
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"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
compile_error!("android works only on android.");
|
compile_error!("android works only on android.");
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
use maplibre::{
|
use maplibre::{
|
||||||
io::apc::SchedulerAsyncProcedureCall,
|
io::apc::SchedulerAsyncProcedureCall,
|
||||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||||
MapBuilder,
|
|
||||||
};
|
};
|
||||||
use maplibre_winit::winit::{
|
use maplibre_winit::{
|
||||||
run_headed_map, WinitEnvironment, WinitEventLoop, WinitMapWindow, WinitMapWindowConfig,
|
run_headed_map, WinitEnvironment, WinitEventLoop, WinitMapWindow, WinitMapWindowConfig,
|
||||||
WinitWindow,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
||||||
|
|||||||
@ -4,7 +4,10 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
|||||||
use maplibre::{
|
use maplibre::{
|
||||||
coords::{WorldTileCoords, ZoomLevel},
|
coords::{WorldTileCoords, ZoomLevel},
|
||||||
error::Error,
|
error::Error,
|
||||||
headless::{utils::HeadlessPipelineProcessor, HeadlessEnvironment, HeadlessMapWindowConfig},
|
headless::{
|
||||||
|
create_headless_renderer, environment::HeadlessEnvironment, map::HeadlessMap,
|
||||||
|
window::HeadlessMapWindowConfig,
|
||||||
|
},
|
||||||
io::{
|
io::{
|
||||||
apc::SchedulerAsyncProcedureCall,
|
apc::SchedulerAsyncProcedureCall,
|
||||||
pipeline::{PipelineContext, Processable},
|
pipeline::{PipelineContext, Processable},
|
||||||
@ -12,43 +15,32 @@ use maplibre::{
|
|||||||
tile_pipelines::build_vector_tile_pipeline,
|
tile_pipelines::build_vector_tile_pipeline,
|
||||||
TileRequest,
|
TileRequest,
|
||||||
},
|
},
|
||||||
|
kernel::{Kernel, KernelBuilder},
|
||||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||||
render::settings::{RendererSettings, TextureFormat},
|
render::{
|
||||||
|
builder::{InitializedRenderer, RendererBuilder},
|
||||||
|
settings::{RendererSettings, TextureFormat},
|
||||||
|
},
|
||||||
|
style::Style,
|
||||||
window::WindowSize,
|
window::WindowSize,
|
||||||
MapBuilder,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn headless_render(c: &mut Criterion) {
|
fn headless_render(c: &mut Criterion) {
|
||||||
c.bench_function("headless_render", |b| {
|
c.bench_function("headless_render", |b| {
|
||||||
let mut map = run_multithreaded(async {
|
let (mut map, tile) = run_multithreaded(async {
|
||||||
let client = ReqwestHttpClient::new(None);
|
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::<
|
let tile = map
|
||||||
HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>,
|
.fetch_tile(
|
||||||
>::new()
|
WorldTileCoords::from((0, 0, ZoomLevel::default())),
|
||||||
.with_map_window_config(HeadlessMapWindowConfig {
|
&["water"],
|
||||||
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())))
|
|
||||||
.await
|
.await
|
||||||
.expect("Failed to fetch and process!");
|
.expect("Failed to fetch and process!");
|
||||||
|
|
||||||
map
|
(map, tile)
|
||||||
});
|
});
|
||||||
|
|
||||||
b.to_async(
|
b.to_async(
|
||||||
@ -58,7 +50,7 @@ fn headless_render(c: &mut Criterion) {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.iter(|| {
|
.iter(|| {
|
||||||
match map.map_schedule_mut().update_and_redraw() {
|
match map.render_tile(tile.clone()) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(Error::Render(e)) => {
|
Err(Error::Render(e)) => {
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
|
|||||||
12
justfile
12
justfile
@ -97,8 +97,16 @@ web-test FEATURES: nightly-toolchain
|
|||||||
#profile-bench:
|
#profile-bench:
|
||||||
# cargo flamegraph --bench render -- --bench
|
# cargo flamegraph --bench render -- --bench
|
||||||
|
|
||||||
build-android: nightly-toolchain print-android-env
|
build-android: build-android-lib build-android-demo
|
||||||
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew assembleDebug
|
|
||||||
|
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
|
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
|
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo test -p maplibre-android --target {{TARGET}} -Z build-std=std,panic_abort
|
||||||
|
|||||||
@ -1,37 +1,33 @@
|
|||||||
use maplibre::{
|
use maplibre::{
|
||||||
coords::{LatLon, WorldTileCoords},
|
coords::{LatLon, WorldTileCoords},
|
||||||
error::Error,
|
error::Error,
|
||||||
headless::{HeadlessEnvironment, HeadlessMapWindowConfig},
|
headless::{create_headless_renderer, map::HeadlessMap, window::HeadlessMapWindowConfig},
|
||||||
io::apc::SchedulerAsyncProcedureCall,
|
io::apc::SchedulerAsyncProcedureCall,
|
||||||
|
kernel::KernelBuilder,
|
||||||
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
|
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
|
||||||
render::settings::{RendererSettings, TextureFormat},
|
render::{
|
||||||
|
builder::RendererBuilder,
|
||||||
|
settings::{RendererSettings, TextureFormat},
|
||||||
|
},
|
||||||
|
style::Style,
|
||||||
util::grid::google_mercator,
|
util::grid::google_mercator,
|
||||||
window::WindowSize,
|
window::WindowSize,
|
||||||
MapBuilder,
|
|
||||||
};
|
};
|
||||||
use maplibre_winit::winit::WinitEnvironment;
|
use maplibre_winit::WinitEnvironment;
|
||||||
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
|
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
|
||||||
|
|
||||||
pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
|
pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
|
||||||
let client = ReqwestHttpClient::new(None);
|
let (kernel, renderer) = create_headless_renderer(tile_size, None).await;
|
||||||
let mut map =
|
|
||||||
MapBuilder::<HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>>::new()
|
let style = Style::default();
|
||||||
.with_map_window_config(HeadlessMapWindowConfig {
|
|
||||||
size: WindowSize::new(tile_size, tile_size).unwrap(),
|
let requested_layers = style
|
||||||
})
|
.layers
|
||||||
.with_http_client(client.clone())
|
.iter()
|
||||||
.with_apc(SchedulerAsyncProcedureCall::new(
|
.map(|layer| layer.source_layer.as_ref().unwrap().clone())
|
||||||
client,
|
.collect::<Vec<_>>();
|
||||||
TokioScheduler::new(),
|
|
||||||
)) // FIXME (wasm-executor): avoid passing client and scheduler here
|
let mut map = HeadlessMap::new(style, renderer, kernel).unwrap();
|
||||||
.with_scheduler(TokioScheduler::new())
|
|
||||||
.with_renderer_settings(RendererSettings {
|
|
||||||
texture_format: TextureFormat::Rgba8UnormSrgb,
|
|
||||||
..RendererSettings::default()
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
.initialize_headless()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let tile_limits = google_mercator().tile_limits(
|
let tile_limits = google_mercator().tile_limits(
|
||||||
extent_wgs84_to_merc(&Extent {
|
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) {
|
for (z, x, y) in GridIterator::new(10, 10, tile_limits) {
|
||||||
let coords = WorldTileCoords::from((x as i32, y as i32, z.into()));
|
let coords = WorldTileCoords::from((x as i32, y as i32, z.into()));
|
||||||
println!("Rendering {}", &coords);
|
println!("Rendering {}", &coords);
|
||||||
map.map_schedule
|
let tile = map
|
||||||
.fetch_process(&coords)
|
.fetch_tile(
|
||||||
|
coords,
|
||||||
|
&requested_layers
|
||||||
|
.iter()
|
||||||
|
.map(|layer| layer.as_str())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to fetch and process!");
|
.expect("Failed to fetch and process");
|
||||||
|
|
||||||
match map.map_schedule_mut().update_and_redraw() {
|
map.render_tile(tile).expect("Rendering failed");
|
||||||
Ok(_) => {}
|
|
||||||
Err(Error::Render(e)) => {
|
|
||||||
eprintln!("{}", e);
|
|
||||||
if e.should_exit() {}
|
|
||||||
}
|
|
||||||
e => eprintln!("{:?}", e),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::io::ErrorKind;
|
|||||||
|
|
||||||
use clap::{builder::ValueParser, Parser, Subcommand};
|
use clap::{builder::ValueParser, Parser, Subcommand};
|
||||||
use maplibre::{coords::LatLon, platform::run_multithreaded};
|
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;
|
use crate::headless::run_headless;
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ wasm-bindgen = "0.2.81"
|
|||||||
wasm-bindgen-futures = "0.4.31"
|
wasm-bindgen-futures = "0.4.31"
|
||||||
|
|
||||||
[dependencies]
|
[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 }
|
winit = { version = "0.27.2", default-features = false }
|
||||||
cgmath = "0.18.0"
|
cgmath = "0.18.0"
|
||||||
instant = { version = "0.1.12", features = ["wasm-bindgen"] } # TODO: Untrusted dependency
|
instant = { version = "0.1.12", features = ["wasm-bindgen"] } # TODO: Untrusted dependency
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cgmath::Vector2;
|
use cgmath::Vector2;
|
||||||
use maplibre::context::ViewState;
|
use maplibre::world::ViewState;
|
||||||
use winit::event::{DeviceEvent, KeyboardInput, TouchPhase, WindowEvent};
|
use winit::event::{DeviceEvent, KeyboardInput, TouchPhase, WindowEvent};
|
||||||
|
|
||||||
use crate::input::{
|
use crate::input::{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cgmath::{EuclideanSpace, Point3, Vector2, Vector3, Zero};
|
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 winit::event::{ElementState, MouseButton};
|
||||||
|
|
||||||
use super::UpdateState;
|
use super::UpdateState;
|
||||||
@ -45,17 +45,17 @@ impl UpdateState for PanHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if self.start_camera_position.is_none() {
|
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 {
|
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),
|
start_camera_position + Vector3::new(delta.x, delta.y, 0.0),
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.reference_camera = Some(state.camera.clone());
|
self.reference_camera = Some(state.camera().clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use maplibre::context::ViewState;
|
use maplibre::world::ViewState;
|
||||||
|
|
||||||
use super::UpdateState;
|
use super::UpdateState;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cgmath::Vector2;
|
use cgmath::Vector2;
|
||||||
use maplibre::context::ViewState;
|
use maplibre::world::ViewState;
|
||||||
use winit::event::{ElementState, MouseButton};
|
use winit::event::{ElementState, MouseButton};
|
||||||
|
|
||||||
use crate::input::UpdateState;
|
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 _z = state.visible_level(); // FIXME: can be wrong, if tiles of different z are visible
|
||||||
let _zoom = state.zoom();
|
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,
|
&window_position,
|
||||||
&inverted_view_proj,
|
&inverted_view_proj,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cgmath::{Vector3, Zero};
|
use cgmath::{Vector3, Zero};
|
||||||
use maplibre::context::ViewState;
|
use maplibre::world::ViewState;
|
||||||
|
|
||||||
use super::UpdateState;
|
use super::UpdateState;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ impl UpdateState for ShiftHandler {
|
|||||||
let dt = dt.as_secs_f64() * (1.0 / self.speed);
|
let dt = dt.as_secs_f64() * (1.0 / self.speed);
|
||||||
|
|
||||||
let delta = self.camera_translate * dt;
|
let delta = self.camera_translate * dt;
|
||||||
state.camera.position += delta;
|
state.camera_mut().move_relative(delta);
|
||||||
self.camera_translate -= delta;
|
self.camera_translate -= delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cgmath::{Deg, Rad, Zero};
|
use cgmath::{Deg, Rad, Zero};
|
||||||
use maplibre::context::ViewState;
|
use maplibre::world::ViewState;
|
||||||
|
|
||||||
use super::UpdateState;
|
use super::UpdateState;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ impl UpdateState for TiltHandler {
|
|||||||
let dt = dt.as_secs_f64() * (1.0 / self.speed);
|
let dt = dt.as_secs_f64() * (1.0 / self.speed);
|
||||||
|
|
||||||
let delta = self.delta_pitch * dt;
|
let delta = self.delta_pitch * dt;
|
||||||
state.camera.pitch += Rad::from(delta);
|
state.camera_mut().pitch_self(delta);
|
||||||
self.delta_pitch -= delta;
|
self.delta_pitch -= delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cgmath::{Vector2, Vector3};
|
use cgmath::{Vector2, Vector3};
|
||||||
use maplibre::{context::ViewState, coords::Zoom};
|
use maplibre::{coords::Zoom, world::ViewState};
|
||||||
|
|
||||||
use super::UpdateState;
|
use super::UpdateState;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ impl UpdateState for ZoomHandler {
|
|||||||
let view_proj = state.view_projection();
|
let view_proj = state.view_projection();
|
||||||
let inverted_view_proj = view_proj.invert();
|
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,
|
&window_position,
|
||||||
&inverted_view_proj,
|
&inverted_view_proj,
|
||||||
false,
|
false,
|
||||||
@ -37,7 +37,7 @@ impl UpdateState for ZoomHandler {
|
|||||||
cursor_position.z,
|
cursor_position.z,
|
||||||
) - cursor_position;
|
) - cursor_position;
|
||||||
|
|
||||||
state.camera.position += delta;
|
state.camera_mut().move_relative(delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,221 @@
|
|||||||
pub mod input;
|
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
|
//! * Platform Events like suspend/resume
|
||||||
//! * Render a new frame
|
//! * Render a new frame
|
||||||
|
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use maplibre::{
|
use maplibre::{
|
||||||
|
event_loop::EventLoop,
|
||||||
io::apc::SchedulerAsyncProcedureCall,
|
io::apc::SchedulerAsyncProcedureCall,
|
||||||
|
kernel::{Kernel, KernelBuilder},
|
||||||
|
map::Map,
|
||||||
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
|
||||||
|
render::{
|
||||||
|
builder::{InitializationResult, InitializedRenderer, RendererBuilder},
|
||||||
|
settings::{Backends, RendererSettings, WgpuSettings},
|
||||||
|
},
|
||||||
|
style::Style,
|
||||||
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
|
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
|
||||||
MapBuilder,
|
|
||||||
};
|
};
|
||||||
use winit::window::WindowBuilder;
|
use winit::window::WindowBuilder;
|
||||||
|
|
||||||
use super::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
|
use super::{RawWinitEventLoop, RawWinitWindow, WinitMapWindow};
|
||||||
use crate::winit::WinitEnvironment;
|
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 {
|
fn size(&self) -> WindowSize {
|
||||||
let size = self.window.inner_size();
|
let size = self.window.inner_size();
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
@ -29,27 +53,22 @@ impl MapWindow for WinitMapWindow {
|
|||||||
window_size
|
window_size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl HeadedMapWindow for WinitMapWindow {
|
|
||||||
type RawWindow = WinitWindow;
|
|
||||||
|
|
||||||
fn inner(&self) -> &Self::RawWindow {
|
impl<ET: 'static> MapWindowConfig for WinitMapWindowConfig<ET> {
|
||||||
&self.window
|
type MapWindow = WinitMapWindow<ET>;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapWindowConfig for WinitMapWindowConfig {
|
|
||||||
type MapWindow = WinitMapWindow;
|
|
||||||
|
|
||||||
fn create(&self) -> Self::MapWindow {
|
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()
|
let window = WindowBuilder::new()
|
||||||
.with_title(&self.title)
|
.with_title(&self.title)
|
||||||
.build(&event_loop)
|
.build(&raw_event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Self::MapWindow {
|
Self::MapWindow {
|
||||||
window,
|
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>) {
|
pub fn run_headed_map(cache_path: Option<String>) {
|
||||||
run_multithreaded(async {
|
run_multithreaded(async {
|
||||||
let client = ReqwestHttpClient::new(cache_path);
|
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_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
|
||||||
.with_http_client(client.clone())
|
.with_http_client(client.clone())
|
||||||
.with_apc(SchedulerAsyncProcedureCall::new(
|
.with_apc(SchedulerAsyncProcedureCall::new(
|
||||||
@ -65,9 +84,23 @@ pub fn run_headed_map(cache_path: Option<String>) {
|
|||||||
TokioScheduler::new(),
|
TokioScheduler::new(),
|
||||||
))
|
))
|
||||||
.with_scheduler(TokioScheduler::new())
|
.with_scheduler(TokioScheduler::new())
|
||||||
.build()
|
.build();
|
||||||
.initialize()
|
|
||||||
|
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
|
.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 maplibre::window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};
|
||||||
use winit::{platform::web::WindowBuilderExtWebSys, window::WindowBuilder};
|
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 {
|
pub struct WinitMapWindowConfig<ET> {
|
||||||
type MapWindow = WinitMapWindow;
|
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 {
|
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()
|
let window: winit::window::Window = WindowBuilder::new()
|
||||||
.with_canvas(Some(get_canvas(&self.canvas_id)))
|
.with_canvas(Some(get_canvas(&self.canvas_id)))
|
||||||
.build(&event_loop)
|
.build(&raw_event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let size = get_body_size().unwrap();
|
let size = get_body_size().unwrap();
|
||||||
window.set_inner_size(size);
|
window.set_inner_size(size);
|
||||||
Self::MapWindow {
|
Self::MapWindow {
|
||||||
window,
|
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 {
|
fn size(&self) -> WindowSize {
|
||||||
let size = self.window.inner_size();
|
let size = self.window.inner_size();
|
||||||
|
|
||||||
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.")
|
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>> {
|
pub fn get_body_size() -> Option<winit::dpi::LogicalSize<i32>> {
|
||||||
let web_window: web_sys::Window = web_sys::window().unwrap();
|
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"
|
readme = "../README.md"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["thread-safe-futures"]
|
||||||
web-webgl = ["wgpu/webgl"]
|
web-webgl = ["wgpu/webgl"]
|
||||||
# Enable tracing using tracy on desktop/mobile and the chrome profiler on web
|
# Enable tracing using tracy on desktop/mobile and the chrome profiler on web
|
||||||
trace = ["tracing-subscriber", "tracing-tracy", "tracy-client"]
|
trace = ["tracing-subscriber", "tracing-tracy", "tracy-client"]
|
||||||
no-thread-safe-futures = []
|
thread-safe-futures = []
|
||||||
embed-static-tiles = ["maplibre-build-tools/sqlite"]
|
embed-static-tiles = ["maplibre-build-tools/sqlite"]
|
||||||
headless = ["png"]
|
headless = ["png"]
|
||||||
|
|
||||||
|
|||||||
@ -1,93 +1,19 @@
|
|||||||
use std::ops::Div;
|
|
||||||
|
|
||||||
use cgmath::Angle;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
coords::{LatLon, ViewRegion, WorldCoords, Zoom, ZoomLevel, TILE_SIZE},
|
render::Renderer,
|
||||||
io::tile_repository::TileRepository,
|
style::Style,
|
||||||
render::camera::{Camera, Perspective, ViewProjection},
|
world::{ViewState, World},
|
||||||
util::ChangeObserver,
|
|
||||||
Renderer, Style, WindowSize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 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.
|
/// Stores the context of the map.
|
||||||
pub struct MapContext {
|
pub struct MapContext {
|
||||||
pub view_state: ViewState,
|
|
||||||
pub style: Style,
|
pub style: Style,
|
||||||
|
pub world: World,
|
||||||
pub tile_repository: TileRepository,
|
|
||||||
pub renderer: Renderer,
|
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::{
|
use crate::{
|
||||||
|
event_loop::EventLoopConfig,
|
||||||
io::{
|
io::{
|
||||||
apc::AsyncProcedureCall,
|
apc::AsyncProcedureCall,
|
||||||
|
scheduler::Scheduler,
|
||||||
|
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||||
transferables::{
|
transferables::{
|
||||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, 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 {
|
pub trait Environment: 'static {
|
||||||
type MapWindowConfig: MapWindowConfig;
|
type MapWindowConfig: MapWindowConfig;
|
||||||
|
|
||||||
type AsyncProcedureCall: AsyncProcedureCall<Self::Transferables, Self::HttpClient>;
|
type AsyncProcedureCall: AsyncProcedureCall<Self::HttpClient>;
|
||||||
type Scheduler: Scheduler;
|
|
||||||
type HttpClient: HttpClient;
|
|
||||||
|
|
||||||
type Transferables: Transferables;
|
type Scheduler: Scheduler;
|
||||||
|
|
||||||
|
type HttpClient: HttpClient;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +1,28 @@
|
|||||||
//! Errors which can happen in various parts of the library.
|
//! 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;
|
use lyon::tessellation::TessellationError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
use crate::render::{error::RenderError, graph::RenderGraphError};
|
||||||
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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enumeration of errors which can happen during the operation of the library.
|
/// Enumeration of errors which can happen during the operation of the library.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Schedule,
|
APC,
|
||||||
|
Scheduler,
|
||||||
Network(String),
|
Network(String),
|
||||||
Tesselation(TessellationError),
|
Tesselation(TessellationError),
|
||||||
Render(RenderError),
|
Render(RenderError),
|
||||||
|
Generic(Cow<'static, str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<wgpu::SurfaceError> for Error {
|
impl<E> From<E> for Error
|
||||||
fn from(e: wgpu::SurfaceError) -> Self {
|
where
|
||||||
Error::Render(RenderError::Surface(e))
|
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 {
|
impl<T> From<SendError<T>> for Error {
|
||||||
fn from(_e: SendError<T>) -> Self {
|
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::{
|
use crate::{
|
||||||
coords::WorldTileCoords,
|
coords::WorldTileCoords,
|
||||||
|
error::Error,
|
||||||
io::{
|
io::{
|
||||||
source_client::{HttpSourceClient, SourceClient},
|
scheduler::Scheduler,
|
||||||
|
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||||
transferables::{DefaultTransferables, Transferables},
|
transferables::{DefaultTransferables, Transferables},
|
||||||
TileRequest,
|
TileRequest,
|
||||||
},
|
},
|
||||||
Environment, HttpClient, Scheduler,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The result of the tessellation of a tile.
|
/// The result of the tessellation of a tile. This is sent as a message from a worker to the caller
|
||||||
/// `TessellatedLayer` contains the result of the tessellation for a specific layer, otherwise
|
/// of an [`AsyncProcedure`].
|
||||||
/// `UnavailableLayer` if the layer doesn't exist.
|
///
|
||||||
|
/// * `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)]
|
#[derive(Clone)]
|
||||||
pub enum Message<T: Transferables> {
|
pub enum Message<T: Transferables> {
|
||||||
TileTessellated(T::TileTessellated),
|
TileTessellated(T::TileTessellated),
|
||||||
@ -30,30 +34,82 @@ pub enum Message<T: Transferables> {
|
|||||||
TessellatedLayer(T::TessellatedLayer),
|
TessellatedLayer(T::TessellatedLayer),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inputs for an [`AsyncProcedure`]
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub enum Input {
|
pub enum Input {
|
||||||
TileRequest(TileRequest),
|
TileRequest(TileRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allows sending messages from workers to back to the caller.
|
||||||
pub trait Context<T: Transferables, HC: HttpClient>: Send + 'static {
|
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>;
|
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)>>;
|
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)>>;
|
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 type AsyncProcedure<C> = fn(input: Input, context: C) -> AsyncProcedureFuture;
|
||||||
|
|
||||||
pub trait AsyncProcedureCall<T: Transferables, HC: HttpClient>: 'static {
|
/// APCs define an interface for performing work asynchronously.
|
||||||
type Context: Context<T, HC> + Send;
|
/// 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)]
|
#[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> {
|
impl<T: Transferables, HC: HttpClient> Context<T, HC> for SchedulerContext<T, HC> {
|
||||||
fn send(&self, data: Message<T>) {
|
fn send(&self, data: Message<T>) -> Result<(), Error> {
|
||||||
self.sender.send(data).unwrap(); // FIXME (wasm-executor): Remove unwrap
|
self.sender.send(data).map_err(|e| Error::APC)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source_client(&self) -> &SourceClient<HC> {
|
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>
|
impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<HC> for SchedulerAsyncProcedureCall<HC, S> {
|
||||||
for SchedulerAsyncProcedureCall<HC, S>
|
type Context = SchedulerContext<Self::Transferables, HC>;
|
||||||
{
|
type Transferables = DefaultTransferables;
|
||||||
type Context = SchedulerContext<DefaultTransferables, HC>;
|
|
||||||
|
|
||||||
fn receive(&mut self) -> Option<Message<DefaultTransferables>> {
|
fn receive(&self) -> Option<Message<DefaultTransferables>> {
|
||||||
let transferred = self.channel.1.try_recv().ok()?;
|
let transferred = self.channel.1.try_recv().ok()?;
|
||||||
Some(transferred)
|
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 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
|
self.scheduler
|
||||||
.schedule(move || async move {
|
.schedule(move || async move {
|
||||||
@ -111,7 +166,7 @@ impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<DefaultTransferables, HC>
|
|||||||
input,
|
input,
|
||||||
SchedulerContext {
|
SchedulerContext {
|
||||||
sender,
|
sender,
|
||||||
source_client: SourceClient::Http(HttpSourceClient::new(client)),
|
source_client: SourceClient::new(HttpSourceClient::new(client)),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@ -5,28 +5,40 @@ use geozero::mvt::tile;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
coords::WorldTileCoords,
|
coords::WorldTileCoords,
|
||||||
|
error::Error,
|
||||||
io::geometry_index::IndexedGeometry,
|
io::geometry_index::IndexedGeometry,
|
||||||
render::ShaderVertex,
|
render::ShaderVertex,
|
||||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Processes events which happen during the pipeline execution
|
/// Processes events which happen during the pipeline execution
|
||||||
|
// FIXME (wasm-executor): handle results for messages below
|
||||||
pub trait PipelineProcessor: Downcast {
|
pub trait PipelineProcessor: Downcast {
|
||||||
fn tile_finished(&mut self, _coords: &WorldTileCoords) {}
|
fn tile_finished(&mut self, _coords: &WorldTileCoords) -> Result<(), Error> {
|
||||||
fn layer_unavailable(&mut self, _coords: &WorldTileCoords, _layer_name: &str) {}
|
Ok(())
|
||||||
|
}
|
||||||
|
fn layer_unavailable(
|
||||||
|
&mut self,
|
||||||
|
_coords: &WorldTileCoords,
|
||||||
|
_layer_name: &str,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
fn layer_tesselation_finished(
|
fn layer_tesselation_finished(
|
||||||
&mut self,
|
&mut self,
|
||||||
_coords: &WorldTileCoords,
|
_coords: &WorldTileCoords,
|
||||||
_buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
_buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||||
_feature_indices: Vec<u32>,
|
_feature_indices: Vec<u32>,
|
||||||
_layer_data: tile::Layer,
|
_layer_data: tile::Layer,
|
||||||
) {
|
) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
fn layer_indexing_finished(
|
fn layer_indexing_finished(
|
||||||
&mut self,
|
&mut self,
|
||||||
_coords: &WorldTileCoords,
|
_coords: &WorldTileCoords,
|
||||||
_geometries: Vec<IndexedGeometry<f64>>,
|
_geometries: Vec<IndexedGeometry<f64>>,
|
||||||
) {
|
) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use crate::error::Error;
|
|||||||
/// Async/await scheduler.
|
/// Async/await scheduler.
|
||||||
/// Can schedule a task from a future factory and a shared state.
|
/// Can schedule a task from a future factory and a shared state.
|
||||||
pub trait Scheduler: 'static {
|
pub trait Scheduler: 'static {
|
||||||
#[cfg(not(feature = "no-thread-safe-futures"))]
|
#[cfg(feature = "thread-safe-futures")]
|
||||||
fn schedule<T>(
|
fn schedule<T>(
|
||||||
&self,
|
&self,
|
||||||
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
||||||
@ -15,7 +15,7 @@ pub trait Scheduler: 'static {
|
|||||||
where
|
where
|
||||||
T: Future<Output = ()> + Send + 'static;
|
T: Future<Output = ()> + Send + 'static;
|
||||||
|
|
||||||
#[cfg(feature = "no-thread-safe-futures")]
|
#[cfg(not(feature = "thread-safe-futures"))]
|
||||||
fn schedule<T>(
|
fn schedule<T>(
|
||||||
&self,
|
&self,
|
||||||
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
||||||
@ -31,6 +31,6 @@ impl Scheduler for NopScheduler {
|
|||||||
where
|
where
|
||||||
T: Future<Output = ()> + 'static,
|
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)
|
/// [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
|
/// 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.
|
/// the future "thread-safe-futures". Tokio futures are thread-safe.
|
||||||
#[cfg_attr(feature = "no-thread-safe-futures", async_trait(?Send))]
|
#[cfg_attr(not(feature = "thread-safe-futures"), async_trait(?Send))]
|
||||||
#[cfg_attr(not(feature = "no-thread-safe-futures"), async_trait)]
|
#[cfg_attr(feature = "thread-safe-futures", async_trait)]
|
||||||
pub trait HttpClient: Clone + Sync + Send + 'static {
|
pub trait HttpClient: Clone + Sync + Send + 'static {
|
||||||
async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error>;
|
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.
|
/// 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.
|
/// More types might be coming such as S3 and other cloud http clients.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum SourceClient<HC>
|
pub struct SourceClient<HC>
|
||||||
where
|
where
|
||||||
HC: HttpClient,
|
HC: HttpClient,
|
||||||
{
|
{
|
||||||
Http(HttpSourceClient<HC>),
|
http: HttpSourceClient<HC>, // TODO: mbtiles: Mbtiles
|
||||||
Mbtiles {
|
|
||||||
// TODO
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<HC> SourceClient<HC>
|
impl<HC> SourceClient<HC>
|
||||||
where
|
where
|
||||||
HC: HttpClient,
|
HC: HttpClient,
|
||||||
{
|
{
|
||||||
|
pub fn new(http: HttpSourceClient<HC>) -> Self {
|
||||||
|
Self { http }
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
|
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
|
||||||
match self {
|
self.http.fetch(coords).await
|
||||||
SourceClient::Http(client) => client.fetch(coords).await,
|
|
||||||
SourceClient::Mbtiles { .. } => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,7 @@ impl Processable for IndexLayer {
|
|||||||
) -> Self::Output {
|
) -> Self::Output {
|
||||||
let index = IndexProcessor::new();
|
let index = IndexProcessor::new();
|
||||||
|
|
||||||
|
// FIXME: Handle result
|
||||||
context
|
context
|
||||||
.processor_mut()
|
.processor_mut()
|
||||||
.layer_indexing_finished(&tile_request.coords, index.get_geometries());
|
.layer_indexing_finished(&tile_request.coords, index.get_geometries());
|
||||||
@ -78,6 +79,7 @@ impl Processable for TessellateLayer {
|
|||||||
|
|
||||||
let mut tessellator = ZeroTessellator::<IndexDataType>::default();
|
let mut tessellator = ZeroTessellator::<IndexDataType>::default();
|
||||||
if let Err(e) = layer.process(&mut tessellator) {
|
if let Err(e) = layer.process(&mut tessellator) {
|
||||||
|
// FIXME: Handle result
|
||||||
context
|
context
|
||||||
.processor_mut()
|
.processor_mut()
|
||||||
.layer_unavailable(coords, layer_name);
|
.layer_unavailable(coords, layer_name);
|
||||||
@ -89,12 +91,13 @@ impl Processable for TessellateLayer {
|
|||||||
e
|
e
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME: Handle result
|
||||||
context.processor_mut().layer_tesselation_finished(
|
context.processor_mut().layer_tesselation_finished(
|
||||||
coords,
|
coords,
|
||||||
tessellator.buffer.into(),
|
tessellator.buffer.into(),
|
||||||
tessellator.feature_indices,
|
tessellator.feature_indices,
|
||||||
cloned_layer,
|
cloned_layer,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +108,7 @@ impl Processable for TessellateLayer {
|
|||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
for missing_layer in tile_request.layers.difference(&available_layers) {
|
for missing_layer in tile_request.layers.difference(&available_layers) {
|
||||||
|
// FIXME: Handle result
|
||||||
context
|
context
|
||||||
.processor_mut()
|
.processor_mut()
|
||||||
.layer_unavailable(coords, missing_layer);
|
.layer_unavailable(coords, missing_layer);
|
||||||
@ -118,6 +122,7 @@ impl Processable for TessellateLayer {
|
|||||||
|
|
||||||
tracing::info!("tile tessellated at {} finished", &tile_request.coords);
|
tracing::info!("tile tessellated at {} finished", &tile_request.coords);
|
||||||
|
|
||||||
|
// FIXME: Handle result
|
||||||
context.processor_mut().tile_finished(&tile_request.coords);
|
context.processor_mut().tile_finished(&tile_request.coords);
|
||||||
|
|
||||||
(tile_request, tile)
|
(tile_request, tile)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// A layer which is stored for future use.
|
/// A layer which is stored for future use.
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum StoredLayer {
|
pub enum StoredLayer {
|
||||||
UnavailableLayer {
|
UnavailableLayer {
|
||||||
coords: WorldTileCoords,
|
coords: WorldTileCoords,
|
||||||
@ -41,7 +42,7 @@ impl StoredLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum TileStatus {
|
pub enum TileStatus {
|
||||||
Pending,
|
Pending,
|
||||||
Failed,
|
Failed,
|
||||||
@ -49,18 +50,37 @@ pub enum TileStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Stores multiple [StoredLayers](StoredLayer).
|
/// Stores multiple [StoredLayers](StoredLayer).
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct StoredTile {
|
pub struct StoredTile {
|
||||||
|
coords: WorldTileCoords,
|
||||||
layers: Vec<StoredLayer>,
|
layers: Vec<StoredLayer>,
|
||||||
status: TileStatus,
|
status: TileStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoredTile {
|
impl StoredTile {
|
||||||
pub fn new() -> Self {
|
pub fn pending(coords: WorldTileCoords) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
coords,
|
||||||
layers: vec![],
|
layers: vec![],
|
||||||
status: TileStatus::Pending,
|
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.
|
/// 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.
|
/// 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
|
/// 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
|
/// If the space is occupied, the tessellated layer is added to the current
|
||||||
/// [crate::io::tile_repository::CachedTile].
|
/// [crate::io::tile_repository::StoredLayer].
|
||||||
pub fn put_tessellated_layer(&mut self, layer: StoredLayer) {
|
pub fn put_layer(&mut self, layer: StoredLayer) {
|
||||||
if let Some(entry) = layer
|
if let Some(entry) = layer
|
||||||
.get_coords()
|
.get_coords()
|
||||||
.build_quad_key()
|
.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
|
/// Returns the list of tessellated layers at the given world tile coords. None if tile is
|
||||||
/// missing from the cache.
|
/// missing from the cache.
|
||||||
pub fn iter_tessellated_layers_at(
|
pub fn iter_layers_at(
|
||||||
&self,
|
&self,
|
||||||
coords: &WorldTileCoords,
|
coords: &WorldTileCoords,
|
||||||
) -> Option<impl Iterator<Item = &StoredLayer> + '_> {
|
) -> Option<impl Iterator<Item = &StoredLayer> + '_> {
|
||||||
@ -115,11 +141,11 @@ impl TileRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new tile.
|
/// 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)) {
|
if let Some(entry) = coords.build_quad_key().map(|key| self.tree.entry(key)) {
|
||||||
match entry {
|
match entry {
|
||||||
btree_map::Entry::Vacant(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.
|
/// 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)) {
|
if let Some(_) = coords.build_quad_key().and_then(|key| self.tree.get(&key)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn success(&mut self, coords: &WorldTileCoords) {
|
pub fn mark_tile_succeeded(&mut self, coords: &WorldTileCoords) {
|
||||||
if let Some(cached_tile) = coords
|
if let Some(cached_tile) = coords
|
||||||
.build_quad_key()
|
.build_quad_key()
|
||||||
.and_then(|key| self.tree.get_mut(&key))
|
.and_then(|key| self.tree.get_mut(&key))
|
||||||
@ -145,7 +171,7 @@ impl TileRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a layer has been fetched.
|
/// 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
|
if let Some(cached_tile) = coords
|
||||||
.build_quad_key()
|
.build_quad_key()
|
||||||
.and_then(|key| self.tree.get_mut(&key))
|
.and_then(|key| self.tree.get_mut(&key))
|
||||||
@ -153,42 +179,4 @@ impl TileRepository {
|
|||||||
cached_tile.status = TileStatus::Failed;
|
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>,
|
pub buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||||
/// Holds for each feature the count of indices.
|
/// Holds for each feature the count of indices.
|
||||||
pub feature_indices: Vec<u32>,
|
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 {
|
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"
|
//! maplibre = "0.0.2"
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::{
|
// Internal modules
|
||||||
borrow::{Borrow, BorrowMut},
|
pub(crate) mod tessellation;
|
||||||
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},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod coords;
|
pub mod coords;
|
||||||
@ -40,10 +25,8 @@ pub mod error;
|
|||||||
#[cfg(feature = "headless")]
|
#[cfg(feature = "headless")]
|
||||||
pub mod headless;
|
pub mod headless;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
// Exposed because of input handlers in maplibre-winit
|
|
||||||
pub mod map_schedule;
|
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
// Exposed because of camera
|
// TODO: Exposed because of camera
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
@ -54,209 +37,15 @@ pub mod schedule;
|
|||||||
// Exposed because of SharedThreadState
|
// Exposed because of SharedThreadState
|
||||||
pub mod stages;
|
pub mod stages;
|
||||||
|
|
||||||
|
pub mod environment;
|
||||||
|
|
||||||
// Used for benchmarking
|
// Used for benchmarking
|
||||||
pub mod benchmarking;
|
pub mod benchmarking;
|
||||||
|
|
||||||
// Internal modules
|
pub mod event_loop;
|
||||||
pub(crate) mod tessellation;
|
pub mod kernel;
|
||||||
|
pub mod map;
|
||||||
pub mod environment;
|
pub mod world;
|
||||||
|
|
||||||
|
// Export tile format
|
||||||
pub use geozero::mvt::tile;
|
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::{
|
/* pub async fn late_init(&mut self) -> bool {
|
||||||
context::{MapContext, ViewState},
|
match &self.map_context {
|
||||||
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
|
EventuallyMapContext::Full(_) => false,
|
||||||
error::Error,
|
EventuallyMapContext::Uninizalized(PrematureMapContext {
|
||||||
io::{
|
wgpu_settings,
|
||||||
scheduler::Scheduler,
|
renderer_settings,
|
||||||
source_client::{HttpClient, HttpSourceClient},
|
..
|
||||||
tile_repository::TileRepository,
|
}) => {
|
||||||
},
|
let window = self.map_window_config.create();
|
||||||
render::{create_default_render_graph, register_default_render_stages},
|
let renderer =
|
||||||
schedule::{Schedule, Stage},
|
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
|
||||||
stages::register_stages,
|
.await
|
||||||
style::Style,
|
.unwrap(); // TODO: Remove unwrap
|
||||||
Environment, HeadedMapWindow, MapWindowConfig, Renderer, RendererSettings, WgpuSettings,
|
self.map_context.make_full(renderer);
|
||||||
WindowSize,
|
true
|
||||||
};
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
}
|
}
|
||||||
|
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::ClientWithMiddleware;
|
||||||
use reqwest_middleware_cache::{managers::CACacheManager, Cache, CacheMode};
|
use reqwest_middleware_cache::{managers::CACacheManager, Cache, CacheMode};
|
||||||
|
|
||||||
use crate::{error::Error, HttpClient};
|
use crate::{error::Error, io::source_client::HttpClient};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ReqwestHttpClient {
|
pub struct ReqwestHttpClient {
|
||||||
@ -23,6 +23,7 @@ impl From<reqwest_middleware::Error> for Error {
|
|||||||
|
|
||||||
impl ReqwestHttpClient {
|
impl ReqwestHttpClient {
|
||||||
/// cache_path: Under which path should we cache requests.
|
/// cache_path: Under which path should we cache requests.
|
||||||
|
// TODO: Use Into<Path> instead of String
|
||||||
pub fn new(cache_path: Option<String>) -> Self {
|
pub fn new(cache_path: Option<String>) -> Self {
|
||||||
let mut builder = reqwest_middleware::ClientBuilder::new(Client::new());
|
let mut builder = reqwest_middleware::ClientBuilder::new(Client::new());
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use crate::{error::Error, Scheduler};
|
use crate::{error::Error, io::scheduler::Scheduler};
|
||||||
|
|
||||||
/// Multi-threading with Tokio.
|
/// Multi-threading with Tokio.
|
||||||
pub struct TokioScheduler;
|
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
|
//! 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::{
|
use crate::util::{
|
||||||
math::{bounds_from_points, Aabb2, Aabb3, Plane},
|
math::{bounds_from_points, Aabb2, Aabb3, Plane},
|
||||||
@ -68,12 +68,12 @@ impl ModelViewProjection {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
pub position: Point3<f64>, // The z axis never changes, the zoom is used instead
|
position: Point3<f64>, // The z axis never changes, the zoom is used instead
|
||||||
pub yaw: cgmath::Rad<f64>,
|
yaw: cgmath::Rad<f64>,
|
||||||
pub pitch: cgmath::Rad<f64>,
|
pitch: cgmath::Rad<f64>,
|
||||||
|
|
||||||
pub width: f64,
|
width: f64,
|
||||||
pub height: f64,
|
height: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignificantlyDifferent for Camera {
|
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) {
|
pub fn resize(&mut self, width: u32, height: u32) {
|
||||||
self.width = width as f64;
|
self.width = width as f64;
|
||||||
self.height = height as f64;
|
self.height = height as f64;
|
||||||
@ -347,6 +355,26 @@ impl Camera {
|
|||||||
Point2::new(max_x, max_y),
|
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 {
|
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,
|
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
|
||||||
SlotInfo,
|
SlotInfo,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::render::{
|
||||||
render::graph::{RenderContext, SlotType},
|
graph::{RenderContext, SlotType},
|
||||||
RenderState,
|
RenderState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,6 @@ use crate::{
|
|||||||
tile_view_pattern::{TileInView, TileShape, TileViewPattern},
|
tile_view_pattern::{TileInView, TileShape, TileViewPattern},
|
||||||
},
|
},
|
||||||
tessellation::IndexDataType,
|
tessellation::IndexDataType,
|
||||||
HeadedMapWindow, MapWindow,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
@ -49,19 +48,24 @@ mod tile_pipeline;
|
|||||||
mod tile_view_pattern;
|
mod tile_view_pattern;
|
||||||
|
|
||||||
// Public API
|
// Public API
|
||||||
|
pub mod builder;
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
|
pub mod error;
|
||||||
pub mod eventually;
|
pub mod eventually;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
||||||
pub use shaders::ShaderVertex;
|
pub use shaders::ShaderVertex;
|
||||||
pub use stages::register_default_render_stages;
|
pub use stages::register_default_render_stages;
|
||||||
|
|
||||||
use crate::render::{
|
use crate::{
|
||||||
graph::{EmptyNode, RenderGraph, RenderGraphError},
|
render::{
|
||||||
main_pass::{MainPassDriverNode, MainPassNode},
|
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 {
|
pub struct RenderState {
|
||||||
render_target: Eventually<TextureView>,
|
render_target: Eventually<TextureView>,
|
||||||
@ -411,7 +415,10 @@ impl Renderer {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{MapWindow, MapWindowConfig, WindowSize};
|
use crate::{
|
||||||
|
render::{settings::RendererSettings, RenderState},
|
||||||
|
window::{MapWindow, MapWindowConfig, WindowSize},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct HeadlessMapWindowConfig {
|
pub struct HeadlessMapWindowConfig {
|
||||||
size: WindowSize,
|
size: WindowSize,
|
||||||
@ -440,9 +447,8 @@ mod tests {
|
|||||||
async fn test_render() {
|
async fn test_render() {
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
|
|
||||||
use crate::{
|
use crate::render::{
|
||||||
render::{graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface},
|
graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface,
|
||||||
RenderState, RendererSettings,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = env_logger::builder()
|
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
|
//! 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.
|
//! into a new render command which executes multiple instruction sets.
|
||||||
|
|
||||||
use crate::{
|
use crate::render::{
|
||||||
render::{
|
eventually::Eventually::Initialized,
|
||||||
eventually::Eventually::Initialized,
|
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
||||||
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
|
resource::{Globals, IndexEntry, TrackedRenderPass},
|
||||||
resource::{Globals, IndexEntry, TrackedRenderPass},
|
tile_view_pattern::{TileInView, TileShape},
|
||||||
tile_view_pattern::{TileInView, TileShape},
|
RenderState, INDEX_FORMAT,
|
||||||
INDEX_FORMAT,
|
|
||||||
},
|
|
||||||
RenderState,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl PhaseItem for TileInView {
|
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`].
|
/// A draw function which is used to draw a specific [`PhaseItem`].
|
||||||
///
|
///
|
||||||
|
|||||||
@ -7,8 +7,7 @@ use wgpu::CompositeAlphaMode;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render::{eventually::HasChanged, resource::texture::TextureView, settings::RendererSettings},
|
render::{eventually::HasChanged, resource::texture::TextureView, settings::RendererSettings},
|
||||||
window::HeadedMapWindow,
|
window::{HeadedMapWindow, MapWindow, WindowSize},
|
||||||
MapWindow, WindowSize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct BufferDimensions {
|
pub struct BufferDimensions {
|
||||||
@ -53,7 +52,7 @@ impl WindowHead {
|
|||||||
where
|
where
|
||||||
MW: MapWindow + HeadedMapWindow,
|
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 {
|
pub fn surface(&self) -> &wgpu::Surface {
|
||||||
&self.surface
|
&self.surface
|
||||||
@ -144,7 +143,7 @@ impl Surface {
|
|||||||
present_mode: wgpu::PresentMode::Fifo, // VSync
|
present_mode: wgpu::PresentMode::Fifo, // VSync
|
||||||
};
|
};
|
||||||
|
|
||||||
let surface = unsafe { instance.create_surface(window.inner()) };
|
let surface = unsafe { instance.create_surface(window.raw()) };
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
size,
|
size,
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
//! Extracts data from the current state.
|
//! Extracts data from the current state.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext, coords::ViewRegion, render::eventually::Eventually::Initialized,
|
context::MapContext,
|
||||||
schedule::Stage, RenderState, Renderer,
|
coords::ViewRegion,
|
||||||
|
render::{eventually::Eventually::Initialized, RenderState, Renderer},
|
||||||
|
schedule::Stage,
|
||||||
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -12,7 +15,7 @@ impl Stage for ExtractStage {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
view_state,
|
world: World { view_state, .. },
|
||||||
renderer:
|
renderer:
|
||||||
Renderer {
|
Renderer {
|
||||||
state:
|
state:
|
||||||
|
|||||||
@ -6,9 +6,9 @@ use crate::{
|
|||||||
context::MapContext,
|
context::MapContext,
|
||||||
render::{
|
render::{
|
||||||
eventually::Eventually::Initialized, graph::RenderGraph, graph_runner::RenderGraphRunner,
|
eventually::Eventually::Initialized, graph::RenderGraph, graph_runner::RenderGraphRunner,
|
||||||
|
Renderer,
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
Renderer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.
|
/// 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).
|
//! 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)]
|
#[derive(Default)]
|
||||||
pub struct PhaseSortStage;
|
pub struct PhaseSortStage;
|
||||||
|
|||||||
@ -4,9 +4,9 @@ use crate::{
|
|||||||
context::MapContext,
|
context::MapContext,
|
||||||
render::{
|
render::{
|
||||||
eventually::Eventually::Initialized, resource::IndexEntry, tile_view_pattern::TileInView,
|
eventually::Eventually::Initialized, resource::IndexEntry, tile_view_pattern::TileInView,
|
||||||
|
RenderState, Renderer,
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
RenderState, Renderer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -17,7 +17,6 @@ impl Stage for QueueStage {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
view_state: _,
|
|
||||||
renderer:
|
renderer:
|
||||||
Renderer {
|
Renderer {
|
||||||
state:
|
state:
|
||||||
|
|||||||
@ -10,9 +10,9 @@ use crate::{
|
|||||||
shaders::{Shader, ShaderTileMetadata},
|
shaders::{Shader, ShaderTileMetadata},
|
||||||
tile_pipeline::TilePipeline,
|
tile_pipeline::TilePipeline,
|
||||||
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_SIZE},
|
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_SIZE},
|
||||||
|
Renderer,
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
Renderer,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|||||||
@ -10,9 +10,11 @@ use crate::{
|
|||||||
camera::ViewProjection,
|
camera::ViewProjection,
|
||||||
eventually::Eventually::Initialized,
|
eventually::Eventually::Initialized,
|
||||||
shaders::{ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32},
|
shaders::{ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32},
|
||||||
|
RenderState, Renderer,
|
||||||
},
|
},
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
RenderState, Renderer, Style,
|
style::Style,
|
||||||
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -23,9 +25,12 @@ impl Stage for UploadStage {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
view_state,
|
world:
|
||||||
|
World {
|
||||||
|
tile_repository,
|
||||||
|
view_state,
|
||||||
|
},
|
||||||
style,
|
style,
|
||||||
tile_repository,
|
|
||||||
renderer: Renderer { queue, state, .. },
|
renderer: Renderer { queue, state, .. },
|
||||||
..
|
..
|
||||||
}: &mut MapContext,
|
}: &mut MapContext,
|
||||||
@ -40,8 +45,8 @@ impl Stage for UploadStage {
|
|||||||
bytemuck::cast_slice(&[ShaderGlobals::new(ShaderCamera::new(
|
bytemuck::cast_slice(&[ShaderGlobals::new(ShaderCamera::new(
|
||||||
view_proj.downcast().into(),
|
view_proj.downcast().into(),
|
||||||
view_state
|
view_state
|
||||||
.camera
|
.camera()
|
||||||
.position
|
.position()
|
||||||
.to_homogeneous()
|
.to_homogeneous()
|
||||||
.cast::<f32>()
|
.cast::<f32>()
|
||||||
.unwrap() // TODO: Remove unwrap
|
.unwrap() // TODO: Remove unwrap
|
||||||
@ -159,9 +164,8 @@ impl UploadStage {
|
|||||||
let loaded_layers = buffer_pool
|
let loaded_layers = buffer_pool
|
||||||
.get_loaded_layers_at(&world_coords)
|
.get_loaded_layers_at(&world_coords)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if let Some(available_layers) = tile_repository
|
if let Some(available_layers) =
|
||||||
.iter_tessellated_layers_at(&world_coords)
|
tile_repository.iter_layers_at(&world_coords).map(|layers| {
|
||||||
.map(|layers| {
|
|
||||||
layers
|
layers
|
||||||
.filter(|result| !loaded_layers.contains(&result.layer_name()))
|
.filter(|result| !loaded_layers.contains(&result.layer_name()))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
|||||||
@ -6,10 +6,9 @@ use crate::{
|
|||||||
platform::MIN_WEBGL_BUFFER_SIZE,
|
platform::MIN_WEBGL_BUFFER_SIZE,
|
||||||
render::{
|
render::{
|
||||||
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
|
||||||
settings::Msaa,
|
settings::{Msaa, RendererSettings},
|
||||||
shaders::ShaderGlobals,
|
shaders::ShaderGlobals,
|
||||||
},
|
},
|
||||||
RendererSettings,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TilePipeline {
|
pub struct TilePipeline {
|
||||||
|
|||||||
@ -12,12 +12,13 @@ use request_stage::RequestStage;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
|
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
|
||||||
|
environment::Environment,
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{
|
io::{
|
||||||
apc::{AsyncProcedureCall, Context, Message},
|
apc::{AsyncProcedureCall, Context, Message},
|
||||||
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
|
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
|
||||||
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
pipeline::{PipelineContext, PipelineProcessor, Processable},
|
||||||
source_client::HttpSourceClient,
|
source_client::{HttpClient, HttpSourceClient},
|
||||||
tile_pipelines::build_vector_tile_pipeline,
|
tile_pipelines::build_vector_tile_pipeline,
|
||||||
transferables::{
|
transferables::{
|
||||||
DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
|
DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
|
||||||
@ -26,27 +27,20 @@ use crate::{
|
|||||||
},
|
},
|
||||||
TileRequest,
|
TileRequest,
|
||||||
},
|
},
|
||||||
|
kernel::Kernel,
|
||||||
render::ShaderVertex,
|
render::ShaderVertex,
|
||||||
schedule::Schedule,
|
schedule::Schedule,
|
||||||
stages::populate_tile_store_stage::PopulateTileStore,
|
stages::populate_tile_store_stage::PopulateTileStore,
|
||||||
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||||
Environment, HttpClient, Scheduler,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod populate_tile_store_stage;
|
mod populate_tile_store_stage;
|
||||||
mod request_stage;
|
mod request_stage;
|
||||||
|
|
||||||
/// Register stages required for requesting and preparing new tiles.
|
/// Register stages required for requesting and preparing new tiles.
|
||||||
pub fn register_stages<E: Environment>(
|
pub fn register_stages<E: Environment>(schedule: &mut Schedule, kernel: Rc<Kernel<E>>) {
|
||||||
schedule: &mut Schedule,
|
schedule.add_stage("request", RequestStage::<E>::new(kernel.clone()));
|
||||||
http_source_client: HttpSourceClient<E::HttpClient>,
|
schedule.add_stage("populate_tile_store", PopulateTileStore::<E>::new(kernel));
|
||||||
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 struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<T, HC>> {
|
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
|
impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
|
||||||
for HeadedPipelineProcessor<T, HC, C>
|
for HeadedPipelineProcessor<T, HC, C>
|
||||||
{
|
{
|
||||||
fn tile_finished(&mut self, coords: &WorldTileCoords) {
|
fn tile_finished(&mut self, coords: &WorldTileCoords) -> Result<(), Error> {
|
||||||
self.context
|
self.context
|
||||||
.send(Message::TileTessellated(T::TileTessellated::new(*coords)))
|
.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
|
self.context
|
||||||
.send(Message::UnavailableLayer(T::UnavailableLayer::new(
|
.send(Message::UnavailableLayer(T::UnavailableLayer::new(
|
||||||
*coords,
|
*coords,
|
||||||
@ -77,7 +75,7 @@ impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
|
|||||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||||
feature_indices: Vec<u32>,
|
feature_indices: Vec<u32>,
|
||||||
layer_data: tile::Layer,
|
layer_data: tile::Layer,
|
||||||
) {
|
) -> Result<(), Error> {
|
||||||
self.context
|
self.context
|
||||||
.send(Message::TessellatedLayer(T::TessellatedLayer::new(
|
.send(Message::TessellatedLayer(T::TessellatedLayer::new(
|
||||||
*coords,
|
*coords,
|
||||||
@ -91,11 +89,12 @@ impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
|
|||||||
&mut self,
|
&mut self,
|
||||||
coords: &WorldTileCoords,
|
coords: &WorldTileCoords,
|
||||||
geometries: Vec<IndexedGeometry<f64>>,
|
geometries: Vec<IndexedGeometry<f64>>,
|
||||||
) {
|
) -> Result<(), Error> {
|
||||||
// FIXME (wasm-executor): Readd
|
// FIXME (wasm-executor): Readd
|
||||||
/* if let Ok(mut geometry_index) = self.state.geometry_index.lock() {
|
/* if let Ok(mut geometry_index) = self.state.geometry_index.lock() {
|
||||||
geometry_index.index_tile(coords, TileIndex::Linear { list: geometries })
|
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::{
|
use crate::{
|
||||||
context::MapContext,
|
context::MapContext,
|
||||||
|
environment::Environment,
|
||||||
io::{
|
io::{
|
||||||
apc::{AsyncProcedureCall, Message},
|
apc::{AsyncProcedureCall, Message},
|
||||||
tile_repository::StoredLayer,
|
tile_repository::StoredLayer,
|
||||||
transferables::{TessellatedLayer, TileTessellated, UnavailableLayer},
|
transferables::{TessellatedLayer, TileTessellated, UnavailableLayer},
|
||||||
},
|
},
|
||||||
|
kernel::Kernel,
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
Environment,
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct PopulateTileStore<E: Environment> {
|
pub struct PopulateTileStore<E: Environment> {
|
||||||
apc: Rc<RefCell<E::AsyncProcedureCall>>,
|
kernel: Rc<Kernel<E>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Environment> PopulateTileStore<E> {
|
impl<E: Environment> PopulateTileStore<E> {
|
||||||
pub fn new(apc: Rc<RefCell<E::AsyncProcedureCall>>) -> Self {
|
pub fn new(kernel: Rc<Kernel<E>>) -> Self {
|
||||||
Self { apc }
|
Self { kernel }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,42 +29,43 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
tile_repository, ..
|
world: World {
|
||||||
|
tile_repository, ..
|
||||||
|
},
|
||||||
|
..
|
||||||
}: &mut MapContext,
|
}: &mut MapContext,
|
||||||
) {
|
) {
|
||||||
if let Ok(mut apc) = self.apc.deref().try_borrow_mut() {
|
if let Some(result) = self.kernel.apc().receive() {
|
||||||
if let Some(result) = apc.receive() {
|
match result {
|
||||||
match result {
|
Message::TileTessellated(tranferred) => {
|
||||||
Message::TileTessellated(tranferred) => {
|
let coords = tranferred.coords();
|
||||||
let coords = tranferred.coords();
|
tile_repository.mark_tile_succeeded(coords);
|
||||||
tile_repository.success(coords);
|
tracing::trace!("Tile at {} finished loading", coords);
|
||||||
tracing::trace!("Tile at {} finished loading", coords);
|
log::warn!("Tile at {} finished loading", coords);
|
||||||
log::warn!("Tile at {} finished loading", coords);
|
}
|
||||||
}
|
// FIXME: deduplicate
|
||||||
// FIXME: deduplicate
|
Message::UnavailableLayer(tranferred) => {
|
||||||
Message::UnavailableLayer(tranferred) => {
|
let layer: StoredLayer = tranferred.to_stored_layer();
|
||||||
let layer: StoredLayer = tranferred.to_stored_layer();
|
tracing::debug!(
|
||||||
tracing::debug!(
|
"Layer {} at {} reached main thread",
|
||||||
"Layer {} at {} reached main thread",
|
layer.layer_name(),
|
||||||
layer.layer_name(),
|
layer.get_coords()
|
||||||
layer.get_coords()
|
);
|
||||||
);
|
tile_repository.put_layer(layer);
|
||||||
tile_repository.put_tessellated_layer(layer);
|
}
|
||||||
}
|
Message::TessellatedLayer(data) => {
|
||||||
Message::TessellatedLayer(data) => {
|
let layer: StoredLayer = data.to_stored_layer();
|
||||||
let layer: StoredLayer = data.to_stored_layer();
|
tracing::debug!(
|
||||||
tracing::debug!(
|
"Layer {} at {} reached main thread",
|
||||||
"Layer {} at {} reached main thread",
|
layer.layer_name(),
|
||||||
layer.layer_name(),
|
layer.get_coords()
|
||||||
layer.get_coords()
|
);
|
||||||
);
|
log::warn!(
|
||||||
log::warn!(
|
"Layer {} at {} reached main thread",
|
||||||
"Layer {} at {} reached main thread",
|
layer.layer_name(),
|
||||||
layer.layer_name(),
|
layer.get_coords()
|
||||||
layer.get_coords()
|
);
|
||||||
);
|
tile_repository.put_layer(layer);
|
||||||
tile_repository.put_tessellated_layer(layer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ use std::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
context::MapContext,
|
context::MapContext,
|
||||||
coords::{ViewRegion, WorldTileCoords, ZoomLevel},
|
coords::{ViewRegion, WorldTileCoords, ZoomLevel},
|
||||||
|
environment::Environment,
|
||||||
error::Error,
|
error::Error,
|
||||||
io::{
|
io::{
|
||||||
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
|
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
|
||||||
@ -25,25 +26,20 @@ use crate::{
|
|||||||
transferables::{Transferables, UnavailableLayer},
|
transferables::{Transferables, UnavailableLayer},
|
||||||
TileRequest,
|
TileRequest,
|
||||||
},
|
},
|
||||||
|
kernel::Kernel,
|
||||||
schedule::Stage,
|
schedule::Stage,
|
||||||
stages::HeadedPipelineProcessor,
|
stages::HeadedPipelineProcessor,
|
||||||
Environment, HttpClient, Scheduler, Style,
|
style::Style,
|
||||||
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RequestStage<E: Environment> {
|
pub struct RequestStage<E: Environment> {
|
||||||
apc: Rc<RefCell<E::AsyncProcedureCall>>,
|
kernel: Rc<Kernel<E>>,
|
||||||
http_source_client: HttpSourceClient<E::HttpClient>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Environment> RequestStage<E> {
|
impl<E: Environment> RequestStage<E> {
|
||||||
pub fn new(
|
pub fn new(kernel: Rc<Kernel<E>>) -> Self {
|
||||||
http_source_client: HttpSourceClient<E::HttpClient>,
|
Self { kernel }
|
||||||
apc: Rc<RefCell<E::AsyncProcedureCall>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
apc,
|
|
||||||
http_source_client,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,27 +47,35 @@ impl<E: Environment> Stage for RequestStage<E> {
|
|||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
MapContext {
|
MapContext {
|
||||||
view_state,
|
world:
|
||||||
|
World {
|
||||||
|
tile_repository,
|
||||||
|
view_state,
|
||||||
|
},
|
||||||
style,
|
style,
|
||||||
tile_repository,
|
|
||||||
..
|
..
|
||||||
}: &mut MapContext,
|
}: &mut MapContext,
|
||||||
) {
|
) {
|
||||||
let view_region = view_state.create_view_region();
|
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 {
|
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
|
// 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);
|
self.request_tiles_in_view(tile_repository, style, view_region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
view_state.camera.update_reference();
|
view_state.update_references();
|
||||||
view_state.zoom.update_reference();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
input: Input,
|
||||||
context: C,
|
context: C,
|
||||||
) -> AsyncProcedureFuture {
|
) -> AsyncProcedureFuture {
|
||||||
@ -102,12 +106,15 @@ pub fn schedule<E: Environment, C: Context<E::Transferables, E::HttpClient>>(
|
|||||||
log::error!("{:?}", &e);
|
log::error!("{:?}", &e);
|
||||||
for to_load in &input.layers {
|
for to_load in &input.layers {
|
||||||
tracing::warn!("layer {} at {} unavailable", to_load, coords);
|
tracing::warn!("layer {} at {} unavailable", to_load, coords);
|
||||||
context.send(Message::UnavailableLayer(
|
// FIXME: Handle result
|
||||||
<E::Transferables as Transferables>::UnavailableLayer::new(
|
context.send(
|
||||||
|
Message::UnavailableLayer(<<E::AsyncProcedureCall as AsyncProcedureCall<
|
||||||
|
E::HttpClient,
|
||||||
|
>>::Transferables as Transferables>::UnavailableLayer::new(
|
||||||
input.coords,
|
input.coords,
|
||||||
to_load.to_string(),
|
to_load.to_string(),
|
||||||
),
|
)),
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,7 +139,7 @@ impl<E: Environment> RequestStage<E> {
|
|||||||
for coords in view_region.iter() {
|
for coords in view_region.iter() {
|
||||||
if coords.build_quad_key().is_some() {
|
if coords.build_quad_key().is_some() {
|
||||||
// TODO: Make tesselation depend on style?
|
// 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
|
.unwrap(); // TODO: Remove unwrap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,26 +148,26 @@ impl<E: Environment> RequestStage<E> {
|
|||||||
fn request_tile(
|
fn request_tile(
|
||||||
&self,
|
&self,
|
||||||
tile_repository: &mut TileRepository,
|
tile_repository: &mut TileRepository,
|
||||||
coords: &WorldTileCoords,
|
coords: WorldTileCoords,
|
||||||
layers: &HashSet<String>,
|
layers: &HashSet<String>,
|
||||||
) -> Result<(), Error> {
|
) -> 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);
|
return Ok(false);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
if tile_repository.needs_fetching(&coords) {
|
if tile_repository.has_tile(&coords) {
|
||||||
tile_repository.create_tile(coords);
|
tile_repository.create_tile(coords);
|
||||||
|
|
||||||
tracing::info!("new tile request: {}", &coords);
|
tracing::info!("new tile request: {}", &coords);
|
||||||
self.apc.deref().borrow().schedule(
|
self.kernel.apc().call(
|
||||||
Input::TileRequest(TileRequest {
|
Input::TileRequest(TileRequest {
|
||||||
coords: *coords,
|
coords,
|
||||||
layers: layers.clone(),
|
layers: layers.clone(),
|
||||||
}),
|
}),
|
||||||
schedule::<
|
schedule::<
|
||||||
E,
|
E,
|
||||||
<E::AsyncProcedureCall as AsyncProcedureCall<
|
<E::AsyncProcedureCall as AsyncProcedureCall<
|
||||||
E::Transferables,
|
|
||||||
E::HttpClient,
|
E::HttpClient,
|
||||||
>>::Context,
|
>>::Context,
|
||||||
>,
|
>,
|
||||||
|
|||||||
@ -62,7 +62,6 @@ impl<V, I> OverAlignedVertexBuffer<V, I> {
|
|||||||
V: Copy,
|
V: Copy,
|
||||||
I: Copy,
|
I: Copy,
|
||||||
{
|
{
|
||||||
// FIXME (wasm-executor), make this fn not needed
|
|
||||||
let mut buffers = VertexBuffers::with_capacity(0, 0);
|
let mut buffers = VertexBuffers::with_capacity(0, 0);
|
||||||
buffers.vertices = Vec::from(vertices);
|
buffers.vertices = Vec::from(vertices);
|
||||||
buffers.indices = Vec::from(indices);
|
buffers.indices = Vec::from(indices);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use std::{cell::RefCell, rc::Rc};
|
|||||||
|
|
||||||
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
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.
|
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
|
||||||
pub trait MapWindow {
|
pub trait MapWindow {
|
||||||
@ -16,7 +16,12 @@ pub trait MapWindow {
|
|||||||
pub trait HeadedMapWindow: MapWindow {
|
pub trait HeadedMapWindow: MapWindow {
|
||||||
type RawWindow: HasRawWindowHandle + HasRawDisplayHandle;
|
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
|
/// 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;
|
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.
|
/// Window size with a width and an height in pixels.
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct WindowSize {
|
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]
|
[dependencies]
|
||||||
async-trait = "0.1.56"
|
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" }
|
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
|
||||||
|
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
|
|||||||
@ -121,7 +121,7 @@ const emitTypeScript = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (child.status !== 0) {
|
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) {
|
if (cargo.status !== 0) {
|
||||||
console.error("Failed to execute cargo build")
|
throw new Error("Failed to execute cargo build")
|
||||||
}
|
}
|
||||||
|
|
||||||
let wasmbindgen = spawnSync('wasm-bindgen', [
|
let wasmbindgen = spawnSync('wasm-bindgen', [
|
||||||
@ -170,7 +170,7 @@ const wasmPack = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (wasmbindgen.status !== 0) {
|
if (wasmbindgen.status !== 0) {
|
||||||
console.error("Failed to execute wasm-bindgen")
|
throw new Error("Failed to execute wasm-bindgen")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (release) {
|
if (release) {
|
||||||
@ -186,10 +186,9 @@ const wasmPack = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (wasmOpt.status !== 0) {
|
if (wasmOpt.status !== 0) {
|
||||||
console.error("Failed to execute wasm-opt")
|
throw new Error("Failed to execute wasm-opt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const watchResult = async (result) => {
|
const watchResult = async (result) => {
|
||||||
@ -238,6 +237,7 @@ const esbuild = async (name, globalName = undefined) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
|
try {
|
||||||
console.log("Creating WASM...")
|
console.log("Creating WASM...")
|
||||||
wasmPack();
|
wasmPack();
|
||||||
|
|
||||||
@ -258,6 +258,9 @@ const start = async () => {
|
|||||||
|
|
||||||
console.log("Emitting TypeScript types...")
|
console.log("Emitting TypeScript types...")
|
||||||
emitTypeScript();
|
emitTypeScript();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to start building: " + e.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _ = start()
|
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})
|
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY / PAGES, shared: true})
|
||||||
await maplibre.default(wasmPath, memory)
|
await maplibre.default(wasmPath, memory)
|
||||||
|
|
||||||
maplibre.run(await maplibre.create_map(() => {
|
await maplibre.run_maplibre(() => {
|
||||||
return workerPath ? new Worker(workerPath, {
|
return workerPath ? new Worker(workerPath, {
|
||||||
type: 'module'
|
type: 'module'
|
||||||
}) : MultithreadedPoolWorker();
|
}) : MultithreadedPoolWorker();
|
||||||
}))
|
});
|
||||||
} else {
|
} else {
|
||||||
const memory = new WebAssembly.Memory({initial: 1024, shared: false})
|
const memory = new WebAssembly.Memory({initial: 1024, shared: false})
|
||||||
await maplibre.default(wasmPath, memory);
|
await maplibre.default(wasmPath, memory);
|
||||||
|
|
||||||
let callbacks: {worker_callback?: (message: MessageEvent) => void} = {}
|
await maplibre.run_maplibre((ptr) => {
|
||||||
|
|
||||||
let map = await maplibre.create_map(() => {
|
|
||||||
let worker: Worker = workerPath ? new Worker(workerPath, {
|
let worker: Worker = workerPath ? new Worker(workerPath, {
|
||||||
type: 'module'
|
type: 'module'
|
||||||
}) : PoolWorker();
|
}) : PoolWorker();
|
||||||
|
|
||||||
worker.onmessage = (message: MessageEvent) => {
|
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;
|
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)]
|
#![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::{
|
||||||
use maplibre_winit::winit::{WinitEnvironment, WinitMapWindowConfig};
|
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 wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use crate::platform::http_client::WHATWGFetchHttpClient;
|
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() {
|
if console_log::init_with_level(log::Level::Info).is_err() {
|
||||||
// Failed to initialize logging. No need to log a message.
|
// 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"))]
|
#[cfg(any(feature = "trace"))]
|
||||||
enable_tracing();
|
enable_tracing();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_feature = "atomics"))]
|
#[cfg(not(target_feature = "atomics"))]
|
||||||
pub type MapType = Map<
|
type CurrentEnvironment = WinitEnvironment<
|
||||||
WinitEnvironment<
|
NopScheduler,
|
||||||
NopScheduler,
|
WHATWGFetchHttpClient,
|
||||||
WHATWGFetchHttpClient,
|
platform::singlethreaded::apc::PassingAsyncProcedureCall,
|
||||||
platform::singlethreaded::transferables::LinearTransferables,
|
(),
|
||||||
platform::singlethreaded::apc::PassingAsyncProcedureCall,
|
|
||||||
>,
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
#[cfg(target_feature = "atomics")]
|
#[cfg(target_feature = "atomics")]
|
||||||
pub type MapType = Map<
|
type CurrentEnvironment = WinitEnvironment<
|
||||||
WinitEnvironment<
|
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
|
||||||
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
|
WHATWGFetchHttpClient,
|
||||||
|
maplibre::io::apc::SchedulerAsyncProcedureCall<
|
||||||
WHATWGFetchHttpClient,
|
WHATWGFetchHttpClient,
|
||||||
maplibre::io::transferables::DefaultTransferables,
|
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
|
||||||
maplibre::io::apc::SchedulerAsyncProcedureCall<
|
|
||||||
WHATWGFetchHttpClient,
|
|
||||||
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
|
|
||||||
>,
|
|
||||||
>,
|
>,
|
||||||
|
(),
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
pub type MapType = Map<CurrentEnvironment>;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub async fn create_map(new_worker: js_sys::Function) -> u32 {
|
pub async fn run_maplibre(new_worker: js_sys::Function) {
|
||||||
// Either call forget or the main loop to keep worker loop alive
|
let mut kernel_builder = KernelBuilder::new()
|
||||||
let mut builder = MapBuilder::new()
|
|
||||||
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
|
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
|
||||||
.with_http_client(WHATWGFetchHttpClient::new());
|
.with_http_client(WHATWGFetchHttpClient::new());
|
||||||
|
|
||||||
#[cfg(target_feature = "atomics")]
|
#[cfg(target_feature = "atomics")]
|
||||||
{
|
{
|
||||||
builder = builder
|
kernel_builder = kernel_builder
|
||||||
.with_apc(maplibre::io::apc::SchedulerAsyncProcedureCall::new(
|
.with_apc(maplibre::io::apc::SchedulerAsyncProcedureCall::new(
|
||||||
WHATWGFetchHttpClient::new(),
|
WHATWGFetchHttpClient::new(),
|
||||||
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler::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"))]
|
#[cfg(not(target_feature = "atomics"))]
|
||||||
{
|
{
|
||||||
builder = builder
|
kernel_builder = kernel_builder
|
||||||
.with_apc(platform::singlethreaded::apc::PassingAsyncProcedureCall::new(new_worker, 4))
|
.with_apc(platform::singlethreaded::apc::PassingAsyncProcedureCall::new(new_worker, 4))
|
||||||
.with_scheduler(NopScheduler);
|
.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]
|
map.window_mut()
|
||||||
pub unsafe fn clone_map(map_ptr: *const RefCell<MapType>) -> *const RefCell<MapType> {
|
.take_event_loop()
|
||||||
let mut map = Rc::from_raw(map_ptr);
|
.expect("Event loop is not available")
|
||||||
let rc = map.clone();
|
.run(map, None)
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -6,6 +6,7 @@ use wasm_bindgen::{prelude::*, JsCast};
|
|||||||
use web_sys::Worker;
|
use web_sys::Worker;
|
||||||
|
|
||||||
use super::pool::WorkerPool;
|
use super::pool::WorkerPool;
|
||||||
|
use crate::CurrentEnvironment;
|
||||||
|
|
||||||
pub struct WebWorkerPoolScheduler {
|
pub struct WebWorkerPoolScheduler {
|
||||||
pool: WorkerPool,
|
pool: WorkerPool,
|
||||||
@ -13,19 +14,19 @@ pub struct WebWorkerPoolScheduler {
|
|||||||
|
|
||||||
impl WebWorkerPoolScheduler {
|
impl WebWorkerPoolScheduler {
|
||||||
pub fn new(new_worker: js_sys::Function) -> Self {
|
pub fn new(new_worker: js_sys::Function) -> Self {
|
||||||
Self {
|
// TODO: Are expects here oke?
|
||||||
pool: WorkerPool::new(
|
let pool = WorkerPool::new(
|
||||||
1,
|
1,
|
||||||
Box::new(move || {
|
Box::new(move || {
|
||||||
new_worker
|
new_worker
|
||||||
.call0(&JsValue::undefined())
|
.call0(&JsValue::undefined())
|
||||||
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
.expect("Unable to call new_worker function")
|
||||||
.dyn_into::<Worker>()
|
.dyn_into::<Worker>()
|
||||||
.unwrap() // FIXME (wasm-executor): remove unwrap
|
.expect("new_worker function did not return a Worker")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.unwrap(), // FIXME (wasm-executor): Remove unwrap
|
.expect("Unable to create WorkerPool");
|
||||||
}
|
Self { pool }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,29 +16,33 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use js_sys::Uint8Array;
|
use js_sys::Uint8Array;
|
||||||
|
use log::info;
|
||||||
use maplibre::{
|
use maplibre::{
|
||||||
environment::Environment,
|
environment::Environment,
|
||||||
|
error::Error,
|
||||||
io::{
|
io::{
|
||||||
apc::{AsyncProcedure, AsyncProcedureCall, Context, Input, Message},
|
apc::{AsyncProcedure, AsyncProcedureCall, Context, Input, Message},
|
||||||
scheduler::Scheduler,
|
scheduler::Scheduler,
|
||||||
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
source_client::{HttpClient, HttpSourceClient, SourceClient},
|
||||||
transferables::Transferables,
|
transferables::Transferables,
|
||||||
},
|
},
|
||||||
|
kernel::Kernel,
|
||||||
};
|
};
|
||||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||||
use web_sys::{DedicatedWorkerGlobalScope, Worker};
|
use web_sys::{console::info, DedicatedWorkerGlobalScope, Worker};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
platform::singlethreaded::transferables::{
|
platform::singlethreaded::transferables::{
|
||||||
InnerData, LinearTessellatedLayer, LinearTransferables,
|
InnerData, LinearTessellatedLayer, LinearTransferables,
|
||||||
},
|
},
|
||||||
MapType, WHATWGFetchHttpClient,
|
CurrentEnvironment, MapType, WHATWGFetchHttpClient,
|
||||||
};
|
};
|
||||||
|
|
||||||
type UsedTransferables = LinearTransferables;
|
type UsedTransferables = LinearTransferables;
|
||||||
type UsedHttpClient = WHATWGFetchHttpClient;
|
type UsedHttpClient = WHATWGFetchHttpClient;
|
||||||
type UsedContext = PassingContext;
|
type UsedContext = PassingContext;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum SerializedMessageTag {
|
enum SerializedMessageTag {
|
||||||
TileTessellated = 1,
|
TileTessellated = 1,
|
||||||
UnavailableLayer = 2,
|
UnavailableLayer = 2,
|
||||||
@ -120,7 +124,7 @@ pub struct PassingContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Context<UsedTransferables, UsedHttpClient> for 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 tag = data.tag();
|
||||||
let serialized = data.serialize();
|
let serialized = data.serialize();
|
||||||
|
|
||||||
@ -130,11 +134,12 @@ impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
|
|||||||
serialized_array.set(&Uint8Array::view(serialized), 0);
|
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();
|
let array = js_sys::Array::new();
|
||||||
array.push(&JsValue::from(tag as u32));
|
array.push(&JsValue::from(tag as u32));
|
||||||
array.push(&serialized_array_buffer);
|
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> {
|
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 {
|
pub struct PassingAsyncProcedureCall {
|
||||||
new_worker: Box<dyn Fn() -> Worker>,
|
new_worker: Box<dyn Fn() -> Worker>,
|
||||||
workers: Vec<Worker>,
|
workers: Vec<Worker>,
|
||||||
|
|
||||||
received: Vec<Message<UsedTransferables>>,
|
received: Rc<ReceivedType>, // FIXME (wasm-executor): Is RefCell fine?
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PassingAsyncProcedureCall {
|
impl PassingAsyncProcedureCall {
|
||||||
pub fn new(new_worker: js_sys::Function, initial_workers: u8) -> Self {
|
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 || {
|
let create_new_worker = Box::new(move || {
|
||||||
new_worker
|
new_worker
|
||||||
.call0(&JsValue::undefined())
|
.call1(
|
||||||
|
&JsValue::undefined(),
|
||||||
|
&JsValue::from(Rc::into_raw(received_ref.clone()) as u32),
|
||||||
|
)
|
||||||
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
||||||
.dyn_into::<Worker>()
|
.dyn_into::<Worker>()
|
||||||
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
.unwrap() // FIXME (wasm-executor): Remove unwrap
|
||||||
@ -173,19 +186,20 @@ impl PassingAsyncProcedureCall {
|
|||||||
Self {
|
Self {
|
||||||
new_worker: create_new_worker,
|
new_worker: create_new_worker,
|
||||||
workers,
|
workers,
|
||||||
received: vec![],
|
received,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncProcedureCall<UsedTransferables, UsedHttpClient> for PassingAsyncProcedureCall {
|
impl AsyncProcedureCall<UsedHttpClient> for PassingAsyncProcedureCall {
|
||||||
type Context = UsedContext;
|
type Context = UsedContext;
|
||||||
|
type Transferables = UsedTransferables;
|
||||||
|
|
||||||
fn receive(&mut self) -> Option<Message<UsedTransferables>> {
|
fn receive(&self) -> Option<Message<UsedTransferables>> {
|
||||||
self.received.pop()
|
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 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
|
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 input = serde_json::from_str::<Input>(&input).unwrap(); // FIXME (wasm-executor): Remove unwrap
|
||||||
|
|
||||||
let context = PassingContext {
|
let context = PassingContext {
|
||||||
source_client: SourceClient::Http(HttpSourceClient::new(WHATWGFetchHttpClient::new())),
|
source_client: SourceClient::new(HttpSourceClient::new(WHATWGFetchHttpClient::new())),
|
||||||
};
|
};
|
||||||
|
|
||||||
(procedure)(input, context).await;
|
(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.
|
/// Entry point invoked by the main thread.
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub unsafe fn singlethreaded_main_entry(
|
pub unsafe fn singlethreaded_main_entry(
|
||||||
map_ptr: *const RefCell<MapType>,
|
received_ptr: *const ReceivedType,
|
||||||
type_id: u32,
|
type_id: u32,
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
// FIXME (wasm-executor): Can we make this call safe? check if it was cloned before?
|
// 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(
|
let message = Message::<UsedTransferables>::deserialize(
|
||||||
SerializedMessageTag::from_u32(type_id).unwrap(),
|
SerializedMessageTag::from_u32(type_id).unwrap(),
|
||||||
data,
|
data,
|
||||||
);
|
);
|
||||||
|
|
||||||
map.deref()
|
info!("singlethreaded_main_entry {:?}", message.tag());
|
||||||
.borrow()
|
|
||||||
.map_schedule()
|
|
||||||
.deref()
|
|
||||||
.borrow()
|
|
||||||
.apc
|
|
||||||
.deref()
|
|
||||||
.borrow_mut()
|
|
||||||
.received
|
|
||||||
.push(message);
|
|
||||||
|
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use bytemuck::{TransparentWrapper, Zeroable};
|
use bytemuck::{TransparentWrapper, Zeroable};
|
||||||
use bytemuck_derive::{Pod, Zeroable};
|
use bytemuck_derive::{Pod, Zeroable};
|
||||||
|
use log::warn;
|
||||||
use maplibre::{
|
use maplibre::{
|
||||||
benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer},
|
||||||
coords::WorldTileCoords,
|
coords::WorldTileCoords,
|
||||||
@ -122,6 +123,75 @@ impl TessellatedLayer for LinearTessellatedLayer {
|
|||||||
feature_indices_len: feature_indices.len(),
|
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.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.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);
|
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 {
|
fn to_stored_layer(self) -> StoredLayer {
|
||||||
let layer = StoredLayer::TessellatedLayer {
|
// TODO: Avoid copies here
|
||||||
|
StoredLayer::TessellatedLayer {
|
||||||
coords: WrapperWorldTileCoords::peel(self.data.coords),
|
coords: WrapperWorldTileCoords::peel(self.data.coords),
|
||||||
layer_name: String::from_utf8(Vec::from(
|
layer_name: String::from_utf8(Vec::from(
|
||||||
&self.data.layer_name[..self.data.layer_name_len],
|
&self.data.layer_name[..self.data.layer_name_len],
|
||||||
@ -143,9 +214,7 @@ impl TessellatedLayer for LinearTessellatedLayer {
|
|||||||
self.data.usable_indices,
|
self.data.usable_indices,
|
||||||
),
|
),
|
||||||
feature_indices: Vec::from(&self.data.feature_indices[..self.data.feature_indices_len]),
|
feature_indices: Vec::from(&self.data.feature_indices[..self.data.feature_indices_len]),
|
||||||
};
|
}
|
||||||
|
|
||||||
layer
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user