mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
Headless windows & Data pipeline (#119)
* Implement headless rendering * Simplify window traits * Simplify and fix headless rendering * Rename HttpClient * Ignore wasm-pack output * Refactor stages and context * Rename runnable to eventloop and fix variable names * Fix tests * Add pipeline draft * Start implementing pipeline steps * Use pipeline in headless rendering * Improve the style significantly. The colors are taken from the default OSM map style * Start working on a specific headless API * Refactor thread state and pipeline * Refactor thread state and pipeline * Fix web * Remove mem::replace usage * Improve pipeline types * Simplify pipeline API * Add comment * Fix tests * Remove dynamic dispatch for schedule * Add Run Test config * Revive old legacy webworker example * Fix resize detection * Cleanup dependencies and simplify bounds * Conditionally enable create_png * Add some comments and rename * Run more tests in CI * Fix tests for various platforms * Install missing dependency
This commit is contained in:
commit
8248d4b4d7
@ -7,4 +7,5 @@ rustflags = [
|
||||
# Enables the possibility to import memory into wasm.
|
||||
# Without --shared-memory it is not possible to use shared WebAssembly.Memory.
|
||||
"-C", "link-args=--shared-memory --import-memory",
|
||||
]
|
||||
]
|
||||
runner = 'wasm-bindgen-test-runner'
|
||||
|
||||
6
.github/actions/android/action.yml
vendored
6
.github/actions/android/action.yml
vendored
@ -12,6 +12,7 @@ runs:
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: just build-android
|
||||
# TODO: Additional clippy checks for different targets
|
||||
- name: Check x86_64
|
||||
shell: bash
|
||||
run: |
|
||||
@ -24,3 +25,8 @@ runs:
|
||||
env "AR_aarch64-linux-android=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" \
|
||||
env "CC_aarch64-linux-android=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang" \
|
||||
just check maplibre-android aarch64-linux-android
|
||||
# FIXME: Requires cross-compilation
|
||||
#- name: Test
|
||||
# shell: bash
|
||||
# # TODO: Additional test runs for different targets
|
||||
# run: just test maplibre-android aarch64-linux-android
|
||||
10
.github/actions/apple/action.yml
vendored
10
.github/actions/apple/action.yml
vendored
@ -22,10 +22,14 @@ runs:
|
||||
- name: Check x86_64 darwin
|
||||
shell: bash
|
||||
run: just check apple x86_64-apple-darwin
|
||||
- name: Check aarch64 darwin
|
||||
- name: Check x86_64 darwin
|
||||
shell: bash
|
||||
run: just check apple aarch64-apple-darwin
|
||||
# TODO: Additional clippy checks for iOS
|
||||
# TODO: Additional clippy checks for different targets (iOS)
|
||||
run: just check apple x86_64-apple-darwin
|
||||
- name: Test x86_64 darwin
|
||||
shell: bash
|
||||
# TODO: Additional test runs for different targets (Different targets might require emulation)
|
||||
run: just test apple x86_64-apple-darwin
|
||||
- name: Build Example
|
||||
shell: bash
|
||||
run: cd apple/xcode && xcodebuild -scheme "example (iOS)" -arch arm64 -sdk iphoneos build CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
6
.github/actions/demo/linux/action.yml
vendored
6
.github/actions/demo/linux/action.yml
vendored
@ -15,12 +15,12 @@ runs:
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cargo build -p maplibre-demo
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: just test maplibre-demo x86_64-unknown-linux-gnu
|
||||
- name: Check
|
||||
shell: bash
|
||||
run: just check maplibre-demo x86_64-unknown-linux-gnu
|
||||
- name: Test x86_64 linux
|
||||
shell: bash
|
||||
run: just test maplibre-demo x86_64-unknown-linux-gnu
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: maplibre-rs
|
||||
|
||||
6
.github/actions/demo/macos/action.yml
vendored
6
.github/actions/demo/macos/action.yml
vendored
@ -19,6 +19,12 @@ runs:
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cd apple/xcode && xcodebuild -scheme "example (macOS)" build CODE_SIGNING_ALLOWED=NO MACOSX_DEPLOYMENT_TARGET=10.9 -derivedDataPath build
|
||||
- name: Check x86_64 darwin
|
||||
shell: bash
|
||||
run: just check maplibre-demo x86_64-apple-darwin
|
||||
- name: Test x86_64 darwin
|
||||
shell: bash
|
||||
run: just test maplibre-demo x86_64-apple-darwin
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: maplibre-x86_64-apple-darwin-demo
|
||||
|
||||
5
.github/actions/demo/windows/action.yml
vendored
5
.github/actions/demo/windows/action.yml
vendored
@ -22,7 +22,10 @@ runs:
|
||||
- name: Build
|
||||
shell: bash
|
||||
run: cargo build -p maplibre-demo --release --target x86_64-pc-windows-msvc
|
||||
- name: Test
|
||||
- name: Check x86_64 windows
|
||||
shell: bash
|
||||
run: just check maplibre-demo x86_64-pc-windows-msvc
|
||||
- name: Test x86_64 windows
|
||||
shell: bash
|
||||
run: just test maplibre-demo x86_64-pc-windows-msvc
|
||||
- uses: actions/upload-artifact@v3
|
||||
|
||||
18
.github/actions/tests/action.yml
vendored
Normal file
18
.github/actions/tests/action.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
name: tests
|
||||
description: Run tests
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: extractions/setup-just@v1
|
||||
- name: Install toolchain
|
||||
shell: bash
|
||||
run: just default-toolchain
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: sudo apt-get install -y libwayland-dev libxkbcommon-dev # Required for winit
|
||||
- name: Test
|
||||
shell: bash
|
||||
# TODO: Additional test runs for different targets
|
||||
run: just test maplibre x86_64-unknown-linux-gnu
|
||||
5
.github/actions/webgl/action.yml
vendored
5
.github/actions/webgl/action.yml
vendored
@ -18,3 +18,8 @@ runs:
|
||||
- name: Check
|
||||
shell: bash
|
||||
run: just check web wasm32-unknown-unknown
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
cargo install wasm-bindgen-cli --version "0.2.80"
|
||||
just web-test "web-webgl"
|
||||
|
||||
7
.github/actions/webgpu/action.yml
vendored
7
.github/actions/webgpu/action.yml
vendored
@ -17,4 +17,9 @@ runs:
|
||||
run: just web-demo build
|
||||
- name: Check
|
||||
shell: bash
|
||||
run: just check web wasm32-unknown-unknown
|
||||
run: just check web wasm32-unknown-unknown
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: |
|
||||
cargo install wasm-bindgen-cli --version "0.2.80"
|
||||
just web-test ""
|
||||
|
||||
7
.github/workflows/on_main_push.yml
vendored
7
.github/workflows/on_main_push.yml
vendored
@ -17,6 +17,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/benchmarks
|
||||
run-tests:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/tests
|
||||
build-android:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
@ -57,7 +62,7 @@ jobs:
|
||||
source: docs/book/.
|
||||
destination: docs
|
||||
key: ${{ secrets.SSH_KEY_MAXAMMANN_ORG }}
|
||||
build-ios:
|
||||
build-apple:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
7
.github/workflows/on_pull_request.yml
vendored
7
.github/workflows/on_pull_request.yml
vendored
@ -17,6 +17,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/benchmarks
|
||||
run-tests:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/tests
|
||||
build-android:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
@ -37,7 +42,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/docs
|
||||
build-ios:
|
||||
build-apple:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
3
.idea/maplibre-rs.iml
generated
3
.idea/maplibre-rs.iml
generated
@ -20,9 +20,10 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/docs/book" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/web/lib/.parcel-cache" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/web/lib/src/wasm-pack" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/maplibre-cache" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
||||
|
||||
19
.idea/runConfigurations/Run_Tests.xml
generated
Normal file
19
.idea/runConfigurations/Run_Tests.xml
generated
Normal file
@ -0,0 +1,19 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Run Tests" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="test --package maplibre" />
|
||||
<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 />
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
8
justfile
8
justfile
@ -45,12 +45,18 @@ web-lib TARGET: nightly-toolchain (web-install "lib")
|
||||
web-demo TARGET: (web-install "demo")
|
||||
cd web/demo && npm run {{TARGET}}
|
||||
|
||||
web-test FEATURES: nightly-toolchain
|
||||
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo test -p web --features "{{FEATURES}}" --target wasm32-unknown-unknown -Z build-std=std,panic_abort
|
||||
|
||||
#profile-bench:
|
||||
# cargo flamegraph --bench render -- --bench
|
||||
|
||||
build-android: print-android-env
|
||||
build-android: nightly-toolchain print-android-env
|
||||
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew assembleDebug
|
||||
|
||||
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
|
||||
|
||||
# language=bash
|
||||
print-android-env:
|
||||
#!/usr/bin/env bash
|
||||
|
||||
@ -14,7 +14,7 @@ trace = ["maplibre/trace", "tracing-subscriber", "tracing-tracy", "tracy-client"
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.9"
|
||||
maplibre = { path = "../maplibre", version = "0.0.2" }
|
||||
maplibre = { path = "../maplibre", version = "0.0.2", features = ["headless"] }
|
||||
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
|
||||
|
||||
tracing = { version = "0.1" }
|
||||
|
||||
@ -1,8 +1,24 @@
|
||||
use maplibre::benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer};
|
||||
use maplibre::coords::{WorldTileCoords, ZoomLevel};
|
||||
use maplibre::error::Error;
|
||||
use maplibre::io::pipeline::Processable;
|
||||
use maplibre::io::pipeline::{PipelineContext, PipelineProcessor};
|
||||
use maplibre::io::scheduler::ScheduleMethod;
|
||||
use maplibre::io::source_client::{HttpClient, HttpSourceClient};
|
||||
use maplibre::io::tile_pipelines::build_vector_tile_pipeline;
|
||||
use maplibre::io::tile_repository::StoredLayer;
|
||||
use maplibre::io::{RawLayer, TileRequest, TileRequestID};
|
||||
use maplibre::map_schedule::{EventuallyMapContext, InteractiveMapSchedule};
|
||||
use maplibre::platform::http_client::ReqwestHttpClient;
|
||||
use maplibre::platform::run_multithreaded;
|
||||
use maplibre::platform::schedule_method::TokioScheduleMethod;
|
||||
use maplibre::render::settings::{RendererSettings, TextureFormat};
|
||||
use maplibre::render::ShaderVertex;
|
||||
use maplibre::window::{EventLoop, MapWindow, MapWindowConfig, WindowSize};
|
||||
use maplibre::MapBuilder;
|
||||
use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
|
||||
use std::any::Any;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
fn enable_tracing() {
|
||||
@ -13,6 +29,27 @@ fn enable_tracing() {
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
|
||||
}
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
fn run_in_window() {
|
||||
run_multithreaded(async {
|
||||
@ -27,11 +64,99 @@ fn run_in_window() {
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct HeadlessPipelineProcessor {
|
||||
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,
|
||||
buffer,
|
||||
feature_indices,
|
||||
layer_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn run_headless() {
|
||||
run_multithreaded(async {
|
||||
let mut map = MapBuilder::new()
|
||||
.with_map_window_config(HeadlessMapWindowConfig {
|
||||
size: WindowSize::new(1000, 1000).unwrap(),
|
||||
})
|
||||
.with_http_client(ReqwestHttpClient::new(None))
|
||||
.with_schedule_method(TokioScheduleMethod::new())
|
||||
.with_renderer_settings(RendererSettings {
|
||||
texture_format: TextureFormat::Rgba8UnormSrgb,
|
||||
..RendererSettings::default()
|
||||
})
|
||||
.build()
|
||||
.initialize_headless()
|
||||
.await;
|
||||
|
||||
let http_source_client: HttpSourceClient<ReqwestHttpClient> =
|
||||
HttpSourceClient::new(ReqwestHttpClient::new(None));
|
||||
|
||||
let coords = WorldTileCoords::from((0, 0, ZoomLevel::default()));
|
||||
let request_id = 0;
|
||||
|
||||
let data = http_source_client
|
||||
.fetch(&coords)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_boxed_slice();
|
||||
|
||||
let processor = HeadlessPipelineProcessor::default();
|
||||
let mut pipeline_context = PipelineContext::new(processor);
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
pipeline.process(
|
||||
(
|
||||
TileRequest {
|
||||
coords,
|
||||
layers: HashSet::from(["boundary".to_owned(), "water".to_owned()]),
|
||||
},
|
||||
request_id,
|
||||
data,
|
||||
),
|
||||
&mut pipeline_context,
|
||||
);
|
||||
|
||||
let mut processor = pipeline_context
|
||||
.take_processor::<HeadlessPipelineProcessor>()
|
||||
.unwrap();
|
||||
|
||||
while let Some(v) = processor.layers.pop() {
|
||||
map.map_schedule_mut()
|
||||
.map_context
|
||||
.tile_repository
|
||||
.put_tessellated_layer(v);
|
||||
}
|
||||
|
||||
match map.map_schedule_mut().update_and_redraw() {
|
||||
Ok(_) => {}
|
||||
Err(Error::Render(e)) => {
|
||||
eprintln!("{}", e);
|
||||
if e.should_exit() {}
|
||||
}
|
||||
e => eprintln!("{:?}", e),
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
enable_tracing();
|
||||
|
||||
run_in_window()
|
||||
//run_headless();
|
||||
run_in_window();
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
use instant::Instant;
|
||||
use maplibre::error::Error;
|
||||
use maplibre::io::scheduler::ScheduleMethod;
|
||||
use maplibre::io::source_client::HTTPClient;
|
||||
use maplibre::io::source_client::HttpClient;
|
||||
use std::borrow::BorrowMut;
|
||||
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::ControlFlow;
|
||||
|
||||
use crate::input::{InputController, UpdateState};
|
||||
use maplibre::map_schedule::MapSchedule;
|
||||
use maplibre::window::{MapWindow, MapWindowConfig, Runnable};
|
||||
use maplibre::map_schedule::InteractiveMapSchedule;
|
||||
use maplibre::window::{EventLoop, HeadedMapWindow, MapWindow, MapWindowConfig};
|
||||
use winit::event::Event;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@ -47,10 +47,6 @@ impl WinitMapWindowConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindowConfig for WinitMapWindowConfig {
|
||||
type MapWindow = WinitMapWindow;
|
||||
}
|
||||
|
||||
pub struct WinitMapWindow {
|
||||
window: WinitWindow,
|
||||
event_loop: Option<WinitEventLoop>,
|
||||
@ -69,13 +65,17 @@ impl WinitMapWindow {
|
||||
///* Input (Mouse/Keyboard)
|
||||
///* Platform Events like suspend/resume
|
||||
///* Render a new frame
|
||||
impl<MWC, SM, HC> Runnable<MWC, SM, HC> for WinitMapWindow
|
||||
impl<MWC, SM, HC> EventLoop<MWC, SM, HC> for WinitMapWindow
|
||||
where
|
||||
MWC: MapWindowConfig<MapWindow = WinitMapWindow>,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
fn run(mut self, mut map_state: MapSchedule<MWC, SM, HC>, max_frames: Option<u64>) {
|
||||
fn run(
|
||||
mut self,
|
||||
mut map_schedule: InteractiveMapSchedule<MWC, SM, HC>,
|
||||
max_frames: Option<u64>,
|
||||
) {
|
||||
let mut last_render_time = Instant::now();
|
||||
let mut current_frame: u64 = 0;
|
||||
|
||||
@ -85,13 +85,13 @@ where
|
||||
.unwrap()
|
||||
.run(move |event, _, control_flow| {
|
||||
#[cfg(target_os = "android")]
|
||||
if !map_state.is_initialized() && event == Event::Resumed {
|
||||
if !map_schedule.is_initialized() && event == Event::Resumed {
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task;
|
||||
|
||||
let state = task::block_in_place(|| {
|
||||
Handle::current().block_on(async {
|
||||
map_state.late_init().await;
|
||||
map_schedule.late_init().await;
|
||||
})
|
||||
});
|
||||
return;
|
||||
@ -122,10 +122,10 @@ where
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
map_state.resize(physical_size.width, physical_size.height);
|
||||
map_schedule.resize(physical_size.width, physical_size.height);
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
|
||||
map_state.resize(new_inner_size.width, new_inner_size.height);
|
||||
map_schedule.resize(new_inner_size.width, new_inner_size.height);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -136,11 +136,9 @@ where
|
||||
let dt = now - last_render_time;
|
||||
last_render_time = now;
|
||||
|
||||
{
|
||||
input_controller.update_state(map_state.view_state_mut(), dt);
|
||||
}
|
||||
input_controller.update_state(map_schedule.view_state_mut(), dt);
|
||||
|
||||
match map_state.update_and_redraw() {
|
||||
match map_schedule.update_and_redraw() {
|
||||
Ok(_) => {}
|
||||
Err(Error::Render(e)) => {
|
||||
eprintln!("{}", e);
|
||||
@ -161,10 +159,10 @@ where
|
||||
}
|
||||
}
|
||||
Event::Suspended => {
|
||||
map_state.suspend();
|
||||
map_schedule.suspend();
|
||||
}
|
||||
Event::Resumed => {
|
||||
map_state.resume(&self);
|
||||
map_schedule.resume(&self);
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
// RedrawRequested will only trigger once, unless we manually
|
||||
|
||||
@ -10,26 +10,9 @@ use super::WinitMapWindow;
|
||||
use super::WinitWindow;
|
||||
|
||||
use super::WinitMapWindowConfig;
|
||||
use maplibre::window::{MapWindow, WindowSize};
|
||||
use maplibre::window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};
|
||||
|
||||
impl MapWindow for WinitMapWindow {
|
||||
type EventLoop = WinitEventLoop;
|
||||
type Window = WinitWindow;
|
||||
type MapWindowConfig = WinitMapWindowConfig;
|
||||
|
||||
fn create(map_window_config: &Self::MapWindowConfig) -> Self {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(&map_window_config.title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
}
|
||||
}
|
||||
|
||||
fn size(&self) -> WindowSize {
|
||||
let size = self.window.inner_size();
|
||||
#[cfg(target_os = "android")]
|
||||
@ -43,8 +26,28 @@ impl MapWindow for WinitMapWindow {
|
||||
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.");
|
||||
window_size
|
||||
}
|
||||
}
|
||||
impl HeadedMapWindow for WinitMapWindow {
|
||||
type RawWindow = WinitWindow;
|
||||
|
||||
fn inner(&self) -> &Self::Window {
|
||||
fn inner(&self) -> &Self::RawWindow {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindowConfig for WinitMapWindowConfig {
|
||||
type MapWindow = WinitMapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(&self.title)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
Self::MapWindow {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,38 +4,43 @@ use super::WinitEventLoop;
|
||||
use super::WinitMapWindow;
|
||||
use super::WinitMapWindowConfig;
|
||||
use super::WinitWindow;
|
||||
use maplibre::window::HeadedMapWindow;
|
||||
use maplibre::window::MapWindowConfig;
|
||||
|
||||
use maplibre::window::{MapWindow, WindowSize};
|
||||
use winit::platform::web::WindowBuilderExtWebSys;
|
||||
|
||||
impl MapWindow for WinitMapWindow {
|
||||
type EventLoop = WinitEventLoop;
|
||||
type Window = WinitWindow;
|
||||
type MapWindowConfig = WinitMapWindowConfig;
|
||||
impl MapWindowConfig for WinitMapWindowConfig {
|
||||
type MapWindow = WinitMapWindow;
|
||||
|
||||
fn create(map_window_config: &Self::MapWindowConfig) -> Self {
|
||||
fn create(&self) -> Self::MapWindow {
|
||||
let event_loop = WinitEventLoop::new();
|
||||
|
||||
let window: winit::window::Window = WindowBuilder::new()
|
||||
.with_canvas(Some(get_canvas(&map_window_config.canvas_id)))
|
||||
.with_canvas(Some(get_canvas(&self.canvas_id)))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let size = get_body_size().unwrap();
|
||||
window.set_inner_size(size);
|
||||
Self {
|
||||
Self::MapWindow {
|
||||
window,
|
||||
event_loop: Some(event_loop),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWindow for WinitMapWindow {
|
||||
fn size(&self) -> WindowSize {
|
||||
let size = self.window.inner_size();
|
||||
|
||||
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.")
|
||||
}
|
||||
}
|
||||
impl HeadedMapWindow for WinitMapWindow {
|
||||
type RawWindow = WinitWindow;
|
||||
|
||||
fn inner(&self) -> &Self::Window {
|
||||
fn inner(&self) -> &Self::RawWindow {
|
||||
&self.window
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ web-webgl = ["wgpu/webgl"]
|
||||
trace = [ "tracing-subscriber", "tracing-tracy", "tracy-client"]
|
||||
no-thread-safe-futures = []
|
||||
embed-static-tiles = ["maplibre-build-tools/sqlite"]
|
||||
headless = ["png"]
|
||||
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android", target_os = "windows"))'.dependencies]
|
||||
@ -33,24 +34,25 @@ reqwest = { version = "0.11", default-features = false, features = ["rustls-tls"
|
||||
async-trait = "0.1"
|
||||
instant = { version = "0.1", features = ["wasm-bindgen"] } # FIXME: Untrusted dependency
|
||||
|
||||
raw-window-handle = "0.4"
|
||||
|
||||
# Tracing
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3", optional = true }
|
||||
|
||||
# Maths
|
||||
cgmath = "0.18"
|
||||
|
||||
# Geo
|
||||
geo = { version = "0.19" }
|
||||
geo-types = { version = "0.7", features = ["use-rstar_0_9"] }
|
||||
rstar = { version = "0.9" }
|
||||
prost = "0.10.1"
|
||||
geozero = { version = "0.9.4", default-features = false, features = ["with-mvt", "with-geo"]}
|
||||
|
||||
tile-grid = "0.3"
|
||||
|
||||
# Rendering
|
||||
wgpu = { version = "0.12" }
|
||||
lyon = { version = "0.17", features = [] }
|
||||
raw-window-handle = "0.4"
|
||||
|
||||
# cached = "0.32"
|
||||
|
||||
@ -61,16 +63,24 @@ log = "0.4"
|
||||
bytemuck = "1.2.0"
|
||||
bytemuck_derive = "1.0"
|
||||
|
||||
# Static tiles inclusion
|
||||
include_dir = "0.7.2"
|
||||
|
||||
# JSON
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
# Colors
|
||||
csscolorparser = { version = "0.5", features = ["serde", "cint"]}
|
||||
cint = "0.2"
|
||||
|
||||
# Required by bevy renderer
|
||||
thiserror = "1"
|
||||
downcast-rs = "1.2"
|
||||
smallvec = "1.8"
|
||||
|
||||
# Headless
|
||||
png = { version = "0.17", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
maplibre-build-tools = { path = "../maplibre-build-tools", version = "0.1.0", features = ["sqlite"] }
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
use crate::coords::{Zoom, ZoomLevel, TILE_SIZE};
|
||||
use crate::io::shared_thread_state::SharedThreadState;
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::TessellateMessage;
|
||||
use crate::io::tile_repository::TileRepository;
|
||||
use crate::render::camera::{Camera, Perspective, ViewProjection};
|
||||
use crate::util::ChangeObserver;
|
||||
use crate::{Renderer, ScheduleMethod, Style, WindowSize};
|
||||
@ -57,14 +55,11 @@ impl ViewState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the context of the map.
|
||||
pub struct MapContext {
|
||||
pub view_state: ViewState,
|
||||
pub style: Style,
|
||||
|
||||
pub tile_cache: TileCache,
|
||||
pub tile_repository: TileRepository,
|
||||
pub renderer: Renderer,
|
||||
pub scheduler: Box<dyn ScheduleMethod>,
|
||||
|
||||
pub message_receiver: mpsc::Receiver<TessellateMessage>,
|
||||
pub shared_thread_state: SharedThreadState,
|
||||
}
|
||||
|
||||
@ -1,96 +1,21 @@
|
||||
//! Handles IO related processing as well as multithreading.
|
||||
|
||||
use crate::coords::WorldTileCoords;
|
||||
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
|
||||
|
||||
use crate::render::ShaderVertex;
|
||||
use geozero::mvt::tile;
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
pub mod scheduler;
|
||||
pub mod source_client;
|
||||
pub mod static_tile_fetcher;
|
||||
|
||||
pub mod geometry_index;
|
||||
pub mod shared_thread_state;
|
||||
pub mod tile_cache;
|
||||
pub mod pipeline;
|
||||
pub mod tile_pipelines;
|
||||
pub mod tile_repository;
|
||||
pub mod tile_request_state;
|
||||
|
||||
/// Contains a `Tile` if the fetch was successful otherwise `Unavailable`.
|
||||
pub enum TileFetchResult {
|
||||
Unavailable {
|
||||
coords: WorldTileCoords,
|
||||
},
|
||||
Tile {
|
||||
coords: WorldTileCoords,
|
||||
data: Box<[u8]>,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for TileFetchResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"TileFetchResult({})",
|
||||
match self {
|
||||
TileFetchResult::Unavailable { coords, .. } => coords,
|
||||
TileFetchResult::Tile { coords, .. } => coords,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// [crate::io::TileTessellateMessage] or [crate::io::LayerTessellateMessage] tessellation message.
|
||||
pub enum TessellateMessage {
|
||||
Tile(TileTessellateMessage),
|
||||
Layer(LayerTessellateMessage),
|
||||
}
|
||||
|
||||
/// The result of the tessellation of a tile.
|
||||
pub struct TileTessellateMessage {
|
||||
pub request_id: TileRequestID,
|
||||
pub coords: WorldTileCoords,
|
||||
}
|
||||
|
||||
/// `TessellatedLayer` contains the result of the tessellation for a specific layer, otherwise
|
||||
/// `UnavailableLayer` if the layer doesn't exist.
|
||||
pub enum LayerTessellateMessage {
|
||||
UnavailableLayer {
|
||||
coords: WorldTileCoords,
|
||||
layer_name: String,
|
||||
},
|
||||
TessellatedLayer {
|
||||
coords: WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
/// Holds for each feature the count of indices.
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: tile::Layer,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for LayerTessellateMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "LayerTessellateMessage{}", self.get_coords())
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerTessellateMessage {
|
||||
pub fn get_coords(&self) -> WorldTileCoords {
|
||||
match self {
|
||||
LayerTessellateMessage::UnavailableLayer { coords, .. } => *coords,
|
||||
LayerTessellateMessage::TessellatedLayer { coords, .. } => *coords,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer_name(&self) -> &str {
|
||||
match self {
|
||||
LayerTessellateMessage::UnavailableLayer { layer_name, .. } => layer_name.as_str(),
|
||||
LayerTessellateMessage::TessellatedLayer { layer_data, .. } => &layer_data.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use geozero::mvt::tile::Layer as RawLayer;
|
||||
|
||||
/// A request for a tile at the given coordinates and in the given layers.
|
||||
#[derive(Clone)]
|
||||
|
||||
243
maplibre/src/io/pipeline.rs
Normal file
243
maplibre/src/io/pipeline.rs
Normal file
@ -0,0 +1,243 @@
|
||||
use crate::coords::WorldTileCoords;
|
||||
use crate::io::geometry_index::IndexedGeometry;
|
||||
use crate::io::TileRequestID;
|
||||
use crate::render::ShaderVertex;
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use geozero::mvt::tile;
|
||||
use std::any::Any;
|
||||
use std::marker::PhantomData;
|
||||
use std::process::Output;
|
||||
use std::sync::mpsc;
|
||||
|
||||
/// Processes events which happen during the pipeline execution
|
||||
pub trait PipelineProcessor: Downcast {
|
||||
fn tile_finished(&mut self, request_id: TileRequestID, coords: &WorldTileCoords) {}
|
||||
fn layer_unavailable(&mut self, coords: &WorldTileCoords, layer_name: &str) {}
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: tile::Layer,
|
||||
) {
|
||||
}
|
||||
fn layer_indexing_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
geometries: Vec<IndexedGeometry<f64>>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl_downcast!(PipelineProcessor);
|
||||
|
||||
/// Context which is available to each step within a [`DataPipeline`]
|
||||
pub struct PipelineContext {
|
||||
processor: Box<dyn PipelineProcessor>,
|
||||
}
|
||||
|
||||
impl PipelineContext {
|
||||
pub fn new<P>(processor: P) -> Self
|
||||
where
|
||||
P: PipelineProcessor,
|
||||
{
|
||||
Self {
|
||||
processor: Box::new(processor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_processor<P>(self) -> Option<Box<P>>
|
||||
where
|
||||
P: PipelineProcessor,
|
||||
{
|
||||
self.processor.into_any().downcast::<P>().ok()
|
||||
}
|
||||
|
||||
pub fn processor_mut(&mut self) -> &mut dyn PipelineProcessor {
|
||||
self.processor.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Processable {
|
||||
type Input;
|
||||
type Output;
|
||||
|
||||
fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output;
|
||||
}
|
||||
|
||||
/// A pipeline which consists of multiple steps. Steps are [`Processable`] workloads. Later steps
|
||||
/// depend on previous ones.
|
||||
pub struct DataPipeline<P, N>
|
||||
where
|
||||
P: Processable,
|
||||
N: Processable<Input = P::Output>,
|
||||
{
|
||||
step: P,
|
||||
next_step: N,
|
||||
}
|
||||
|
||||
impl<P, N> DataPipeline<P, N>
|
||||
where
|
||||
P: Processable,
|
||||
N: Processable<Input = P::Output>,
|
||||
{
|
||||
pub fn new(step: P, next_step: N) -> Self {
|
||||
Self { step, next_step }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, N> Processable for DataPipeline<P, N>
|
||||
where
|
||||
P: Processable,
|
||||
N: Processable<Input = P::Output>,
|
||||
{
|
||||
type Input = P::Input;
|
||||
type Output = N::Output;
|
||||
|
||||
fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output {
|
||||
let output = self.step.process(input, context);
|
||||
self.next_step.process(output, context)
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the end of a [`DataPipeline`]
|
||||
pub struct PipelineEnd<I> {
|
||||
phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<I> Default for PipelineEnd<I> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Processable for PipelineEnd<I> {
|
||||
type Input = I;
|
||||
type Output = I;
|
||||
|
||||
fn process(&self, input: Self::Input, _context: &mut PipelineContext) -> Self::Output {
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Processable for &fn(input: I, context: &mut PipelineContext) -> O {
|
||||
type Input = I;
|
||||
type Output = O;
|
||||
|
||||
fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output {
|
||||
(self)(input, context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O> Processable for fn(input: I, context: &mut PipelineContext) -> O {
|
||||
type Input = I;
|
||||
type Output = O;
|
||||
|
||||
fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output {
|
||||
(self)(input, context)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Implementing Processable directly on Fn is not possible for some strange reason:
|
||||
// https://github.com/rust-lang/rust/issues/25041
|
||||
pub struct ClosureProcessable<F, I, O>
|
||||
where
|
||||
F: Fn(I, &mut PipelineContext) -> O,
|
||||
{
|
||||
func: F,
|
||||
phantom_i: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<F, I, O> From<F> for ClosureProcessable<F, I, O>
|
||||
where
|
||||
F: Fn(I, &mut PipelineContext) -> O,
|
||||
{
|
||||
fn from(func: F) -> Self {
|
||||
ClosureProcessable {
|
||||
func,
|
||||
phantom_i: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, I, O> Processable for ClosureProcessable<F, I, O>
|
||||
where
|
||||
F: Fn(I, &mut PipelineContext) -> O,
|
||||
{
|
||||
type Input = I;
|
||||
type Output = O;
|
||||
|
||||
fn process(&self, input: Self::Input, context: &mut PipelineContext) -> Self::Output {
|
||||
(self.func)(input, context)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::io::pipeline::{
|
||||
ClosureProcessable, DataPipeline, PipelineContext, PipelineEnd, PipelineProcessor,
|
||||
Processable,
|
||||
};
|
||||
use std::sync::mpsc;
|
||||
|
||||
pub struct DummyPipelineProcessor;
|
||||
|
||||
impl PipelineProcessor for DummyPipelineProcessor {}
|
||||
|
||||
fn add_one(input: u32, context: &mut PipelineContext) -> u8 {
|
||||
input as u8 + 1
|
||||
}
|
||||
|
||||
fn add_two(input: u8, context: &mut PipelineContext) -> u32 {
|
||||
input as u32 + 2
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fn_pointer() {
|
||||
let mut context = PipelineContext::new(DummyPipelineProcessor);
|
||||
let output: u32 = DataPipeline::new(
|
||||
add_two as fn(u8, &mut PipelineContext) -> u32,
|
||||
PipelineEnd::default(),
|
||||
)
|
||||
.process(5u8, &mut context);
|
||||
assert_eq!(output, 7);
|
||||
|
||||
let output: u32 = DataPipeline::new(
|
||||
add_one as fn(u32, &mut PipelineContext) -> u8,
|
||||
DataPipeline::new(
|
||||
add_two as fn(u8, &mut PipelineContext) -> u32,
|
||||
PipelineEnd::default(),
|
||||
),
|
||||
)
|
||||
.process(5u32, &mut context);
|
||||
assert_eq!(output, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_closure() {
|
||||
let mut context = PipelineContext::new(DummyPipelineProcessor);
|
||||
let mut outer_value = 3;
|
||||
|
||||
// using from()
|
||||
let closure = ClosureProcessable::from(|input: u8, context: &mut PipelineContext| -> u32 {
|
||||
return input as u32 + 2 + outer_value;
|
||||
});
|
||||
let output: u32 =
|
||||
DataPipeline::new(closure, PipelineEnd::default()).process(5u8, &mut context);
|
||||
assert_eq!(output, 10);
|
||||
|
||||
// with into()
|
||||
let output: u32 = DataPipeline::<ClosureProcessable<_, u8, u32>, _>::new(
|
||||
(|input: u8, context: &mut PipelineContext| -> u32 {
|
||||
return input as u32 + 2 + outer_value;
|
||||
})
|
||||
.into(),
|
||||
PipelineEnd::<u32>::default(),
|
||||
)
|
||||
.process(5u8, &mut context);
|
||||
assert_eq!(output, 10);
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,6 @@ use std::pin::Pin;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
use crate::io::shared_thread_state::SharedThreadState;
|
||||
|
||||
/// Async/await scheduler.
|
||||
pub struct Scheduler<SM>
|
||||
where
|
||||
@ -26,30 +24,23 @@ where
|
||||
pub fn schedule_method(&self) -> &SM {
|
||||
&self.schedule_method
|
||||
}
|
||||
|
||||
pub fn take(self) -> SM {
|
||||
self.schedule_method
|
||||
}
|
||||
}
|
||||
|
||||
/// Can schedule a task from a future factory and a shared state.
|
||||
// Should be object safe in order to be able to have a dyn object in MapContext
|
||||
pub trait ScheduleMethod: 'static {
|
||||
#[cfg(not(feature = "no-thread-safe-futures"))]
|
||||
fn schedule(
|
||||
fn schedule<T>(
|
||||
&self,
|
||||
shared_thread_state: SharedThreadState,
|
||||
future_factory: Box<
|
||||
(dyn (FnOnce(SharedThreadState) -> Pin<Box<dyn Future<Output = ()> + Send>>) + Send),
|
||||
>,
|
||||
) -> Result<(), Error>;
|
||||
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: Future<Output = ()> + Send + 'static;
|
||||
|
||||
#[cfg(feature = "no-thread-safe-futures")]
|
||||
fn schedule(
|
||||
fn schedule<T>(
|
||||
&self,
|
||||
shared_thread_state: SharedThreadState,
|
||||
future_factory: Box<
|
||||
(dyn (FnOnce(SharedThreadState) -> Pin<Box<dyn Future<Output = ()>>>) + Send),
|
||||
>,
|
||||
) -> Result<(), Error>;
|
||||
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: Future<Output = ()> + 'static;
|
||||
}
|
||||
|
||||
@ -1,170 +0,0 @@
|
||||
//! Shared thread state.
|
||||
|
||||
use crate::coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel};
|
||||
use crate::error::Error;
|
||||
use crate::io::geometry_index::{GeometryIndex, IndexProcessor, IndexedGeometry, TileIndex};
|
||||
use crate::io::tile_request_state::TileRequestState;
|
||||
use crate::io::{
|
||||
LayerTessellateMessage, TessellateMessage, TileRequest, TileRequestID, TileTessellateMessage,
|
||||
};
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::tessellation::zero_tessellator::ZeroTessellator;
|
||||
|
||||
use geozero::GeozeroDatasource;
|
||||
use prost::Message;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
|
||||
/// Stores and provides access to the thread safe data shared between the schedulers.
|
||||
#[derive(Clone)]
|
||||
pub struct SharedThreadState {
|
||||
pub tile_request_state: Arc<Mutex<TileRequestState>>,
|
||||
pub message_sender: mpsc::Sender<TessellateMessage>,
|
||||
pub geometry_index: Arc<Mutex<GeometryIndex>>,
|
||||
}
|
||||
|
||||
impl SharedThreadState {
|
||||
fn get_tile_request(&self, request_id: TileRequestID) -> Option<TileRequest> {
|
||||
self.tile_request_state
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|tile_request_state| tile_request_state.get_tile_request(request_id).cloned())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn process_tile(&self, request_id: TileRequestID, data: Box<[u8]>) -> Result<(), Error> {
|
||||
if let Some(tile_request) = self.get_tile_request(request_id) {
|
||||
let coords = tile_request.coords;
|
||||
|
||||
tracing::info!("parsing tile {} with {}bytes", &coords, data.len());
|
||||
|
||||
let _span_ = tracing::span!(tracing::Level::TRACE, "parse_tile_bytes").entered();
|
||||
|
||||
let mut tile = geozero::mvt::Tile::decode(data.as_ref()).expect("failed to load tile");
|
||||
|
||||
let index = IndexProcessor::new();
|
||||
|
||||
for layer in &mut tile.layers {
|
||||
let cloned_layer = layer.clone();
|
||||
let layer_name: &str = &cloned_layer.name;
|
||||
if !tile_request.layers.contains(layer_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::info!("layer {} at {} ready", layer_name, &coords);
|
||||
|
||||
let mut tessellator = ZeroTessellator::default();
|
||||
if let Err(e) = layer.process(&mut tessellator) {
|
||||
self.message_sender.send(TessellateMessage::Layer(
|
||||
LayerTessellateMessage::UnavailableLayer {
|
||||
coords,
|
||||
layer_name: layer_name.to_owned(),
|
||||
},
|
||||
))?;
|
||||
|
||||
tracing::error!(
|
||||
"layer {} at {} tesselation failed {:?}",
|
||||
layer_name,
|
||||
&coords,
|
||||
e
|
||||
);
|
||||
} else {
|
||||
self.message_sender.send(TessellateMessage::Layer(
|
||||
LayerTessellateMessage::TessellatedLayer {
|
||||
coords,
|
||||
buffer: tessellator.buffer.into(),
|
||||
feature_indices: tessellator.feature_indices,
|
||||
layer_data: cloned_layer,
|
||||
},
|
||||
))?;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// layer.process(&mut index).unwrap();
|
||||
}
|
||||
|
||||
let available_layers: HashSet<_> = tile
|
||||
.layers
|
||||
.iter()
|
||||
.map(|layer| layer.name.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for missing_layer in tile_request.layers.difference(&available_layers) {
|
||||
self.message_sender.send(TessellateMessage::Layer(
|
||||
LayerTessellateMessage::UnavailableLayer {
|
||||
coords,
|
||||
layer_name: missing_layer.to_owned(),
|
||||
},
|
||||
))?;
|
||||
|
||||
tracing::info!(
|
||||
"requested layer {} at {} not found in tile",
|
||||
missing_layer,
|
||||
&coords
|
||||
);
|
||||
}
|
||||
|
||||
tracing::info!("tile tessellated at {} finished", &tile_request.coords);
|
||||
|
||||
self.message_sender
|
||||
.send(TessellateMessage::Tile(TileTessellateMessage {
|
||||
request_id,
|
||||
coords: tile_request.coords,
|
||||
}))?;
|
||||
|
||||
if let Ok(mut geometry_index) = self.geometry_index.lock() {
|
||||
geometry_index.index_tile(
|
||||
&coords,
|
||||
TileIndex::Linear {
|
||||
list: index.get_geometries(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tile_unavailable(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
request_id: TileRequestID,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(tile_request) = self.get_tile_request(request_id) {
|
||||
for to_load in &tile_request.layers {
|
||||
tracing::warn!("layer {} at {} unavailable", to_load, coords);
|
||||
self.message_sender.send(TessellateMessage::Layer(
|
||||
LayerTessellateMessage::UnavailableLayer {
|
||||
coords: tile_request.coords,
|
||||
layer_name: to_load.to_string(),
|
||||
},
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn query_point(
|
||||
&self,
|
||||
world_coords: &WorldCoords,
|
||||
z: ZoomLevel,
|
||||
zoom: Zoom,
|
||||
) -> Option<Vec<IndexedGeometry<f64>>> {
|
||||
if let Ok(geometry_index) = self.geometry_index.lock() {
|
||||
geometry_index
|
||||
.query_point(world_coords, z, zoom)
|
||||
.map(|geometries| {
|
||||
geometries
|
||||
.iter()
|
||||
.cloned()
|
||||
.cloned()
|
||||
.collect::<Vec<IndexedGeometry<f64>>>()
|
||||
})
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ pub type HTTPClientFactory<HC> = dyn Fn() -> HC;
|
||||
/// the future "no-thread-safe-futures". Tokio futures are thread-safe.
|
||||
#[cfg_attr(feature = "no-thread-safe-futures", async_trait(?Send))]
|
||||
#[cfg_attr(not(feature = "no-thread-safe-futures"), async_trait)]
|
||||
pub trait HTTPClient: Clone + Sync + Send + 'static {
|
||||
pub trait HttpClient: Clone + Sync + Send + 'static {
|
||||
async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error>;
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ pub trait HTTPClient: Clone + Sync + Send + 'static {
|
||||
#[derive(Clone)]
|
||||
pub struct HttpSourceClient<HC>
|
||||
where
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
inner_client: HC,
|
||||
}
|
||||
@ -35,7 +35,7 @@ where
|
||||
#[derive(Clone)]
|
||||
pub enum SourceClient<HC>
|
||||
where
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
Http(HttpSourceClient<HC>),
|
||||
Mbtiles {
|
||||
@ -45,7 +45,7 @@ where
|
||||
|
||||
impl<HC> SourceClient<HC>
|
||||
where
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
|
||||
match self {
|
||||
@ -57,7 +57,7 @@ where
|
||||
|
||||
impl<HC> HttpSourceClient<HC>
|
||||
where
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub fn new(http_client: HC) -> Self {
|
||||
Self {
|
||||
|
||||
156
maplibre/src/io/tile_pipelines.rs
Normal file
156
maplibre/src/io/tile_pipelines.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use crate::io::geometry_index::IndexProcessor;
|
||||
use crate::io::pipeline::{DataPipeline, PipelineContext, PipelineEnd, Processable};
|
||||
use crate::io::{TileRequest, TileRequestID};
|
||||
use crate::tessellation::zero_tessellator::ZeroTessellator;
|
||||
use crate::tessellation::IndexDataType;
|
||||
use geozero::GeozeroDatasource;
|
||||
use prost::Message;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct ParseTile;
|
||||
|
||||
impl Processable for ParseTile {
|
||||
type Input = (TileRequest, TileRequestID, Box<[u8]>);
|
||||
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile);
|
||||
|
||||
// TODO (perf): Maybe force inline
|
||||
fn process(
|
||||
&self,
|
||||
(tile_request, request_id, data): Self::Input,
|
||||
_context: &mut PipelineContext,
|
||||
) -> Self::Output {
|
||||
let tile = geozero::mvt::Tile::decode(data.as_ref()).expect("failed to load tile");
|
||||
(tile_request, request_id, tile)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IndexLayer;
|
||||
|
||||
impl Processable for IndexLayer {
|
||||
type Input = (TileRequest, TileRequestID, geozero::mvt::Tile);
|
||||
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile);
|
||||
|
||||
// TODO (perf): Maybe force inline
|
||||
fn process(
|
||||
&self,
|
||||
(tile_request, request_id, tile): Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Self::Output {
|
||||
let index = IndexProcessor::new();
|
||||
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_indexing_finished(&tile_request.coords, index.get_geometries());
|
||||
(tile_request, request_id, tile)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TessellateLayer;
|
||||
|
||||
impl Processable for TessellateLayer {
|
||||
type Input = (TileRequest, TileRequestID, geozero::mvt::Tile);
|
||||
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile);
|
||||
|
||||
// TODO (perf): Maybe force inline
|
||||
fn process(
|
||||
&self,
|
||||
(tile_request, request_id, mut tile): Self::Input,
|
||||
context: &mut PipelineContext,
|
||||
) -> Self::Output {
|
||||
let coords = &tile_request.coords;
|
||||
|
||||
for layer in &mut tile.layers {
|
||||
let cloned_layer = layer.clone();
|
||||
let layer_name: &str = &cloned_layer.name;
|
||||
if !tile_request.layers.contains(layer_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::info!("layer {} at {} ready", layer_name, coords);
|
||||
|
||||
let mut tessellator = ZeroTessellator::<IndexDataType>::default();
|
||||
if let Err(e) = layer.process(&mut tessellator) {
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_unavailable(coords, layer_name);
|
||||
|
||||
tracing::error!(
|
||||
"layer {} at {} tesselation failed {:?}",
|
||||
layer_name,
|
||||
&coords,
|
||||
e
|
||||
);
|
||||
} else {
|
||||
context.processor_mut().layer_tesselation_finished(
|
||||
coords,
|
||||
tessellator.buffer.into(),
|
||||
tessellator.feature_indices,
|
||||
cloned_layer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let available_layers: HashSet<_> = tile
|
||||
.layers
|
||||
.iter()
|
||||
.map(|layer| layer.name.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for missing_layer in tile_request.layers.difference(&available_layers) {
|
||||
context
|
||||
.processor_mut()
|
||||
.layer_unavailable(coords, missing_layer);
|
||||
|
||||
tracing::info!(
|
||||
"requested layer {} at {} not found in tile",
|
||||
missing_layer,
|
||||
&coords
|
||||
);
|
||||
}
|
||||
|
||||
tracing::info!("tile tessellated at {} finished", &tile_request.coords);
|
||||
|
||||
context
|
||||
.processor_mut()
|
||||
.tile_finished(request_id, &tile_request.coords);
|
||||
|
||||
(tile_request, request_id, tile)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_vector_tile_pipeline() -> impl Processable<Input = <ParseTile as Processable>::Input> {
|
||||
DataPipeline::new(
|
||||
ParseTile,
|
||||
DataPipeline::new(TessellateLayer, PipelineEnd::default()),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::build_vector_tile_pipeline;
|
||||
use crate::coords::ZoomLevel;
|
||||
use crate::io::pipeline::{PipelineContext, PipelineProcessor, Processable};
|
||||
use crate::io::TileRequest;
|
||||
pub struct DummyPipelineProcessor;
|
||||
|
||||
impl PipelineProcessor for DummyPipelineProcessor {}
|
||||
|
||||
#[test] // TODO: Add proper tile byte array
|
||||
#[ignore]
|
||||
fn test() {
|
||||
let mut context = PipelineContext::new(DummyPipelineProcessor);
|
||||
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
let output = pipeline.process(
|
||||
(
|
||||
TileRequest {
|
||||
coords: (0, 0, ZoomLevel::default()).into(),
|
||||
layers: Default::default(),
|
||||
},
|
||||
0,
|
||||
Box::new([0]),
|
||||
),
|
||||
&mut context,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,49 @@
|
||||
//! Tile cache.
|
||||
|
||||
use crate::coords::{Quadkey, WorldTileCoords};
|
||||
|
||||
use crate::io::LayerTessellateMessage;
|
||||
|
||||
use crate::render::ShaderVertex;
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
|
||||
use geozero::mvt::tile;
|
||||
use std::collections::{btree_map, BTreeMap, HashSet};
|
||||
|
||||
/// Stores the multiple [crate::io::LayerTessellateMessage] of a cached tile.
|
||||
pub struct CachedTile {
|
||||
layers: Vec<LayerTessellateMessage>,
|
||||
/// A layer which is stored for future use.
|
||||
pub enum StoredLayer {
|
||||
UnavailableLayer {
|
||||
coords: WorldTileCoords,
|
||||
layer_name: String,
|
||||
},
|
||||
TessellatedLayer {
|
||||
coords: WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
/// Holds for each feature the count of indices.
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: tile::Layer,
|
||||
},
|
||||
}
|
||||
|
||||
impl CachedTile {
|
||||
pub fn new(first_layer: LayerTessellateMessage) -> Self {
|
||||
impl StoredLayer {
|
||||
pub fn get_coords(&self) -> WorldTileCoords {
|
||||
match self {
|
||||
StoredLayer::UnavailableLayer { coords, .. } => *coords,
|
||||
StoredLayer::TessellatedLayer { coords, .. } => *coords,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer_name(&self) -> &str {
|
||||
match self {
|
||||
StoredLayer::UnavailableLayer { layer_name, .. } => layer_name.as_str(),
|
||||
StoredLayer::TessellatedLayer { layer_data, .. } => &layer_data.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores multiple [StoredLayers](StoredLayer).
|
||||
pub struct StoredTile {
|
||||
layers: Vec<StoredLayer>,
|
||||
}
|
||||
|
||||
impl StoredTile {
|
||||
pub fn new(first_layer: StoredLayer) -> Self {
|
||||
Self {
|
||||
layers: vec![first_layer],
|
||||
}
|
||||
@ -21,34 +52,34 @@ impl CachedTile {
|
||||
|
||||
/// Stores and provides access to a quad tree of cached tiles with world tile coords.
|
||||
#[derive(Default)]
|
||||
pub struct TileCache {
|
||||
cache: BTreeMap<Quadkey, CachedTile>,
|
||||
pub struct TileRepository {
|
||||
tree: BTreeMap<Quadkey, StoredTile>,
|
||||
}
|
||||
|
||||
impl TileCache {
|
||||
impl TileRepository {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cache: BTreeMap::new(),
|
||||
tree: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a tessellated layer into the quad tree at its world tile coords.
|
||||
/// If the space is vacant, the tessellated layer is inserted into a new
|
||||
/// [crate::io::tile_cache::CachedTile].
|
||||
/// [crate::io::tile_repository::CachedTile].
|
||||
/// If the space is occupied, the tessellated layer is added to the current
|
||||
/// [crate::io::tile_cache::CachedTile].
|
||||
pub fn put_tessellated_layer(&mut self, message: LayerTessellateMessage) {
|
||||
if let Some(entry) = message
|
||||
/// [crate::io::tile_repository::CachedTile].
|
||||
pub fn put_tessellated_layer(&mut self, layer: StoredLayer) {
|
||||
if let Some(entry) = layer
|
||||
.get_coords()
|
||||
.build_quad_key()
|
||||
.map(|key| self.cache.entry(key))
|
||||
.map(|key| self.tree.entry(key))
|
||||
{
|
||||
match entry {
|
||||
btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(CachedTile::new(message));
|
||||
entry.insert(StoredTile::new(layer));
|
||||
}
|
||||
btree_map::Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().layers.push(message);
|
||||
entry.get_mut().layers.push(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -59,10 +90,10 @@ impl TileCache {
|
||||
pub fn iter_tessellated_layers_at(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
) -> Option<impl Iterator<Item = &LayerTessellateMessage> + '_> {
|
||||
) -> Option<impl Iterator<Item = &StoredLayer> + '_> {
|
||||
coords
|
||||
.build_quad_key()
|
||||
.and_then(|key| self.cache.get(&key))
|
||||
.and_then(|key| self.tree.get(&key))
|
||||
.map(|results| results.layers.iter())
|
||||
}
|
||||
|
||||
@ -73,7 +104,7 @@ impl TileCache {
|
||||
coords: &WorldTileCoords,
|
||||
layers: &mut HashSet<String>,
|
||||
) {
|
||||
if let Some(cached_tile) = coords.build_quad_key().and_then(|key| self.cache.get(&key)) {
|
||||
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()
|
||||
@ -86,7 +117,7 @@ impl TileCache {
|
||||
|
||||
/// 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.cache.get(&key)) {
|
||||
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()
|
||||
@ -17,12 +17,12 @@
|
||||
//! ```
|
||||
|
||||
use crate::io::scheduler::{ScheduleMethod, Scheduler};
|
||||
use crate::io::source_client::HTTPClient;
|
||||
use crate::map_schedule::MapSchedule;
|
||||
use crate::io::source_client::HttpClient;
|
||||
use crate::map_schedule::{InteractiveMapSchedule, SimpleMapSchedule};
|
||||
use crate::render::settings::{RendererSettings, WgpuSettings};
|
||||
use crate::render::{RenderState, Renderer};
|
||||
use crate::style::Style;
|
||||
use crate::window::{MapWindow, MapWindowConfig, Runnable, WindowSize};
|
||||
use crate::window::{EventLoop, HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};
|
||||
|
||||
pub mod context;
|
||||
pub mod coords;
|
||||
@ -37,43 +37,51 @@ pub mod style;
|
||||
pub mod window;
|
||||
// Exposed because of doc-strings
|
||||
pub mod schedule;
|
||||
// Exposed because of SharedThreadState
|
||||
pub mod stages;
|
||||
|
||||
// Used for benchmarking
|
||||
pub mod benchmarking;
|
||||
|
||||
// Internal modules
|
||||
pub(crate) mod stages;
|
||||
pub(crate) mod tessellation;
|
||||
pub(crate) mod util;
|
||||
pub mod util;
|
||||
|
||||
/// Map's configuration and execution.
|
||||
pub struct Map<W, SM, HC>
|
||||
/// The [`Map`] defines the public interface of the map renderer.
|
||||
// DO NOT IMPLEMENT INTERNALS ON THIS STRUCT.
|
||||
pub struct Map<MWC, SM, HC>
|
||||
where
|
||||
W: MapWindow,
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
map_state: MapSchedule<W::MapWindowConfig, SM, HC>,
|
||||
window: W,
|
||||
map_schedule: InteractiveMapSchedule<MWC, SM, HC>,
|
||||
window: MWC::MapWindow,
|
||||
}
|
||||
|
||||
impl<W, SM, HC> Map<W, SM, HC>
|
||||
impl<MWC, SM, HC> Map<MWC, SM, HC>
|
||||
where
|
||||
W: MapWindow + Runnable<W::MapWindowConfig, SM, HC>,
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
/// Starts the [`crate::map_state::MapState`] Runnable with the configured event loop.
|
||||
pub fn run(self) {
|
||||
/// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
|
||||
pub fn run(self)
|
||||
where
|
||||
MWC::MapWindow: EventLoop<MWC, SM, HC>,
|
||||
{
|
||||
self.run_with_optionally_max_frames(None);
|
||||
}
|
||||
|
||||
/// Starts the [`crate::map_state::MapState`] Runnable with the configured event loop.
|
||||
/// 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) {
|
||||
pub fn run_with_max_frames(self, max_frames: u64)
|
||||
where
|
||||
MWC::MapWindow: EventLoop<MWC, SM, HC>,
|
||||
{
|
||||
self.run_with_optionally_max_frames(Some(max_frames));
|
||||
}
|
||||
|
||||
@ -82,20 +90,49 @@ where
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `max_frames` - Optional maximum number of frames per second.
|
||||
pub fn run_with_optionally_max_frames(self, max_frames: Option<u64>) {
|
||||
self.window.run(self.map_state, max_frames);
|
||||
pub fn run_with_optionally_max_frames(self, max_frames: Option<u64>)
|
||||
where
|
||||
MWC::MapWindow: EventLoop<MWC, SM, HC>,
|
||||
{
|
||||
self.window.run(self.map_schedule, max_frames);
|
||||
}
|
||||
|
||||
pub fn map_schedule(&self) -> &InteractiveMapSchedule<MWC, SM, HC> {
|
||||
&self.map_schedule
|
||||
}
|
||||
|
||||
pub fn map_schedule_mut(&mut self) -> &mut InteractiveMapSchedule<MWC, SM, HC> {
|
||||
&mut self.map_schedule
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HeadlessMap<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HttpClient,
|
||||
{
|
||||
map_schedule: SimpleMapSchedule<MWC, SM, HC>,
|
||||
window: MWC::MapWindow,
|
||||
}
|
||||
|
||||
impl<MWC, SM, HC> HeadlessMap<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub fn map_schedule_mut(&mut self) -> &mut SimpleMapSchedule<MWC, SM, HC> {
|
||||
&mut self.map_schedule
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the map configuration before the map's state has been fully initialized.
|
||||
///
|
||||
/// FIXME: We could maybe remove this class, and store the render_state in an Optional in [`crate::map_state::MapState`].
|
||||
/// FIXME: I think we can find a workaround so that this class doesn't exist.
|
||||
pub struct UninitializedMap<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
scheduler: Scheduler<SM>,
|
||||
http_client: HC,
|
||||
@ -110,12 +147,16 @@ impl<MWC, SM, HC> UninitializedMap<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
/// Initializes the whole rendering pipeline for the given configuration.
|
||||
/// Returns the initialized map, ready to be run.
|
||||
pub async fn initialize(self) -> Map<MWC::MapWindow, SM, HC> {
|
||||
let window = MWC::MapWindow::create(&self.map_window_config);
|
||||
pub async fn initialize(self) -> Map<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
|
||||
{
|
||||
let window = self.map_window_config.create();
|
||||
let window_size = window.size();
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
@ -129,7 +170,7 @@ where
|
||||
.await
|
||||
.ok();
|
||||
Map {
|
||||
map_state: MapSchedule::new(
|
||||
map_schedule: InteractiveMapSchedule::new(
|
||||
self.map_window_config,
|
||||
window_size,
|
||||
renderer,
|
||||
@ -142,6 +183,30 @@ where
|
||||
window,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initialize_headless(self) -> HeadlessMap<MWC, SM, HC> {
|
||||
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");
|
||||
HeadlessMap {
|
||||
map_schedule: SimpleMapSchedule::new(
|
||||
self.map_window_config,
|
||||
window_size,
|
||||
renderer,
|
||||
self.scheduler,
|
||||
self.http_client,
|
||||
self.style,
|
||||
),
|
||||
window,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MapBuilder<MWC, SM, HC>
|
||||
@ -162,7 +227,7 @@ impl<MWC, SM, HC> MapBuilder<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
||||
@ -1,35 +1,27 @@
|
||||
//! Stores the state of the map such as `[crate::coords::Zoom]`, `[crate::camera::Camera]`, `[crate::style::Style]`, `[crate::io::tile_cache::TileCache]` and more.
|
||||
|
||||
use crate::context::{MapContext, ViewState};
|
||||
use crate::error::Error;
|
||||
use crate::io::geometry_index::GeometryIndex;
|
||||
use crate::io::scheduler::Scheduler;
|
||||
use crate::io::shared_thread_state::SharedThreadState;
|
||||
use crate::io::source_client::{HTTPClient, HttpSourceClient, SourceClient};
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::source_client::{HttpClient, HttpSourceClient, SourceClient};
|
||||
use crate::io::tile_repository::TileRepository;
|
||||
use crate::io::tile_request_state::TileRequestState;
|
||||
use crate::io::TessellateMessage;
|
||||
use crate::render::register_render_stages;
|
||||
use crate::schedule::{Schedule, Stage};
|
||||
use crate::stages::register_stages;
|
||||
use crate::style::Style;
|
||||
use crate::{
|
||||
MapWindow, MapWindowConfig, Renderer, RendererSettings, ScheduleMethod, WgpuSettings,
|
||||
WindowSize,
|
||||
HeadedMapWindow, MapWindow, MapWindowConfig, Renderer, RendererSettings, ScheduleMethod,
|
||||
WgpuSettings, WindowSize,
|
||||
};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
|
||||
pub struct PrematureMapContext {
|
||||
pub view_state: ViewState,
|
||||
pub style: Style,
|
||||
view_state: ViewState,
|
||||
style: Style,
|
||||
|
||||
pub tile_cache: TileCache,
|
||||
pub scheduler: Box<dyn ScheduleMethod>,
|
||||
|
||||
pub message_receiver: mpsc::Receiver<TessellateMessage>,
|
||||
pub shared_thread_state: SharedThreadState,
|
||||
tile_repository: TileRepository,
|
||||
|
||||
wgpu_settings: WgpuSettings,
|
||||
renderer_settings: RendererSettings,
|
||||
@ -38,53 +30,43 @@ pub struct PrematureMapContext {
|
||||
pub enum EventuallyMapContext {
|
||||
Full(MapContext),
|
||||
Premature(PrematureMapContext),
|
||||
Empty,
|
||||
_Uninitialized,
|
||||
}
|
||||
|
||||
impl EventuallyMapContext {
|
||||
pub fn make_full(&mut self, renderer: Renderer) {
|
||||
let context = mem::replace(self, EventuallyMapContext::Empty);
|
||||
let context = mem::replace(self, EventuallyMapContext::_Uninitialized);
|
||||
|
||||
match context {
|
||||
EventuallyMapContext::Full(_) => {}
|
||||
EventuallyMapContext::Premature(PrematureMapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_cache,
|
||||
scheduler,
|
||||
message_receiver,
|
||||
shared_thread_state,
|
||||
wgpu_settings,
|
||||
renderer_settings,
|
||||
tile_repository,
|
||||
..
|
||||
}) => {
|
||||
mem::replace(
|
||||
self,
|
||||
EventuallyMapContext::Full(MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_cache,
|
||||
renderer,
|
||||
scheduler,
|
||||
message_receiver,
|
||||
shared_thread_state,
|
||||
}),
|
||||
);
|
||||
*self = EventuallyMapContext::Full(MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_repository,
|
||||
renderer,
|
||||
});
|
||||
}
|
||||
EventuallyMapContext::Empty => {}
|
||||
EventuallyMapContext::_Uninitialized => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
|
||||
pub struct MapSchedule<MWC, SM, HC>
|
||||
pub struct InteractiveMapSchedule<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
map_window_config: MWC,
|
||||
|
||||
map_context: EventuallyMapContext,
|
||||
pub map_context: EventuallyMapContext,
|
||||
|
||||
schedule: Schedule,
|
||||
|
||||
@ -94,11 +76,11 @@ where
|
||||
suspended: bool,
|
||||
}
|
||||
|
||||
impl<MWC, SM, HC> MapSchedule<MWC, SM, HC>
|
||||
impl<MWC, SM, HC> InteractiveMapSchedule<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub fn new(
|
||||
map_window_config: MWC,
|
||||
@ -111,42 +93,29 @@ where
|
||||
renderer_settings: RendererSettings,
|
||||
) -> Self {
|
||||
let view_state = ViewState::new(&window_size);
|
||||
let tile_cache = TileCache::new();
|
||||
|
||||
let tile_repository = TileRepository::new();
|
||||
let mut schedule = Schedule::default();
|
||||
let client: SourceClient<HC> = SourceClient::Http(HttpSourceClient::new(http_client));
|
||||
register_stages(&mut schedule, client);
|
||||
register_render_stages(&mut schedule);
|
||||
|
||||
let (message_sender, message_receiver) = mpsc::channel();
|
||||
let http_source_client: HttpSourceClient<HC> = HttpSourceClient::new(http_client);
|
||||
register_stages(&mut schedule, http_source_client, Box::new(scheduler));
|
||||
|
||||
register_render_stages(&mut schedule, false).unwrap();
|
||||
|
||||
let scheduler = Box::new(scheduler.take());
|
||||
let shared_thread_state = SharedThreadState {
|
||||
tile_request_state: Arc::new(Mutex::new(TileRequestState::new())),
|
||||
message_sender,
|
||||
geometry_index: Arc::new(Mutex::new(GeometryIndex::new())),
|
||||
};
|
||||
Self {
|
||||
map_window_config,
|
||||
map_context: match renderer {
|
||||
None => EventuallyMapContext::Premature(PrematureMapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_cache,
|
||||
scheduler,
|
||||
shared_thread_state,
|
||||
tile_repository,
|
||||
wgpu_settings,
|
||||
message_receiver,
|
||||
renderer_settings,
|
||||
}),
|
||||
Some(renderer) => EventuallyMapContext::Full(MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_cache,
|
||||
tile_repository,
|
||||
renderer,
|
||||
scheduler,
|
||||
shared_thread_state,
|
||||
message_receiver,
|
||||
}),
|
||||
},
|
||||
schedule,
|
||||
@ -183,13 +152,15 @@ where
|
||||
self.suspended = true;
|
||||
}
|
||||
|
||||
pub fn resume<MW>(&mut self, window: &MW)
|
||||
pub fn resume(&mut self, window: &MWC::MapWindow)
|
||||
where
|
||||
MW: MapWindow,
|
||||
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
|
||||
{
|
||||
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
|
||||
let mut renderer = &mut map_context.renderer;
|
||||
renderer.surface.recreate(window, &renderer.instance);
|
||||
renderer
|
||||
.state
|
||||
.recreate_surface::<MWC::MapWindow>(window, &renderer.instance);
|
||||
self.suspended = false;
|
||||
}
|
||||
}
|
||||
@ -201,28 +172,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn late_init(&mut self) -> bool {
|
||||
pub async fn late_init(&mut self) -> bool
|
||||
where
|
||||
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
|
||||
{
|
||||
match &self.map_context {
|
||||
EventuallyMapContext::Full(_) => false,
|
||||
EventuallyMapContext::Premature(PrematureMapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_cache,
|
||||
scheduler,
|
||||
message_receiver,
|
||||
shared_thread_state,
|
||||
wgpu_settings,
|
||||
renderer_settings,
|
||||
..
|
||||
}) => {
|
||||
let window = MWC::MapWindow::create(&self.map_window_config);
|
||||
let window = self.map_window_config.create();
|
||||
let renderer =
|
||||
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
&self.map_context.make_full(renderer);
|
||||
self.map_context.make_full(renderer);
|
||||
true
|
||||
}
|
||||
EventuallyMapContext::Empty => false,
|
||||
EventuallyMapContext::_Uninitialized => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,3 +203,71 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
|
||||
pub struct SimpleMapSchedule<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HttpClient,
|
||||
{
|
||||
map_window_config: MWC,
|
||||
|
||||
pub map_context: MapContext,
|
||||
|
||||
schedule: Schedule,
|
||||
scheduler: Scheduler<SM>,
|
||||
http_client: HC,
|
||||
}
|
||||
|
||||
impl<MWC, SM, HC> SimpleMapSchedule<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub fn new(
|
||||
map_window_config: MWC,
|
||||
window_size: WindowSize,
|
||||
renderer: Renderer,
|
||||
scheduler: Scheduler<SM>,
|
||||
http_client: HC,
|
||||
style: Style,
|
||||
) -> Self {
|
||||
let view_state = ViewState::new(&window_size);
|
||||
let tile_repository = TileRepository::new();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
register_render_stages(&mut schedule, true).unwrap();
|
||||
|
||||
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) -> &Scheduler<SM> {
|
||||
&self.scheduler
|
||||
}
|
||||
pub fn http_client(&self) -> &HC {
|
||||
&self.http_client
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::error::Error;
|
||||
use crate::HTTPClient;
|
||||
use crate::HttpClient;
|
||||
use async_trait::async_trait;
|
||||
use reqwest::{Client, StatusCode};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
@ -41,7 +41,7 @@ impl ReqwestHttpClient {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HTTPClient for ReqwestHttpClient {
|
||||
impl HttpClient for ReqwestHttpClient {
|
||||
async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error> {
|
||||
let response = self.client.get(url).send().await?;
|
||||
match response.error_for_status() {
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
use crate::error::Error;
|
||||
use crate::io::shared_thread_state::SharedThreadState;
|
||||
use crate::ScheduleMethod;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
@ -14,16 +13,11 @@ impl TokioScheduleMethod {
|
||||
}
|
||||
|
||||
impl ScheduleMethod for TokioScheduleMethod {
|
||||
fn schedule(
|
||||
&self,
|
||||
shared_thread_state: SharedThreadState,
|
||||
future_factory: Box<
|
||||
(dyn (FnOnce(SharedThreadState) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>)
|
||||
+ Send
|
||||
+ 'static),
|
||||
>,
|
||||
) -> Result<(), Error> {
|
||||
tokio::task::spawn((future_factory)(shared_thread_state));
|
||||
fn schedule<T>(&self, future_factory: impl FnOnce() -> T + Send + 'static) -> Result<(), Error>
|
||||
where
|
||||
T: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
tokio::task::spawn((future_factory)());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
68
maplibre/src/render/copy_surface_to_buffer_node.rs
Normal file
68
maplibre/src/render/copy_surface_to_buffer_node.rs
Normal file
@ -0,0 +1,68 @@
|
||||
//! 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`].
|
||||
|
||||
use crate::render::graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo};
|
||||
use crate::render::render_commands::{DrawMasks, DrawTiles};
|
||||
use crate::render::render_phase::{PhaseItem, RenderCommand};
|
||||
use crate::render::resource::{Head, TrackedRenderPass};
|
||||
use crate::render::util::FloatOrd;
|
||||
use crate::render::Eventually::Initialized;
|
||||
use crate::render::RenderState;
|
||||
use std::ops::{Deref, Range};
|
||||
|
||||
#[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> {
|
||||
match state.surface.head() {
|
||||
Head::Headed(_) => {}
|
||||
Head::Headless(buffered_texture) => {
|
||||
let size = state.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(),
|
||||
),
|
||||
rows_per_image: None,
|
||||
},
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: size.width() as u32,
|
||||
height: size.height() as u32,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -27,7 +27,8 @@ use super::EdgeExistence;
|
||||
///
|
||||
/// ## Example
|
||||
/// Here is a simple render graph example with two nodes connected by a node edge.
|
||||
/// ```ignore
|
||||
/// ```
|
||||
/// #
|
||||
/// # use maplibre::render::graph::{Node, NodeRunError, RenderContext, RenderGraph, RenderGraphContext};
|
||||
/// # use maplibre::render::{RenderState};
|
||||
/// # struct MyNode;
|
||||
@ -574,10 +575,8 @@ mod tests {
|
||||
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
|
||||
SlotInfo,
|
||||
};
|
||||
use crate::render::{
|
||||
graph::{RenderContext, SlotType},
|
||||
RenderState,
|
||||
};
|
||||
use crate::render::graph::{RenderContext, SlotType};
|
||||
use crate::RenderState;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -610,9 +609,9 @@ mod tests {
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_: &mut RenderGraphContext,
|
||||
_: &mut RenderContext,
|
||||
_: &RenderState,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
) -> Result<(), NodeRunError> {
|
||||
Ok(())
|
||||
}
|
||||
@ -683,9 +682,9 @@ mod tests {
|
||||
impl Node for MyNode {
|
||||
fn run(
|
||||
&self,
|
||||
_: &mut RenderGraphContext,
|
||||
_: &mut RenderContext,
|
||||
_: &RenderState,
|
||||
graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext,
|
||||
state: &RenderState,
|
||||
) -> Result<(), NodeRunError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -7,12 +7,21 @@ use crate::render::graph::{Node, NodeRunError, RenderContext, RenderGraphContext
|
||||
use crate::render::render_commands::{DrawMasks, DrawTiles};
|
||||
use crate::render::render_phase::{PhaseItem, RenderCommand};
|
||||
use crate::render::resource::TrackedRenderPass;
|
||||
use crate::render::stages::draw_graph;
|
||||
use crate::render::util::FloatOrd;
|
||||
use crate::render::Eventually::Initialized;
|
||||
use crate::render::RenderState;
|
||||
use crate::render::{draw_graph, main_graph, RenderState};
|
||||
use std::ops::{Deref, Range};
|
||||
|
||||
pub mod graph {
|
||||
// Labels for input nodes
|
||||
pub mod input {}
|
||||
// Labels for non-input nodes
|
||||
pub mod node {
|
||||
pub const MAIN_PASS_DEPENDENCIES: &str = "main_pass_dependencies";
|
||||
pub const MAIN_PASS_DRIVER: &str = "main_pass_driver";
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MainPassNode {}
|
||||
|
||||
impl MainPassNode {
|
||||
|
||||
@ -27,18 +27,23 @@ use crate::render::shaders::{ShaderFeatureStyle, ShaderLayerMetadata};
|
||||
use crate::render::tile_view_pattern::{TileInView, TileShape, TileViewPattern};
|
||||
use crate::render::util::Eventually;
|
||||
use crate::tessellation::IndexDataType;
|
||||
use crate::MapWindow;
|
||||
use crate::{HeadedMapWindow, MapWindow, MapWindowConfig};
|
||||
use log::info;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
// Exposed because it should be addable conditionally
|
||||
pub mod copy_surface_to_buffer_node;
|
||||
pub mod graph;
|
||||
pub mod resource;
|
||||
pub mod stages;
|
||||
|
||||
// Rendering internals
|
||||
mod graph;
|
||||
mod graph_runner;
|
||||
mod main_pass;
|
||||
mod render_commands;
|
||||
mod render_phase;
|
||||
mod resource;
|
||||
mod shaders;
|
||||
mod stages;
|
||||
mod tile_pipeline;
|
||||
mod tile_view_pattern;
|
||||
mod util;
|
||||
@ -52,7 +57,6 @@ pub use stages::register_render_stages;
|
||||
|
||||
pub const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderState {
|
||||
render_target: Eventually<TextureView>,
|
||||
|
||||
@ -76,13 +80,40 @@ pub struct RenderState {
|
||||
depth_texture: Eventually<Texture>,
|
||||
multisampling_texture: Eventually<Option<Texture>>,
|
||||
|
||||
surface: Surface,
|
||||
|
||||
mask_phase: RenderPhase<TileInView>,
|
||||
tile_phase: RenderPhase<(IndexEntry, TileShape)>,
|
||||
}
|
||||
|
||||
impl RenderState {
|
||||
pub fn new(surface: Surface) -> Self {
|
||||
Self {
|
||||
render_target: Default::default(),
|
||||
buffer_pool: Default::default(),
|
||||
tile_view_pattern: Default::default(),
|
||||
tile_pipeline: Default::default(),
|
||||
mask_pipeline: Default::default(),
|
||||
globals_bind_group: Default::default(),
|
||||
depth_texture: Default::default(),
|
||||
multisampling_texture: Default::default(),
|
||||
surface,
|
||||
mask_phase: Default::default(),
|
||||
tile_phase: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recreate_surface<MW>(&mut self, window: &MW, instance: &wgpu::Instance)
|
||||
where
|
||||
MW: MapWindow + HeadedMapWindow,
|
||||
{
|
||||
self.surface.recreate::<MW>(window, instance);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Renderer {
|
||||
pub instance: wgpu::Instance,
|
||||
pub device: wgpu::Device,
|
||||
pub device: Arc<wgpu::Device>, // TODO: Arc is needed for headless rendering. Is there a simpler solution?
|
||||
pub queue: wgpu::Queue,
|
||||
pub adapter_info: wgpu::AdapterInfo,
|
||||
|
||||
@ -90,7 +121,6 @@ pub struct Renderer {
|
||||
pub settings: RendererSettings,
|
||||
|
||||
pub state: RenderState,
|
||||
pub surface: Surface,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
@ -102,22 +132,15 @@ impl Renderer {
|
||||
settings: RendererSettings,
|
||||
) -> Result<Self, wgpu::RequestDeviceError>
|
||||
where
|
||||
MW: MapWindow,
|
||||
MW: MapWindow + HeadedMapWindow,
|
||||
{
|
||||
let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all()));
|
||||
|
||||
let maybe_surface = match &settings.surface_type {
|
||||
SurfaceType::Headless => None,
|
||||
SurfaceType::Headed => Some(Surface::from_window(&instance, window, &settings)),
|
||||
};
|
||||
let surface = Surface::from_window(&instance, window, &settings);
|
||||
|
||||
let compatible_surface = if let Some(surface) = &maybe_surface {
|
||||
match &surface.head() {
|
||||
Head::Headed(window_head) => Some(window_head.surface()),
|
||||
Head::Headless(_) => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
let compatible_surface = match &surface.head() {
|
||||
Head::Headed(window_head) => Some(window_head.surface()),
|
||||
Head::Headless(_) => None,
|
||||
};
|
||||
|
||||
let (device, queue, adapter_info) = Self::request_device(
|
||||
@ -131,11 +154,6 @@ impl Renderer {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let surface = maybe_surface.unwrap_or_else(|| match &settings.surface_type {
|
||||
SurfaceType::Headless => Surface::from_image(&device, window, &settings),
|
||||
SurfaceType::Headed => Surface::from_window(&instance, window, &settings),
|
||||
});
|
||||
|
||||
match surface.head() {
|
||||
Head::Headed(window) => window.configure(&device),
|
||||
Head::Headless(_) => {}
|
||||
@ -143,18 +161,51 @@ impl Renderer {
|
||||
|
||||
Ok(Self {
|
||||
instance,
|
||||
device,
|
||||
device: Arc::new(device),
|
||||
queue,
|
||||
adapter_info,
|
||||
wgpu_settings,
|
||||
settings,
|
||||
state: Default::default(),
|
||||
surface,
|
||||
state: RenderState::new(surface),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn initialize_headless<MW>(
|
||||
window: &MW,
|
||||
wgpu_settings: WgpuSettings,
|
||||
settings: RendererSettings,
|
||||
) -> Result<Self, wgpu::RequestDeviceError>
|
||||
where
|
||||
MW: MapWindow,
|
||||
{
|
||||
let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all()));
|
||||
|
||||
let (device, queue, adapter_info) = Self::request_device(
|
||||
&instance,
|
||||
&wgpu_settings,
|
||||
&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu_settings.power_preference,
|
||||
force_fallback_adapter: false,
|
||||
compatible_surface: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let surface = Surface::from_image(&device, window, &settings);
|
||||
|
||||
Ok(Self {
|
||||
instance,
|
||||
device: Arc::new(device),
|
||||
queue,
|
||||
adapter_info,
|
||||
wgpu_settings,
|
||||
settings,
|
||||
state: RenderState::new(surface),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
self.surface.resize(width, height)
|
||||
self.state.surface.resize(width, height)
|
||||
}
|
||||
|
||||
/// Requests a device
|
||||
@ -323,7 +374,7 @@ impl Renderer {
|
||||
&self.state
|
||||
}
|
||||
pub fn surface(&self) -> &Surface {
|
||||
&self.surface
|
||||
&self.state.surface
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,10 +382,34 @@ impl Renderer {
|
||||
mod tests {
|
||||
use crate::render::graph::RenderGraph;
|
||||
use crate::render::graph_runner::RenderGraphRunner;
|
||||
use crate::render::RenderState;
|
||||
use crate::render::resource::Surface;
|
||||
use crate::{MapWindow, MapWindowConfig, RenderState, Renderer, RendererSettings, WindowSize};
|
||||
|
||||
pub struct HeadlessMapWindowConfig {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[tokio::test]
|
||||
#[ignore] // FIXME: We do not have a GPU in CI
|
||||
async fn test_render() {
|
||||
let graph = RenderGraph::default();
|
||||
|
||||
@ -362,8 +437,41 @@ mod tests {
|
||||
.ok()
|
||||
.unwrap();
|
||||
|
||||
let render_state = RenderState::default();
|
||||
let render_state = RenderState::new(Surface::from_image(
|
||||
&device,
|
||||
&HeadlessMapWindow {
|
||||
size: WindowSize::new(100, 100).unwrap(),
|
||||
},
|
||||
&RendererSettings::default(),
|
||||
));
|
||||
|
||||
RenderGraphRunner::run(&graph, &device, &queue, &render_state);
|
||||
RenderGraphRunner::run(&graph, &device, &queue, &render_state).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Contributors to the RenderGraph should use the following label conventions:
|
||||
// 1. Graph modules should have a NAME, input module, and node module (where relevant)
|
||||
// 2. The "main_graph" graph is the root.
|
||||
// 3. "sub graph" modules should be nested beneath their parent graph module
|
||||
pub mod main_graph {
|
||||
// Labels for input nodes
|
||||
pub mod input {}
|
||||
// Labels for non-input nodes
|
||||
pub mod node {
|
||||
pub const MAIN_PASS_DEPENDENCIES: &str = "main_pass_dependencies";
|
||||
pub const MAIN_PASS_DRIVER: &str = "main_pass_driver";
|
||||
}
|
||||
}
|
||||
|
||||
/// Labels for the "draw" graph
|
||||
pub mod draw_graph {
|
||||
pub const NAME: &str = "draw";
|
||||
// Labels for input nodes
|
||||
pub mod input {}
|
||||
// Labels for non-input nodes
|
||||
pub mod node {
|
||||
pub const MAIN_PASS: &str = "main_pass";
|
||||
#[cfg(feature = "headless")]
|
||||
pub const COPY: &str = "copy";
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ pub struct BufferPool<Q, B, V, I, TM, FM> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum BackingBufferType {
|
||||
pub enum BackingBufferType {
|
||||
Vertices,
|
||||
Indices,
|
||||
Metadata,
|
||||
@ -578,12 +578,12 @@ impl RingIndex {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::coords::ZoomLevel;
|
||||
use crate::style::layer::StyleLayer;
|
||||
use lyon::tessellation::VertexBuffers;
|
||||
|
||||
use crate::render::resource::buffer_pool::{
|
||||
BackingBufferDescriptor, BackingBufferType, BufferPool, Queue,
|
||||
};
|
||||
use crate::render::resource::buffer_pool::BackingBufferType;
|
||||
use crate::render::resource::{BackingBufferDescriptor, BufferPool, Queue};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestBuffer {
|
||||
@ -639,7 +639,7 @@ mod tests {
|
||||
for _ in 0..2 {
|
||||
pool.allocate_layer_geometry(
|
||||
&queue,
|
||||
(0, 0, 0).into(),
|
||||
(0, 0, ZoomLevel::default()).into(),
|
||||
style_layer.clone(),
|
||||
&data48bytes_aligned,
|
||||
2,
|
||||
@ -653,7 +653,7 @@ mod tests {
|
||||
|
||||
pool.allocate_layer_geometry(
|
||||
&queue,
|
||||
(0, 0, 0).into(),
|
||||
(0, 0, ZoomLevel::default()).into(),
|
||||
style_layer.clone(),
|
||||
&data24bytes_aligned,
|
||||
2,
|
||||
@ -667,7 +667,7 @@ mod tests {
|
||||
|
||||
pool.allocate_layer_geometry(
|
||||
&queue,
|
||||
(0, 0, 0).into(),
|
||||
(0, 0, ZoomLevel::default()).into(),
|
||||
style_layer.clone(),
|
||||
&data24bytes_aligned,
|
||||
2,
|
||||
@ -679,7 +679,7 @@ mod tests {
|
||||
|
||||
pool.allocate_layer_geometry(
|
||||
&queue,
|
||||
(0, 0, 0).into(),
|
||||
(0, 0, ZoomLevel::default()).into(),
|
||||
style_layer.clone(),
|
||||
&data24bytes_aligned,
|
||||
2,
|
||||
@ -690,7 +690,7 @@ mod tests {
|
||||
|
||||
pool.allocate_layer_geometry(
|
||||
&queue,
|
||||
(0, 0, 0).into(),
|
||||
(0, 0, ZoomLevel::default()).into(),
|
||||
style_layer.clone(),
|
||||
&data24bytes_aligned,
|
||||
2,
|
||||
@ -701,7 +701,7 @@ mod tests {
|
||||
|
||||
pool.allocate_layer_geometry(
|
||||
&queue,
|
||||
(0, 0, 0).into(),
|
||||
(0, 0, ZoomLevel::default()).into(),
|
||||
style_layer,
|
||||
&data24bytes_aligned,
|
||||
2,
|
||||
|
||||
@ -4,14 +4,18 @@
|
||||
use crate::render::resource::texture::TextureView;
|
||||
use crate::render::settings::RendererSettings;
|
||||
use crate::render::util::HasChanged;
|
||||
use crate::{MapWindow, WindowSize};
|
||||
use crate::window::HeadedMapWindow;
|
||||
use crate::{MapWindow, MapWindowConfig, WindowSize};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::mem::size_of;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct BufferDimensions {
|
||||
width: usize,
|
||||
height: usize,
|
||||
unpadded_bytes_per_row: usize,
|
||||
padded_bytes_per_row: usize,
|
||||
pub struct BufferDimensions {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub unpadded_bytes_per_row: usize,
|
||||
pub padded_bytes_per_row: usize,
|
||||
}
|
||||
|
||||
impl BufferDimensions {
|
||||
@ -42,7 +46,7 @@ impl WindowHead {
|
||||
|
||||
pub fn recreate_surface<MW>(&mut self, window: &MW, instance: &wgpu::Instance)
|
||||
where
|
||||
MW: MapWindow,
|
||||
MW: MapWindow + HeadedMapWindow,
|
||||
{
|
||||
self.surface = unsafe { instance.create_surface(window.inner()) };
|
||||
}
|
||||
@ -52,14 +56,63 @@ impl WindowHead {
|
||||
}
|
||||
|
||||
pub struct BufferedTextureHead {
|
||||
texture: wgpu::Texture,
|
||||
output_buffer: wgpu::Buffer,
|
||||
buffer_dimensions: BufferDimensions,
|
||||
pub texture: wgpu::Texture,
|
||||
pub output_buffer: wgpu::Buffer,
|
||||
pub buffer_dimensions: BufferDimensions,
|
||||
}
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
impl BufferedTextureHead {
|
||||
pub async fn create_png<'a>(
|
||||
&self,
|
||||
device: &wgpu::Device,
|
||||
png_output_path: &str,
|
||||
// device: &wgpu::Device,
|
||||
) {
|
||||
// Note that we're not calling `.await` here.
|
||||
let buffer_slice = self.output_buffer.slice(..);
|
||||
let buffer_future = buffer_slice.map_async(wgpu::MapMode::Read);
|
||||
|
||||
// Poll the device in a blocking manner so that our future resolves.
|
||||
// In an actual application, `device.poll(...)` should
|
||||
// be called in an event loop or on another thread.
|
||||
device.poll(wgpu::Maintain::Wait);
|
||||
if let Ok(()) = buffer_future.await {
|
||||
let padded_buffer = buffer_slice.get_mapped_range();
|
||||
|
||||
let mut png_encoder = png::Encoder::new(
|
||||
File::create(png_output_path).unwrap(),
|
||||
self.buffer_dimensions.width as u32,
|
||||
self.buffer_dimensions.height as u32,
|
||||
);
|
||||
png_encoder.set_depth(png::BitDepth::Eight);
|
||||
png_encoder.set_color(png::ColorType::Rgba);
|
||||
let mut png_writer = png_encoder
|
||||
.write_header()
|
||||
.unwrap()
|
||||
.into_stream_writer_with_size(self.buffer_dimensions.unpadded_bytes_per_row)
|
||||
.unwrap();
|
||||
|
||||
// from the padded_buffer we write just the unpadded bytes into the image
|
||||
for chunk in padded_buffer.chunks(self.buffer_dimensions.padded_bytes_per_row) {
|
||||
png_writer
|
||||
.write_all(&chunk[..self.buffer_dimensions.unpadded_bytes_per_row])
|
||||
.unwrap();
|
||||
}
|
||||
png_writer.finish().unwrap();
|
||||
|
||||
// With the current interface, we have to make sure all mapped views are
|
||||
// dropped before we unmap the buffer.
|
||||
drop(padded_buffer);
|
||||
|
||||
self.output_buffer.unmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Head {
|
||||
Headed(WindowHead),
|
||||
Headless(BufferedTextureHead),
|
||||
Headless(Arc<BufferedTextureHead>),
|
||||
}
|
||||
|
||||
pub struct Surface {
|
||||
@ -74,7 +127,7 @@ impl Surface {
|
||||
settings: &RendererSettings,
|
||||
) -> Self
|
||||
where
|
||||
MW: MapWindow,
|
||||
MW: MapWindow + HeadedMapWindow,
|
||||
{
|
||||
let size = window.size();
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
@ -97,6 +150,7 @@ impl Surface {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Give better name
|
||||
pub fn from_image<MW>(device: &wgpu::Device, window: &MW, settings: &RendererSettings) -> Self
|
||||
where
|
||||
MW: MapWindow,
|
||||
@ -111,7 +165,7 @@ impl Surface {
|
||||
BufferDimensions::new(size.width() as usize, size.height() as usize);
|
||||
// The output buffer lets us retrieve the data as an array
|
||||
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
label: Some("BufferedTextureHead buffer"),
|
||||
size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
|
||||
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
@ -133,11 +187,11 @@ impl Surface {
|
||||
|
||||
Self {
|
||||
size,
|
||||
head: Head::Headless(BufferedTextureHead {
|
||||
head: Head::Headless(Arc::new(BufferedTextureHead {
|
||||
texture,
|
||||
output_buffer,
|
||||
buffer_dimensions,
|
||||
}),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +203,7 @@ impl Surface {
|
||||
let frame = match surface.get_current_texture() {
|
||||
Ok(view) => view,
|
||||
Err(wgpu::SurfaceError::Outdated) => {
|
||||
tracing::trace!("surface outdated");
|
||||
log::warn!("surface outdated");
|
||||
window.configure(device);
|
||||
surface
|
||||
.get_current_texture()
|
||||
@ -159,7 +213,8 @@ impl Surface {
|
||||
};
|
||||
frame.into()
|
||||
}
|
||||
Head::Headless(BufferedTextureHead { texture, .. }) => texture
|
||||
Head::Headless(arc) => arc
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default())
|
||||
.into(),
|
||||
}
|
||||
@ -193,11 +248,13 @@ impl Surface {
|
||||
|
||||
pub fn recreate<MW>(&mut self, window: &MW, instance: &wgpu::Instance)
|
||||
where
|
||||
MW: MapWindow,
|
||||
MW: MapWindow + HeadedMapWindow,
|
||||
{
|
||||
match &mut self.head {
|
||||
Head::Headed(head) => {
|
||||
head.recreate_surface(window, instance);
|
||||
Head::Headed(window_head) => {
|
||||
if window_head.has_changed(&(self.size.width(), self.size.height())) {
|
||||
window_head.recreate_surface(window, instance);
|
||||
}
|
||||
}
|
||||
Head::Headless(_) => {}
|
||||
}
|
||||
@ -216,6 +273,6 @@ impl HasChanged for WindowHead {
|
||||
type Criteria = (u32, u32);
|
||||
|
||||
fn has_changed(&self, criteria: &Self::Criteria) -> bool {
|
||||
self.surface_config.height != criteria.0 || self.surface_config.width != criteria.1
|
||||
self.surface_config.width != criteria.0 || self.surface_config.height != criteria.1
|
||||
}
|
||||
}
|
||||
|
||||
@ -202,7 +202,7 @@ impl<'a> TrackedRenderPass<'a> {
|
||||
/// Push a new debug group over the internal stack. Subsequent render commands and debug
|
||||
/// markers are grouped into this new group, until [`pop_debug_group`] is called.
|
||||
///
|
||||
/// ```ignore
|
||||
/// ```
|
||||
/// # fn example(mut pass: maplibre::render::resource::TrackedRenderPass<'static>) {
|
||||
/// pass.push_debug_group("Render the car");
|
||||
/// // [setup pipeline etc...]
|
||||
|
||||
@ -4,6 +4,10 @@ use crate::platform::COLOR_TEXTURE_FORMAT;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub use wgpu::Backends;
|
||||
pub use wgpu::Features;
|
||||
pub use wgpu::Limits;
|
||||
pub use wgpu::PowerPreference;
|
||||
pub use wgpu::TextureFormat;
|
||||
|
||||
/// Provides configuration for renderer initialization. Use [`Device::features`](crate::renderer::Device::features),
|
||||
/// [`Device::limits`](crate::renderer::Device::limits), and the [`WgpuAdapterInfo`](crate::render_resource::WgpuAdapterInfo)
|
||||
@ -11,17 +15,17 @@ pub use wgpu::Backends;
|
||||
#[derive(Clone)]
|
||||
pub struct WgpuSettings {
|
||||
pub device_label: Option<Cow<'static, str>>,
|
||||
pub backends: Option<wgpu::Backends>,
|
||||
pub power_preference: wgpu::PowerPreference,
|
||||
pub backends: Option<Backends>,
|
||||
pub power_preference: PowerPreference,
|
||||
/// The features to ensure are enabled regardless of what the adapter/backend supports.
|
||||
/// Setting these explicitly may cause renderer initialization to fail.
|
||||
pub features: wgpu::Features,
|
||||
pub features: Features,
|
||||
/// The features to ensure are disabled regardless of what the adapter/backend supports
|
||||
pub disabled_features: Option<wgpu::Features>,
|
||||
pub disabled_features: Option<Features>,
|
||||
/// The imposed limits.
|
||||
pub limits: wgpu::Limits,
|
||||
pub limits: Limits,
|
||||
/// The constraints on limits allowed regardless of what the adapter/backend supports
|
||||
pub constrained_limits: Option<wgpu::Limits>,
|
||||
pub constrained_limits: Option<Limits>,
|
||||
|
||||
/// Whether a trace is recorded an stored in the current working directory
|
||||
pub record_trace: bool,
|
||||
@ -32,12 +36,12 @@ impl Default for WgpuSettings {
|
||||
let backends = Some(wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::all()));
|
||||
|
||||
let limits = if cfg!(feature = "web-webgl") {
|
||||
wgpu::Limits {
|
||||
Limits {
|
||||
max_texture_dimension_2d: 4096,
|
||||
..wgpu::Limits::downlevel_webgl2_defaults()
|
||||
..Limits::downlevel_webgl2_defaults()
|
||||
}
|
||||
} else if cfg!(target_os = "android") {
|
||||
wgpu::Limits {
|
||||
Limits {
|
||||
max_storage_textures_per_shader_stage: 4,
|
||||
max_compute_workgroups_per_dimension: 0,
|
||||
max_compute_workgroup_size_z: 0,
|
||||
@ -45,19 +49,19 @@ impl Default for WgpuSettings {
|
||||
max_compute_workgroup_size_x: 0,
|
||||
max_compute_workgroup_storage_size: 0,
|
||||
max_compute_invocations_per_workgroup: 0,
|
||||
..wgpu::Limits::downlevel_defaults()
|
||||
..Limits::downlevel_defaults()
|
||||
}
|
||||
} else {
|
||||
wgpu::Limits {
|
||||
..wgpu::Limits::default()
|
||||
Limits {
|
||||
..Limits::default()
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
device_label: Default::default(),
|
||||
backends,
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
|
||||
power_preference: PowerPreference::HighPerformance,
|
||||
features: Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
|
||||
disabled_features: None,
|
||||
limits,
|
||||
constrained_limits: None,
|
||||
@ -101,8 +105,7 @@ impl Default for Msaa {
|
||||
#[derive(Clone)]
|
||||
pub struct RendererSettings {
|
||||
pub msaa: Msaa,
|
||||
pub texture_format: wgpu::TextureFormat,
|
||||
pub surface_type: SurfaceType,
|
||||
pub texture_format: TextureFormat,
|
||||
}
|
||||
|
||||
impl Default for RendererSettings {
|
||||
@ -110,7 +113,6 @@ impl Default for RendererSettings {
|
||||
Self {
|
||||
msaa: Msaa::default(),
|
||||
texture_format: COLOR_TEXTURE_FORMAT,
|
||||
surface_type: SurfaceType::Headed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
61
maplibre/src/render/stages/extract_stage.rs
Normal file
61
maplibre/src/render/stages/extract_stage.rs
Normal file
@ -0,0 +1,61 @@
|
||||
//! Extracts data from the current state.
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::coords::{ViewRegion, Zoom};
|
||||
use crate::io::tile_repository::TileRepository;
|
||||
use crate::render::camera::ViewProjection;
|
||||
use crate::render::render_phase::RenderPhase;
|
||||
use crate::render::resource::IndexEntry;
|
||||
use crate::render::shaders::{
|
||||
ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32,
|
||||
};
|
||||
use crate::render::tile_view_pattern::TileInView;
|
||||
use crate::render::util::Eventually::Initialized;
|
||||
use crate::schedule::Stage;
|
||||
use crate::{RenderState, Renderer, Style};
|
||||
use std::iter;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExtractStage;
|
||||
|
||||
impl Stage for ExtractStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state,
|
||||
renderer:
|
||||
Renderer {
|
||||
state:
|
||||
RenderState {
|
||||
mask_phase,
|
||||
tile_phase,
|
||||
tile_view_pattern,
|
||||
buffer_pool,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
if let (Initialized(tile_view_pattern), Initialized(buffer_pool)) =
|
||||
(tile_view_pattern, &buffer_pool)
|
||||
{
|
||||
let visible_level = view_state.visible_level();
|
||||
|
||||
let view_proj = view_state.view_projection();
|
||||
|
||||
let view_region = view_state
|
||||
.camera
|
||||
.view_region_bounding_box(&view_proj.invert())
|
||||
.map(|bounding_box| {
|
||||
ViewRegion::new(bounding_box, 0, *view_state.zoom, visible_level)
|
||||
});
|
||||
|
||||
if let Some(view_region) = &view_region {
|
||||
let zoom = view_state.zoom();
|
||||
tile_view_pattern.update_pattern(view_region, buffer_pool, zoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,5 @@
|
||||
//! Executes the [`RenderGraph`] current render graph.
|
||||
|
||||
// Plugins that contribute to the RenderGraph should use the following label conventions:
|
||||
// 1. Graph modules should have a NAME, input module, and node module (where relevant)
|
||||
// 2. The "top level" graph is the plugin module root. Just add things like `pub mod node` directly under the plugin module
|
||||
// 3. "sub graph" modules should be nested beneath their parent graph module
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::render::graph::{EmptyNode, RenderGraph};
|
||||
use crate::render::graph_runner::RenderGraphRunner;
|
||||
@ -14,42 +9,13 @@ use crate::schedule::Stage;
|
||||
use crate::Renderer;
|
||||
use log::error;
|
||||
|
||||
pub mod node {
|
||||
pub const MAIN_PASS_DEPENDENCIES: &str = "main_pass_dependencies";
|
||||
pub const MAIN_PASS_DRIVER: &str = "main_pass_driver";
|
||||
}
|
||||
|
||||
pub mod draw_graph {
|
||||
pub const NAME: &str = "draw";
|
||||
pub mod input {}
|
||||
pub mod node {
|
||||
pub const MAIN_PASS: &str = "main_pass";
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.
|
||||
pub struct GraphRunnerStage {
|
||||
graph: RenderGraph,
|
||||
}
|
||||
|
||||
impl Default for GraphRunnerStage {
|
||||
fn default() -> Self {
|
||||
let pass_node = MainPassNode::new();
|
||||
let mut graph = RenderGraph::default();
|
||||
|
||||
let mut draw_graph = RenderGraph::default();
|
||||
draw_graph.add_node(draw_graph::node::MAIN_PASS, pass_node);
|
||||
let input_node_id = draw_graph.set_input(vec![]);
|
||||
draw_graph
|
||||
.add_node_edge(input_node_id, draw_graph::node::MAIN_PASS)
|
||||
.unwrap();
|
||||
graph.add_sub_graph(draw_graph::NAME, draw_graph);
|
||||
|
||||
graph.add_node(node::MAIN_PASS_DEPENDENCIES, EmptyNode);
|
||||
graph.add_node(node::MAIN_PASS_DRIVER, MainPassDriverNode);
|
||||
graph
|
||||
.add_node_edge(node::MAIN_PASS_DEPENDENCIES, node::MAIN_PASS_DRIVER)
|
||||
.unwrap();
|
||||
impl GraphRunnerStage {
|
||||
pub fn new(graph: RenderGraph) -> Self {
|
||||
Self { graph }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +1,28 @@
|
||||
//! Rendering specific [Stages](Stage)
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::multi_stage;
|
||||
use crate::render::graph::{EmptyNode, RenderGraph, RenderGraphError};
|
||||
use crate::render::main_pass::{MainPassDriverNode, MainPassNode};
|
||||
use crate::render::stages::extract_stage::ExtractStage;
|
||||
use crate::render::stages::phase_sort_stage::PhaseSortStage;
|
||||
use crate::render::stages::queue_stage::QueueStage;
|
||||
use crate::render::{draw_graph, main_graph};
|
||||
use crate::schedule::{MultiStage, Schedule, Stage, StageLabel};
|
||||
use graph_runner_stage::GraphRunnerStage;
|
||||
use resource_stage::ResourceStage;
|
||||
use upload_stage::UploadStage;
|
||||
|
||||
mod extract_stage;
|
||||
mod graph_runner_stage;
|
||||
mod phase_sort_stage;
|
||||
mod queue_stage;
|
||||
mod resource_stage;
|
||||
mod upload_stage;
|
||||
|
||||
use crate::multi_stage;
|
||||
use crate::render::stages::phase_sort_stage::PhaseSortStage;
|
||||
use crate::render::stages::queue_stage::QueueStage;
|
||||
pub use graph_runner_stage::{draw_graph, node};
|
||||
#[cfg(feature = "headless")]
|
||||
// Exposed because it should be addable conditionally
|
||||
pub mod write_surface_buffer_stage;
|
||||
|
||||
/// The labels of the default App rendering stages.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||
@ -46,11 +53,52 @@ impl StageLabel for RenderStageLabel {
|
||||
}
|
||||
}
|
||||
|
||||
multi_stage!(PrepareStage, upload: UploadStage, resource: ResourceStage);
|
||||
multi_stage!(
|
||||
PrepareStage,
|
||||
resource: ResourceStage,
|
||||
extract: ExtractStage,
|
||||
upload: UploadStage
|
||||
);
|
||||
|
||||
pub fn register_render_stages(
|
||||
schedule: &mut Schedule,
|
||||
headless: bool,
|
||||
) -> Result<(), RenderGraphError> {
|
||||
let mut graph = RenderGraph::default();
|
||||
|
||||
let mut draw_graph = RenderGraph::default();
|
||||
draw_graph.add_node(draw_graph::node::MAIN_PASS, MainPassNode::new());
|
||||
let input_node_id = draw_graph.set_input(vec![]);
|
||||
draw_graph.add_node_edge(input_node_id, draw_graph::node::MAIN_PASS)?;
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
if headless {
|
||||
use crate::render::copy_surface_to_buffer_node::CopySurfaceBufferNode;
|
||||
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
|
||||
draw_graph.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)?;
|
||||
}
|
||||
|
||||
graph.add_sub_graph(draw_graph::NAME, draw_graph);
|
||||
graph.add_node(main_graph::node::MAIN_PASS_DEPENDENCIES, EmptyNode);
|
||||
graph.add_node(main_graph::node::MAIN_PASS_DRIVER, MainPassDriverNode);
|
||||
graph.add_node_edge(
|
||||
main_graph::node::MAIN_PASS_DEPENDENCIES,
|
||||
main_graph::node::MAIN_PASS_DRIVER,
|
||||
)?;
|
||||
|
||||
pub fn register_render_stages(schedule: &mut Schedule) {
|
||||
schedule.add_stage(RenderStageLabel::Prepare, PrepareStage::default());
|
||||
schedule.add_stage(RenderStageLabel::Queue, QueueStage::default());
|
||||
schedule.add_stage(RenderStageLabel::PhaseSort, PhaseSortStage::default());
|
||||
schedule.add_stage(RenderStageLabel::Render, GraphRunnerStage::default());
|
||||
schedule.add_stage(RenderStageLabel::Render, GraphRunnerStage::new(graph));
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
if headless {
|
||||
use crate::render::stages::write_surface_buffer_stage::WriteSurfaceBufferStage;
|
||||
schedule.add_stage(
|
||||
RenderStageLabel::Cleanup,
|
||||
WriteSurfaceBufferStage::default(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -2,8 +2,7 @@
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::coords::{ViewRegion, Zoom};
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::LayerTessellateMessage;
|
||||
use crate::io::tile_repository::TileRepository;
|
||||
use crate::render::camera::ViewProjection;
|
||||
use crate::render::render_phase::RenderPhase;
|
||||
use crate::render::resource::IndexEntry;
|
||||
|
||||
@ -2,8 +2,7 @@
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::coords::{ViewRegion, Zoom};
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::LayerTessellateMessage;
|
||||
use crate::io::tile_repository::TileRepository;
|
||||
use crate::render::camera::ViewProjection;
|
||||
use crate::render::resource::IndexEntry;
|
||||
use crate::render::shaders::{
|
||||
@ -23,15 +22,27 @@ impl Stage for QueueStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, .. },
|
||||
view_state,
|
||||
renderer:
|
||||
Renderer {
|
||||
state:
|
||||
RenderState {
|
||||
mask_phase,
|
||||
tile_phase,
|
||||
tile_view_pattern,
|
||||
buffer_pool,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
state.mask_phase.items.clear();
|
||||
state.tile_phase.items.clear();
|
||||
mask_phase.items.clear();
|
||||
tile_phase.items.clear();
|
||||
|
||||
if let (Initialized(tile_view_pattern), Initialized(buffer_pool)) =
|
||||
(&state.tile_view_pattern, &state.buffer_pool)
|
||||
(tile_view_pattern, &buffer_pool)
|
||||
{
|
||||
let index = buffer_pool.index();
|
||||
|
||||
@ -43,7 +54,7 @@ impl Stage for QueueStage {
|
||||
let shape_to_render = fallback.as_ref().unwrap_or(shape);
|
||||
|
||||
// Draw mask
|
||||
state.mask_phase.add(tile_in_view.clone());
|
||||
mask_phase.add(tile_in_view.clone());
|
||||
|
||||
if let Some(entries) = index.get_layers(&shape_to_render.coords) {
|
||||
let mut layers_to_render: Vec<&IndexEntry> = Vec::from_iter(entries);
|
||||
@ -51,9 +62,7 @@ impl Stage for QueueStage {
|
||||
|
||||
for entry in layers_to_render {
|
||||
// Draw tile
|
||||
state
|
||||
.tile_phase
|
||||
.add((entry.clone(), shape_to_render.clone()))
|
||||
tile_phase.add((entry.clone(), shape_to_render.clone()))
|
||||
}
|
||||
} else {
|
||||
tracing::trace!("No layers found at {}", &shape_to_render.coords);
|
||||
|
||||
@ -28,13 +28,14 @@ impl Stage for ResourceStage {
|
||||
Renderer {
|
||||
settings,
|
||||
device,
|
||||
surface,
|
||||
state,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
let surface = &mut state.surface;
|
||||
|
||||
let size = surface.size();
|
||||
|
||||
surface.reconfigure(device);
|
||||
|
||||
@ -2,8 +2,7 @@
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::coords::{ViewRegion, Zoom};
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::LayerTessellateMessage;
|
||||
use crate::io::tile_repository::{StoredLayer, TileRepository};
|
||||
use crate::render::camera::ViewProjection;
|
||||
use crate::render::resource::IndexEntry;
|
||||
use crate::render::shaders::{
|
||||
@ -26,16 +25,8 @@ impl Stage for UploadStage {
|
||||
MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_cache,
|
||||
renderer:
|
||||
Renderer {
|
||||
settings: _,
|
||||
device: _,
|
||||
queue,
|
||||
surface: _,
|
||||
state,
|
||||
..
|
||||
},
|
||||
tile_repository,
|
||||
renderer: Renderer { queue, state, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
@ -67,10 +58,8 @@ impl Stage for UploadStage {
|
||||
.map(|bounding_box| ViewRegion::new(bounding_box, 0, *view_state.zoom, visible_level));
|
||||
|
||||
if let Some(view_region) = &view_region {
|
||||
let zoom = view_state.zoom();
|
||||
|
||||
self.upload_tile_geometry(state, queue, tile_cache, style, view_region);
|
||||
self.update_tile_view_pattern(state, queue, view_region, &view_proj, zoom);
|
||||
self.upload_tile_geometry(state, queue, tile_repository, style, view_region);
|
||||
self.upload_tile_view_pattern(state, queue, &view_proj);
|
||||
self.update_metadata();
|
||||
}
|
||||
}
|
||||
@ -99,7 +88,7 @@ impl UploadStage {
|
||||
/*let source_layer = entry.style_layer.source_layer.as_ref().unwrap();
|
||||
|
||||
if let Some(result) = scheduler
|
||||
.get_tile_cache()
|
||||
.get_tile_repository()
|
||||
.iter_tessellated_layers_at(&world_coords)
|
||||
.unwrap()
|
||||
.find(|layer| source_layer.as_str() == layer.layer_name())
|
||||
@ -147,22 +136,15 @@ impl UploadStage {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn update_tile_view_pattern(
|
||||
pub fn upload_tile_view_pattern(
|
||||
&self,
|
||||
RenderState {
|
||||
tile_view_pattern,
|
||||
buffer_pool,
|
||||
..
|
||||
tile_view_pattern, ..
|
||||
}: &mut RenderState,
|
||||
queue: &wgpu::Queue,
|
||||
view_region: &ViewRegion,
|
||||
view_proj: &ViewProjection,
|
||||
zoom: Zoom,
|
||||
) {
|
||||
if let (Initialized(tile_view_pattern), Initialized(buffer_pool)) =
|
||||
(tile_view_pattern, buffer_pool)
|
||||
{
|
||||
tile_view_pattern.update_pattern(view_region, buffer_pool, zoom);
|
||||
if let Initialized(tile_view_pattern) = tile_view_pattern {
|
||||
tile_view_pattern.upload_pattern(queue, view_proj);
|
||||
}
|
||||
}
|
||||
@ -172,7 +154,7 @@ impl UploadStage {
|
||||
&self,
|
||||
RenderState { buffer_pool, .. }: &mut RenderState,
|
||||
queue: &wgpu::Queue,
|
||||
tile_cache: &TileCache,
|
||||
tile_repository: &TileRepository,
|
||||
style: &Style,
|
||||
view_region: &ViewRegion,
|
||||
) {
|
||||
@ -182,7 +164,7 @@ impl UploadStage {
|
||||
let loaded_layers = buffer_pool
|
||||
.get_loaded_layers_at(&world_coords)
|
||||
.unwrap_or_default();
|
||||
if let Some(available_layers) = tile_cache
|
||||
if let Some(available_layers) = tile_repository
|
||||
.iter_tessellated_layers_at(&world_coords)
|
||||
.map(|layers| {
|
||||
layers
|
||||
@ -204,10 +186,10 @@ impl UploadStage {
|
||||
.map(|color| color.into());
|
||||
|
||||
match message {
|
||||
LayerTessellateMessage::UnavailableLayer { coords: _, .. } => {
|
||||
StoredLayer::UnavailableLayer { coords: _, .. } => {
|
||||
/*self.buffer_pool.mark_layer_unavailable(*coords);*/
|
||||
}
|
||||
LayerTessellateMessage::TessellatedLayer {
|
||||
StoredLayer::TessellatedLayer {
|
||||
coords,
|
||||
feature_indices,
|
||||
layer_data,
|
||||
|
||||
60
maplibre/src/render/stages/write_surface_buffer_stage.rs
Normal file
60
maplibre/src/render/stages/write_surface_buffer_stage.rs
Normal file
@ -0,0 +1,60 @@
|
||||
//! Stage which writes the current contents of the GPU/CPU buffer in [`BufferedTextureHead`]
|
||||
//! to disk as PNG.
|
||||
|
||||
use crate::context::MapContext;
|
||||
use crate::coords::{ViewRegion, Zoom};
|
||||
use crate::io::tile_repository::TileRepository;
|
||||
use crate::render::camera::ViewProjection;
|
||||
use crate::render::render_phase::RenderPhase;
|
||||
use crate::render::resource::{BufferDimensions, BufferedTextureHead, Head, IndexEntry};
|
||||
use crate::render::shaders::{
|
||||
ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32,
|
||||
};
|
||||
use crate::render::tile_view_pattern::TileInView;
|
||||
use crate::render::util::Eventually::Initialized;
|
||||
use crate::schedule::Stage;
|
||||
use crate::{RenderState, Renderer, Style};
|
||||
use std::fs::File;
|
||||
use std::future::Future;
|
||||
use std::io::Write;
|
||||
use std::iter;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::task;
|
||||
use wgpu::{BufferAsyncError, BufferSlice};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WriteSurfaceBufferStage {
|
||||
frame: u64,
|
||||
}
|
||||
|
||||
impl Stage for WriteSurfaceBufferStage {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
renderer: Renderer { state, device, .. },
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
match state.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,7 +79,7 @@ where
|
||||
};
|
||||
|
||||
if should_replace {
|
||||
mem::replace(self, Eventually::Initialized(f()));
|
||||
*self = Eventually::Initialized(f());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,7 +87,7 @@ impl<T> Eventually<T> {
|
||||
#[tracing::instrument(name = "initialize", skip_all)]
|
||||
pub fn initialize(&mut self, f: impl FnOnce() -> T) {
|
||||
if let Eventually::Uninitialized = self {
|
||||
mem::replace(self, Eventually::Initialized(f()));
|
||||
*self = Eventually::Initialized(f());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
80
maplibre/src/stages/message.rs
Normal file
80
maplibre/src/stages/message.rs
Normal file
@ -0,0 +1,80 @@
|
||||
use crate::coords::{WorldCoords, WorldTileCoords, Zoom};
|
||||
use crate::error::Error;
|
||||
use crate::io::geometry_index::{GeometryIndex, IndexedGeometry};
|
||||
use crate::io::pipeline::PipelineContext;
|
||||
use crate::io::pipeline::Processable;
|
||||
use crate::io::tile_pipelines::build_vector_tile_pipeline;
|
||||
use crate::io::tile_repository::StoredLayer;
|
||||
use crate::io::tile_request_state::TileRequestState;
|
||||
use crate::io::{TileRequest, TileRequestID};
|
||||
use crate::render::ShaderVertex;
|
||||
use crate::stages::HeadedPipelineProcessor;
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
|
||||
use geozero::mvt::tile;
|
||||
use std::fmt;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
|
||||
pub type MessageSender = mpsc::Sender<TessellateMessage>;
|
||||
pub type MessageReceiver = mpsc::Receiver<TessellateMessage>;
|
||||
|
||||
/// [crate::io::TileTessellateMessage] or [crate::io::LayerTessellateMessage] tessellation message.
|
||||
pub enum TessellateMessage {
|
||||
Tile(TileTessellateMessage),
|
||||
Layer(LayerTessellateMessage),
|
||||
}
|
||||
|
||||
/// The result of the tessellation of a tile.
|
||||
pub struct TileTessellateMessage {
|
||||
pub request_id: TileRequestID,
|
||||
pub coords: WorldTileCoords,
|
||||
}
|
||||
|
||||
/// `TessellatedLayer` contains the result of the tessellation for a specific layer, otherwise
|
||||
/// `UnavailableLayer` if the layer doesn't exist.
|
||||
pub enum LayerTessellateMessage {
|
||||
UnavailableLayer {
|
||||
coords: WorldTileCoords,
|
||||
layer_name: String,
|
||||
},
|
||||
TessellatedLayer {
|
||||
coords: WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
/// Holds for each feature the count of indices.
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: tile::Layer,
|
||||
},
|
||||
}
|
||||
|
||||
impl Into<StoredLayer> for LayerTessellateMessage {
|
||||
fn into(self) -> StoredLayer {
|
||||
match self {
|
||||
LayerTessellateMessage::UnavailableLayer { coords, layer_name } => {
|
||||
StoredLayer::UnavailableLayer { coords, layer_name }
|
||||
}
|
||||
LayerTessellateMessage::TessellatedLayer {
|
||||
coords,
|
||||
buffer,
|
||||
feature_indices,
|
||||
layer_data,
|
||||
} => StoredLayer::TessellatedLayer {
|
||||
coords,
|
||||
buffer,
|
||||
feature_indices,
|
||||
layer_data,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for LayerTessellateMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"LayerTessellateMessage{}",
|
||||
match self {
|
||||
LayerTessellateMessage::UnavailableLayer { coords, .. } => coords,
|
||||
LayerTessellateMessage::TessellatedLayer { coords, .. } => coords,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,188 @@
|
||||
//! [Stages](Stage) for requesting and preparing data
|
||||
|
||||
use crate::io::source_client::SourceClient;
|
||||
use crate::coords::ZoomLevel;
|
||||
use crate::coords::{WorldCoords, WorldTileCoords, Zoom};
|
||||
use crate::error::Error;
|
||||
use crate::io::geometry_index::GeometryIndex;
|
||||
use crate::io::geometry_index::{IndexProcessor, IndexedGeometry, TileIndex};
|
||||
use crate::io::pipeline::Processable;
|
||||
use crate::io::pipeline::{PipelineContext, PipelineProcessor};
|
||||
use crate::io::source_client::{HttpSourceClient, SourceClient};
|
||||
use crate::io::tile_pipelines::build_vector_tile_pipeline;
|
||||
use crate::io::tile_repository::StoredLayer;
|
||||
use crate::io::tile_request_state::TileRequestState;
|
||||
use crate::io::{TileRequest, TileRequestID};
|
||||
use crate::render::ShaderVertex;
|
||||
use crate::schedule::Schedule;
|
||||
use crate::stages::message::{
|
||||
LayerTessellateMessage, MessageReceiver, MessageSender, TessellateMessage,
|
||||
TileTessellateMessage,
|
||||
};
|
||||
use crate::stages::populate_tile_store_stage::PopulateTileStore;
|
||||
use crate::HTTPClient;
|
||||
use crate::tessellation::zero_tessellator::ZeroTessellator;
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
|
||||
use crate::{HttpClient, ScheduleMethod, Scheduler};
|
||||
use geozero::mvt::tile;
|
||||
use geozero::GeozeroDatasource;
|
||||
use prost::Message;
|
||||
use request_stage::RequestStage;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
|
||||
mod message;
|
||||
mod populate_tile_store_stage;
|
||||
mod request_stage;
|
||||
|
||||
pub fn register_stages<HC: HTTPClient>(schedule: &mut Schedule, source_client: SourceClient<HC>) {
|
||||
schedule.add_stage("request", RequestStage::new(source_client));
|
||||
schedule.add_stage("populate_tile_store", PopulateTileStore::default());
|
||||
/// Register stages required for requesting and preparing new tiles.
|
||||
pub fn register_stages<HC: HttpClient, SM: ScheduleMethod>(
|
||||
schedule: &mut Schedule,
|
||||
http_source_client: HttpSourceClient<HC>,
|
||||
scheduler: Box<Scheduler<SM>>,
|
||||
) {
|
||||
let (message_sender, message_receiver): (MessageSender, MessageReceiver) = mpsc::channel();
|
||||
let shared_thread_state = SharedThreadState {
|
||||
tile_request_state: Arc::new(Mutex::new(TileRequestState::new())),
|
||||
message_sender,
|
||||
geometry_index: Arc::new(Mutex::new(GeometryIndex::new())),
|
||||
};
|
||||
|
||||
schedule.add_stage(
|
||||
"request",
|
||||
RequestStage::new(shared_thread_state.clone(), http_source_client, *scheduler),
|
||||
);
|
||||
schedule.add_stage(
|
||||
"populate_tile_store",
|
||||
PopulateTileStore::new(shared_thread_state, message_receiver),
|
||||
);
|
||||
}
|
||||
|
||||
pub struct HeadedPipelineProcessor {
|
||||
state: SharedThreadState,
|
||||
}
|
||||
|
||||
impl PipelineProcessor for HeadedPipelineProcessor {
|
||||
fn tile_finished(&mut self, request_id: TileRequestID, coords: &WorldTileCoords) {
|
||||
self.state
|
||||
.message_sender
|
||||
.send(TessellateMessage::Tile(TileTessellateMessage {
|
||||
request_id,
|
||||
coords: *coords,
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn layer_unavailable(&mut self, coords: &WorldTileCoords, layer_name: &str) {
|
||||
self.state
|
||||
.message_sender
|
||||
.send(TessellateMessage::Layer(
|
||||
LayerTessellateMessage::UnavailableLayer {
|
||||
coords: *coords,
|
||||
layer_name: layer_name.to_owned(),
|
||||
},
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn layer_tesselation_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: tile::Layer,
|
||||
) {
|
||||
self.state
|
||||
.message_sender
|
||||
.send(TessellateMessage::Layer(
|
||||
LayerTessellateMessage::TessellatedLayer {
|
||||
coords: *coords,
|
||||
buffer,
|
||||
feature_indices,
|
||||
layer_data,
|
||||
},
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn layer_indexing_finished(
|
||||
&mut self,
|
||||
coords: &WorldTileCoords,
|
||||
geometries: Vec<IndexedGeometry<f64>>,
|
||||
) {
|
||||
if let Ok(mut geometry_index) = self.state.geometry_index.lock() {
|
||||
geometry_index.index_tile(&coords, TileIndex::Linear { list: geometries })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores and provides access to the thread safe data shared between the schedulers.
|
||||
#[derive(Clone)]
|
||||
pub struct SharedThreadState {
|
||||
pub tile_request_state: Arc<Mutex<TileRequestState>>,
|
||||
pub message_sender: mpsc::Sender<TessellateMessage>,
|
||||
pub geometry_index: Arc<Mutex<GeometryIndex>>,
|
||||
}
|
||||
|
||||
impl SharedThreadState {
|
||||
fn get_tile_request(&self, request_id: TileRequestID) -> Option<TileRequest> {
|
||||
self.tile_request_state
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|tile_request_state| tile_request_state.get_tile_request(request_id).cloned())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn process_tile(&self, request_id: TileRequestID, data: Box<[u8]>) -> Result<(), Error> {
|
||||
if let Some(tile_request) = self.get_tile_request(request_id) {
|
||||
let mut pipeline_context = PipelineContext::new(HeadedPipelineProcessor {
|
||||
state: self.clone(),
|
||||
});
|
||||
let pipeline = build_vector_tile_pipeline();
|
||||
pipeline.process((tile_request, request_id, data), &mut pipeline_context);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tile_unavailable(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
request_id: TileRequestID,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(tile_request) = self.get_tile_request(request_id) {
|
||||
for to_load in &tile_request.layers {
|
||||
tracing::warn!("layer {} at {} unavailable", to_load, coords);
|
||||
self.message_sender.send(TessellateMessage::Layer(
|
||||
LayerTessellateMessage::UnavailableLayer {
|
||||
coords: tile_request.coords,
|
||||
layer_name: to_load.to_string(),
|
||||
},
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn query_point(
|
||||
&self,
|
||||
world_coords: &WorldCoords,
|
||||
z: ZoomLevel,
|
||||
zoom: Zoom,
|
||||
) -> Option<Vec<IndexedGeometry<f64>>> {
|
||||
if let Ok(geometry_index) = self.geometry_index.lock() {
|
||||
geometry_index
|
||||
.query_point(world_coords, z, zoom)
|
||||
.map(|geometries| {
|
||||
geometries
|
||||
.iter()
|
||||
.cloned()
|
||||
.cloned()
|
||||
.collect::<Vec<IndexedGeometry<f64>>>()
|
||||
})
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,35 +1,46 @@
|
||||
//! Receives data from async threads and populates the [`crate::io::tile_cache::TileCache`].
|
||||
//! Receives data from async threads and populates the [`crate::io::tile_repository::TileRepository`].
|
||||
|
||||
use super::{MessageReceiver, SharedThreadState, TessellateMessage, TileTessellateMessage};
|
||||
use crate::context::MapContext;
|
||||
use crate::io::{TessellateMessage, TileTessellateMessage};
|
||||
use crate::io::tile_repository::StoredLayer;
|
||||
use crate::schedule::Stage;
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PopulateTileStore {}
|
||||
pub struct PopulateTileStore {
|
||||
shared_thread_state: SharedThreadState,
|
||||
message_receiver: MessageReceiver,
|
||||
}
|
||||
|
||||
impl PopulateTileStore {
|
||||
pub fn new(shared_thread_state: SharedThreadState, message_receiver: MessageReceiver) -> Self {
|
||||
Self {
|
||||
shared_thread_state,
|
||||
message_receiver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stage for PopulateTileStore {
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
tile_cache,
|
||||
shared_thread_state,
|
||||
message_receiver,
|
||||
..
|
||||
tile_repository, ..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
if let Ok(result) = message_receiver.try_recv() {
|
||||
if let Ok(result) = self.message_receiver.try_recv() {
|
||||
match result {
|
||||
TessellateMessage::Layer(layer_result) => {
|
||||
let layer: StoredLayer = layer_result.into();
|
||||
tracing::trace!(
|
||||
"Layer {} at {} reached main thread",
|
||||
layer_result.layer_name(),
|
||||
layer_result.get_coords()
|
||||
layer.layer_name(),
|
||||
layer.get_coords()
|
||||
);
|
||||
tile_cache.put_tessellated_layer(layer_result);
|
||||
tile_repository.put_tessellated_layer(layer);
|
||||
}
|
||||
TessellateMessage::Tile(TileTessellateMessage { request_id, coords }) => loop {
|
||||
if let Ok(mut tile_request_state) =
|
||||
shared_thread_state.tile_request_state.try_lock()
|
||||
self.shared_thread_state.tile_request_state.try_lock()
|
||||
{
|
||||
tile_request_state.finish_tile_request(request_id);
|
||||
tracing::trace!("Tile at {} finished loading", coords);
|
||||
|
||||
@ -3,46 +3,55 @@
|
||||
use crate::context::MapContext;
|
||||
use crate::coords::{ViewRegion, WorldTileCoords};
|
||||
use crate::error::Error;
|
||||
use crate::io::shared_thread_state::SharedThreadState;
|
||||
use crate::io::source_client::SourceClient;
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::source_client::{HttpSourceClient, SourceClient};
|
||||
use crate::io::tile_repository::TileRepository;
|
||||
use crate::io::TileRequest;
|
||||
use crate::schedule::Stage;
|
||||
use crate::{HTTPClient, ScheduleMethod, Style};
|
||||
use crate::stages::SharedThreadState;
|
||||
use crate::{HttpClient, ScheduleMethod, Scheduler, Style};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct RequestStage<HC>
|
||||
pub struct RequestStage<SM, HC>
|
||||
where
|
||||
HC: HTTPClient,
|
||||
SM: ScheduleMethod,
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub source_client: SourceClient<HC>,
|
||||
pub try_failed: bool,
|
||||
shared_thread_state: SharedThreadState,
|
||||
scheduler: Scheduler<SM>,
|
||||
http_source_client: HttpSourceClient<HC>,
|
||||
try_failed: bool,
|
||||
}
|
||||
|
||||
impl<HC> RequestStage<HC>
|
||||
impl<SM, HC> RequestStage<SM, HC>
|
||||
where
|
||||
HC: HTTPClient,
|
||||
SM: ScheduleMethod,
|
||||
HC: HttpClient,
|
||||
{
|
||||
pub fn new(source_client: SourceClient<HC>) -> Self {
|
||||
pub fn new(
|
||||
shared_thread_state: SharedThreadState,
|
||||
http_source_client: HttpSourceClient<HC>,
|
||||
scheduler: Scheduler<SM>,
|
||||
) -> Self {
|
||||
Self {
|
||||
source_client,
|
||||
shared_thread_state,
|
||||
scheduler,
|
||||
http_source_client,
|
||||
try_failed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<HC> Stage for RequestStage<HC>
|
||||
impl<SM, HC> Stage for RequestStage<SM, HC>
|
||||
where
|
||||
HC: HTTPClient,
|
||||
SM: ScheduleMethod,
|
||||
HC: HttpClient,
|
||||
{
|
||||
fn run(
|
||||
&mut self,
|
||||
MapContext {
|
||||
view_state,
|
||||
style,
|
||||
tile_cache,
|
||||
scheduler,
|
||||
shared_thread_state,
|
||||
tile_repository,
|
||||
..
|
||||
}: &mut MapContext,
|
||||
) {
|
||||
@ -59,13 +68,7 @@ where
|
||||
{
|
||||
if let Some(view_region) = &view_region {
|
||||
// FIXME: We also need to request tiles from layers above if we are over the maximum zoom level
|
||||
self.try_failed = self.request_tiles_in_view(
|
||||
tile_cache,
|
||||
style,
|
||||
shared_thread_state,
|
||||
scheduler,
|
||||
view_region,
|
||||
);
|
||||
self.try_failed = self.request_tiles_in_view(tile_repository, style, view_region);
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,18 +77,17 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<HC> RequestStage<HC>
|
||||
impl<SM, HC> RequestStage<SM, HC>
|
||||
where
|
||||
HC: HTTPClient,
|
||||
SM: ScheduleMethod,
|
||||
HC: HttpClient,
|
||||
{
|
||||
/// Request tiles which are currently in view.
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn request_tiles_in_view(
|
||||
&self,
|
||||
tile_cache: &TileCache,
|
||||
tile_repository: &TileRepository,
|
||||
style: &Style,
|
||||
shared_thread_state: &SharedThreadState,
|
||||
scheduler: &Box<dyn ScheduleMethod>,
|
||||
view_region: &ViewRegion,
|
||||
) -> bool {
|
||||
let mut try_failed = false;
|
||||
@ -99,13 +101,7 @@ where
|
||||
if coords.build_quad_key().is_some() {
|
||||
// TODO: Make tesselation depend on style?
|
||||
try_failed = self
|
||||
.try_request_tile(
|
||||
tile_cache,
|
||||
shared_thread_state,
|
||||
scheduler,
|
||||
&coords,
|
||||
&source_layers,
|
||||
)
|
||||
.try_request_tile(tile_repository, &coords, &source_layers)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@ -114,17 +110,15 @@ where
|
||||
|
||||
fn try_request_tile(
|
||||
&self,
|
||||
tile_cache: &TileCache,
|
||||
shared_thread_state: &SharedThreadState,
|
||||
scheduler: &Box<dyn ScheduleMethod>,
|
||||
tile_repository: &TileRepository,
|
||||
coords: &WorldTileCoords,
|
||||
layers: &HashSet<String>,
|
||||
) -> Result<bool, Error> {
|
||||
if !tile_cache.is_layers_missing(coords, layers) {
|
||||
if !tile_repository.is_layers_missing(coords, layers) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Ok(mut tile_request_state) = shared_thread_state.tile_request_state.try_lock() {
|
||||
if let Ok(mut tile_request_state) = self.shared_thread_state.tile_request_state.try_lock() {
|
||||
if let Some(request_id) = tile_request_state.start_tile_request(TileRequest {
|
||||
coords: *coords,
|
||||
layers: layers.clone(),
|
||||
@ -141,26 +135,25 @@ where
|
||||
);
|
||||
}*/
|
||||
|
||||
let client = self.source_client.clone();
|
||||
let client = SourceClient::Http(self.http_source_client.clone());
|
||||
let coords = *coords;
|
||||
|
||||
scheduler
|
||||
.schedule(
|
||||
shared_thread_state.clone(),
|
||||
Box::new(move |state: SharedThreadState| {
|
||||
Box::pin(async move {
|
||||
match client.fetch(&coords).await {
|
||||
Ok(data) => state
|
||||
.process_tile(request_id, data.into_boxed_slice())
|
||||
.unwrap(),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", &e);
|
||||
state.tile_unavailable(&coords, request_id).unwrap()
|
||||
}
|
||||
let state = self.shared_thread_state.clone();
|
||||
self.scheduler
|
||||
.schedule_method()
|
||||
.schedule(Box::new(move || {
|
||||
Box::pin(async move {
|
||||
match client.fetch(&coords).await {
|
||||
Ok(data) => state
|
||||
.process_tile(request_id, data.into_boxed_slice())
|
||||
.unwrap(),
|
||||
Err(e) => {
|
||||
log::error!("{:?}", &e);
|
||||
state.tile_unavailable(&coords, request_id).unwrap()
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ impl Default for Style {
|
||||
minzoom: None,
|
||||
metadata: None,
|
||||
paint: Some(LayerPaint::Line(LinePaint {
|
||||
line_color: Some(Color::from_str("lightgreen").unwrap()),
|
||||
line_color: Some(Color::from_str("#c8facc").unwrap()),
|
||||
})),
|
||||
source: None,
|
||||
source_layer: Some("park".to_string()),
|
||||
@ -46,7 +46,7 @@ impl Default for Style {
|
||||
minzoom: None,
|
||||
metadata: None,
|
||||
paint: Some(LayerPaint::Line(LinePaint {
|
||||
line_color: Some(Color::from_str("lightgreen").unwrap()),
|
||||
line_color: Some(Color::from_str("#e0dfdf").unwrap()),
|
||||
})),
|
||||
source: None,
|
||||
source_layer: Some("landuse".to_string()),
|
||||
@ -59,20 +59,20 @@ impl Default for Style {
|
||||
minzoom: None,
|
||||
metadata: None,
|
||||
paint: Some(LayerPaint::Line(LinePaint {
|
||||
line_color: Some(Color::from_str("lightgreen").unwrap()),
|
||||
line_color: Some(Color::from_str("#aedfa3").unwrap()),
|
||||
})),
|
||||
source: None,
|
||||
source_layer: Some("landcover".to_string()),
|
||||
},
|
||||
StyleLayer {
|
||||
index: 3,
|
||||
id: "1transportation".to_string(),
|
||||
id: "transportation".to_string(),
|
||||
typ: "line".to_string(),
|
||||
maxzoom: None,
|
||||
minzoom: None,
|
||||
metadata: None,
|
||||
paint: Some(LayerPaint::Line(LinePaint {
|
||||
line_color: Some(Color::from_str("violet").unwrap()),
|
||||
line_color: Some(Color::from_str("#ffffff").unwrap()),
|
||||
})),
|
||||
source: None,
|
||||
source_layer: Some("transportation".to_string()),
|
||||
@ -85,7 +85,7 @@ impl Default for Style {
|
||||
minzoom: None,
|
||||
metadata: None,
|
||||
paint: Some(LayerPaint::Line(LinePaint {
|
||||
line_color: Some(Color::from_str("grey").unwrap()),
|
||||
line_color: Some(Color::from_str("#d9d0c9").unwrap()),
|
||||
})),
|
||||
source: None,
|
||||
source_layer: Some("building".to_string()),
|
||||
@ -98,7 +98,7 @@ impl Default for Style {
|
||||
minzoom: None,
|
||||
metadata: None,
|
||||
paint: Some(LayerPaint::Line(LinePaint {
|
||||
line_color: Some(Color::from_str("blue").unwrap()),
|
||||
line_color: Some(Color::from_str("#aad3df").unwrap()),
|
||||
})),
|
||||
source: None,
|
||||
source_layer: Some("water".to_string()),
|
||||
@ -111,7 +111,7 @@ impl Default for Style {
|
||||
minzoom: None,
|
||||
metadata: None,
|
||||
paint: Some(LayerPaint::Line(LinePaint {
|
||||
line_color: Some(Color::from_str("blue").unwrap()),
|
||||
line_color: Some(Color::from_str("#aad3df").unwrap()),
|
||||
})),
|
||||
source: None,
|
||||
source_layer: Some("waterway".to_string()),
|
||||
|
||||
@ -4,7 +4,7 @@ use std::time::Duration;
|
||||
/// Measures the frames per second.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// ```
|
||||
/// use maplibre::util::FPSMeter;
|
||||
///
|
||||
/// let mut meter = FPSMeter::new();
|
||||
|
||||
@ -51,7 +51,7 @@ where
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// ```
|
||||
/// # use maplibre::define_label;
|
||||
/// define_label!(MyNewLabelTrait);
|
||||
/// ```
|
||||
|
||||
@ -1,31 +1,38 @@
|
||||
//! Utilities for the window system.
|
||||
|
||||
use crate::{HTTPClient, MapSchedule, ScheduleMethod};
|
||||
use crate::{HttpClient, InteractiveMapSchedule, ScheduleMethod};
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
|
||||
/// Window with an optional [carte::window::WindowSize].
|
||||
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
|
||||
pub trait MapWindow {
|
||||
type EventLoop;
|
||||
type Window: raw_window_handle::HasRawWindowHandle; // FIXME: Not true for headless
|
||||
type MapWindowConfig: MapWindowConfig<MapWindow = Self>;
|
||||
|
||||
fn create(map_window_config: &Self::MapWindowConfig) -> Self;
|
||||
|
||||
fn size(&self) -> WindowSize;
|
||||
|
||||
fn inner(&self) -> &Self::Window;
|
||||
}
|
||||
|
||||
/// Window which references a physical `RawWindow`. This is only implemented by headed windows and
|
||||
/// not by headless windows.
|
||||
pub trait HeadedMapWindow: MapWindow {
|
||||
type RawWindow: HasRawWindowHandle;
|
||||
|
||||
fn inner(&self) -> &Self::RawWindow;
|
||||
}
|
||||
|
||||
/// A configuration for a window which determines the corresponding implementation of a
|
||||
/// [`MapWindow`] and is able to create it.
|
||||
pub trait MapWindowConfig: 'static {
|
||||
type MapWindow: MapWindow<MapWindowConfig = Self>;
|
||||
type MapWindow: MapWindow;
|
||||
|
||||
fn create(&self) -> Self::MapWindow;
|
||||
}
|
||||
|
||||
pub trait Runnable<MWC, SM, HC>
|
||||
/// 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<MWC, SM, HC>
|
||||
where
|
||||
MWC: MapWindowConfig,
|
||||
SM: ScheduleMethod,
|
||||
HC: HTTPClient,
|
||||
HC: HttpClient,
|
||||
{
|
||||
fn run(self, map_state: MapSchedule<MWC, SM, HC>, max_frames: Option<u64>);
|
||||
fn run(self, map_schedule: InteractiveMapSchedule<MWC, SM, HC>, max_frames: Option<u64>);
|
||||
}
|
||||
|
||||
/// Window size with a width and an height in pixels.
|
||||
|
||||
@ -35,3 +35,6 @@ wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
console_log = { version = "0.2", features = ["color"] }
|
||||
tracing-wasm = { version = "0.2", optional = true } # FIXME: Low quality dependency
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
7
web/demo/package-lock.json
generated
7
web/demo/package-lock.json
generated
@ -28,7 +28,6 @@
|
||||
"../lib": {
|
||||
"name": "maplibre-rs",
|
||||
"version": "0.0.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"spectorjs": "^0.9.27",
|
||||
@ -36,10 +35,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chialab/esbuild-plugin-env": "^0.15.3",
|
||||
"@chialab/esbuild-plugin-meta-url": "^0.15.15",
|
||||
"@chialab/esbuild-plugin-meta-url": "^0.15.28",
|
||||
"esbuild": "^0.14.38",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
"patch-package": "^6.4.7",
|
||||
"ts-loader": "^9.2.8",
|
||||
"typescript": "^4.5.4",
|
||||
"wasm-pack": "^0.10.2"
|
||||
@ -5892,10 +5890,9 @@
|
||||
"version": "file:../lib",
|
||||
"requires": {
|
||||
"@chialab/esbuild-plugin-env": "^0.15.3",
|
||||
"@chialab/esbuild-plugin-meta-url": "^0.15.15",
|
||||
"@chialab/esbuild-plugin-meta-url": "^0.15.28",
|
||||
"esbuild": "^0.14.38",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
"patch-package": "^6.4.7",
|
||||
"spectorjs": "^0.9.27",
|
||||
"ts-loader": "^9.2.8",
|
||||
"typescript": "^4.5.4",
|
||||
|
||||
627
web/lib/package-lock.json
generated
627
web/lib/package-lock.json
generated
@ -7,7 +7,6 @@
|
||||
"": {
|
||||
"name": "maplibre-rs",
|
||||
"version": "0.0.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"spectorjs": "^0.9.27",
|
||||
@ -18,7 +17,6 @@
|
||||
"@chialab/esbuild-plugin-meta-url": "^0.15.28",
|
||||
"esbuild": "^0.14.38",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
"patch-package": "^6.4.7",
|
||||
"ts-loader": "^9.2.8",
|
||||
"typescript": "^4.5.4",
|
||||
"wasm-pack": "^0.10.2"
|
||||
@ -288,12 +286,6 @@
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.7.0",
|
||||
"dev": true,
|
||||
@ -315,18 +307,6 @@
|
||||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"dev": true,
|
||||
@ -424,20 +404,6 @@
|
||||
"license": "CC-BY-4.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
@ -455,27 +421,6 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ci-info": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
|
||||
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/commondir": {
|
||||
"version": "1.0.1",
|
||||
"dev": true,
|
||||
@ -486,31 +431,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"nice-try": "^1.0.4",
|
||||
"path-key": "^2.0.1",
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn/node_modules/semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
@ -614,15 +534,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
@ -726,15 +637,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-yarn-workspace-root": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"dev": true,
|
||||
@ -754,20 +656,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6 <7 || >=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
@ -814,15 +702,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"dev": true,
|
||||
@ -837,33 +716,6 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/is-ci": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
|
||||
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ci-info": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"is-ci": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"is-docker": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"dev": true,
|
||||
@ -872,24 +724,6 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"dev": true,
|
||||
@ -934,24 +768,6 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/klaw-sync": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.1.11"
|
||||
}
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
@ -1055,12 +871,6 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "3.1.6",
|
||||
"dev": true,
|
||||
@ -1101,12 +911,6 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/nice-try": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.3",
|
||||
"dev": true,
|
||||
@ -1121,31 +925,6 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/open": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-docker": "^2.0.0",
|
||||
"is-wsl": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"dev": true,
|
||||
@ -1179,54 +958,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package": {
|
||||
"version": "6.4.7",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.4.7.tgz",
|
||||
"integrity": "sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"chalk": "^2.4.2",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"is-ci": "^2.0.0",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"open": "^7.4.2",
|
||||
"rimraf": "^2.6.3",
|
||||
"semver": "^5.6.0",
|
||||
"slash": "^2.0.0",
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"bin": {
|
||||
"patch-package": "index.js"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">5"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
@ -1243,15 +974,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
@ -1355,36 +1077,6 @@
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"dev": true,
|
||||
@ -1408,18 +1100,6 @@
|
||||
"version": "0.9.27",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"dev": true,
|
||||
@ -1560,18 +1240,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"dev": true,
|
||||
@ -1677,15 +1345,6 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"dev": true,
|
||||
@ -1829,18 +1488,6 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"which": "bin/which"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"dev": true,
|
||||
@ -2076,12 +1723,6 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
|
||||
"integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.7.0",
|
||||
"dev": true,
|
||||
@ -2093,15 +1734,6 @@
|
||||
"peer": true,
|
||||
"requires": {}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"dev": true,
|
||||
@ -2159,17 +1791,6 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "2.0.0",
|
||||
"dev": true
|
||||
@ -2179,27 +1800,6 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"ci-info": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
|
||||
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
"dev": true
|
||||
@ -2208,27 +1808,6 @@
|
||||
"version": "0.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nice-try": "^1.0.4",
|
||||
"path-key": "^2.0.1",
|
||||
"semver": "^5.5.0",
|
||||
"shebang-command": "^1.2.0",
|
||||
"which": "^1.2.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
@ -2297,12 +1876,6 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
@ -2371,30 +1944,10 @@
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"find-yarn-workspace-root": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
|
||||
"integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"micromatch": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"dev": true
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
@ -2427,12 +1980,6 @@
|
||||
"version": "4.2.10",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"dev": true,
|
||||
@ -2445,40 +1992,10 @@
|
||||
"version": "2.0.4",
|
||||
"dev": true
|
||||
},
|
||||
"is-ci": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
|
||||
"integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ci-info": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-docker": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
|
||||
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"dev": true
|
||||
},
|
||||
"is-wsl": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
|
||||
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-docker": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
|
||||
"dev": true
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "27.5.1",
|
||||
"dev": true,
|
||||
@ -2509,24 +2026,6 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"klaw-sync": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
|
||||
"integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.11"
|
||||
}
|
||||
},
|
||||
"loader-runner": {
|
||||
"version": "4.3.0",
|
||||
"dev": true,
|
||||
@ -2592,12 +2091,6 @@
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "3.1.6",
|
||||
"dev": true,
|
||||
@ -2622,12 +2115,6 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"nice-try": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "2.0.3",
|
||||
"dev": true,
|
||||
@ -2640,22 +2127,6 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"open": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
|
||||
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-docker": "^2.0.0",
|
||||
"is-wsl": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.3.0",
|
||||
"dev": true,
|
||||
@ -2674,44 +2145,6 @@
|
||||
"version": "2.2.0",
|
||||
"dev": true
|
||||
},
|
||||
"patch-package": {
|
||||
"version": "6.4.7",
|
||||
"resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.4.7.tgz",
|
||||
"integrity": "sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"chalk": "^2.4.2",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"find-yarn-workspace-root": "^2.0.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"is-ci": "^2.0.0",
|
||||
"klaw-sync": "^6.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"open": "^7.4.2",
|
||||
"rimraf": "^2.6.3",
|
||||
"semver": "^5.6.0",
|
||||
"slash": "^2.0.0",
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"dependencies": {
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "4.0.0",
|
||||
"dev": true
|
||||
@ -2720,12 +2153,6 @@
|
||||
"version": "1.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"path-key": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
|
||||
"dev": true
|
||||
},
|
||||
"picocolors": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
@ -2782,27 +2209,6 @@
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
|
||||
"dev": true
|
||||
},
|
||||
"slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"dev": true,
|
||||
@ -2820,15 +2226,6 @@
|
||||
"spectorjs": {
|
||||
"version": "0.9.27"
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tapable": {
|
||||
"version": "2.2.1",
|
||||
"dev": true
|
||||
@ -2914,15 +2311,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"dev": true,
|
||||
@ -2983,12 +2371,6 @@
|
||||
"version": "4.6.3",
|
||||
"dev": true
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.4.1",
|
||||
"dev": true,
|
||||
@ -3086,15 +2468,6 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"which": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"dev": true
|
||||
|
||||
@ -65,3 +65,15 @@ pub async fn run(scheduler_ptr: *mut Scheduler<WebWorkerPoolScheduleMethod>) {
|
||||
|
||||
// std::mem::forget(scheduler);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// See https://rustwasm.github.io/wasm-bindgen/wasm-bindgen-test/browsers.html
|
||||
mod tests {
|
||||
use wasm_bindgen_test::*;
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
assert_eq!(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use js_sys::{ArrayBuffer, Uint8Array};
|
||||
use maplibre::io::source_client::HTTPClient;
|
||||
use maplibre::io::source_client::HttpClient;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
@ -61,7 +61,7 @@ impl Clone for WHATWGFetchHttpClient {
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl HTTPClient for WHATWGFetchHttpClient {
|
||||
impl HttpClient for WHATWGFetchHttpClient {
|
||||
async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error> {
|
||||
self.fetch_bytes(url)
|
||||
.await
|
||||
|
||||
@ -5,9 +5,9 @@ use wasm_bindgen::prelude::*;
|
||||
use maplibre::coords::TileCoords;
|
||||
|
||||
use maplibre::io::scheduler::Scheduler;
|
||||
use maplibre::io::shared_thread_state::SharedThreadState;
|
||||
|
||||
use maplibre::io::TileRequestID;
|
||||
use maplibre::stages::SharedThreadState;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
|
||||
@ -8,7 +8,6 @@ use web_sys::Worker;
|
||||
|
||||
use maplibre::error::Error;
|
||||
use maplibre::io::scheduler::ScheduleMethod;
|
||||
use maplibre::io::shared_thread_state::SharedThreadState;
|
||||
|
||||
use super::pool::WorkerPool;
|
||||
|
||||
@ -35,19 +34,17 @@ impl WebWorkerPoolScheduleMethod {
|
||||
}
|
||||
|
||||
impl ScheduleMethod for WebWorkerPoolScheduleMethod {
|
||||
fn schedule(
|
||||
fn schedule<T>(
|
||||
&self,
|
||||
shared_thread_state: SharedThreadState,
|
||||
future_factory: Box<
|
||||
(dyn (FnOnce(SharedThreadState) -> Pin<Box<dyn Future<Output = ()> + 'static>>)
|
||||
+ Send
|
||||
+ 'static),
|
||||
>,
|
||||
) -> Result<(), Error> {
|
||||
future_factory: impl (FnOnce() -> T) + Send + 'static,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
T: Future<Output = ()> + 'static,
|
||||
{
|
||||
self.pool
|
||||
.run(move || {
|
||||
wasm_bindgen_futures::future_to_promise(async move {
|
||||
future_factory(shared_thread_state).await;
|
||||
future_factory().await;
|
||||
Ok(JsValue::undefined())
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user