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:
Max Ammann 2022-06-01 19:01:05 +02:00 committed by GitHub
commit 8248d4b4d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 1921 additions and 1414 deletions

View File

@ -7,4 +7,5 @@ rustflags = [
# Enables the possibility to import memory into wasm. # Enables the possibility to import memory into wasm.
# Without --shared-memory it is not possible to use shared WebAssembly.Memory. # Without --shared-memory it is not possible to use shared WebAssembly.Memory.
"-C", "link-args=--shared-memory --import-memory", "-C", "link-args=--shared-memory --import-memory",
] ]
runner = 'wasm-bindgen-test-runner'

View File

@ -12,6 +12,7 @@ runs:
- name: Build - name: Build
shell: bash shell: bash
run: just build-android run: just build-android
# TODO: Additional clippy checks for different targets
- name: Check x86_64 - name: Check x86_64
shell: bash shell: bash
run: | run: |
@ -24,3 +25,8 @@ runs:
env "AR_aarch64-linux-android=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" \ 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" \ 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 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

View File

@ -22,10 +22,14 @@ runs:
- name: Check x86_64 darwin - name: Check x86_64 darwin
shell: bash shell: bash
run: just check apple x86_64-apple-darwin run: just check apple x86_64-apple-darwin
- name: Check aarch64 darwin - name: Check x86_64 darwin
shell: bash shell: bash
run: just check apple aarch64-apple-darwin # TODO: Additional clippy checks for different targets (iOS)
# TODO: Additional clippy checks for 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 - name: Build Example
shell: bash shell: bash
run: cd apple/xcode && xcodebuild -scheme "example (iOS)" -arch arm64 -sdk iphoneos build CODE_SIGNING_ALLOWED=NO run: cd apple/xcode && xcodebuild -scheme "example (iOS)" -arch arm64 -sdk iphoneos build CODE_SIGNING_ALLOWED=NO

View File

@ -15,12 +15,12 @@ runs:
- name: Build - name: Build
shell: bash shell: bash
run: cargo build -p maplibre-demo run: cargo build -p maplibre-demo
- name: Test
shell: bash
run: just test maplibre-demo x86_64-unknown-linux-gnu
- name: Check - name: Check
shell: bash shell: bash
run: just check maplibre-demo x86_64-unknown-linux-gnu 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 - uses: actions/upload-artifact@v2
with: with:
name: maplibre-rs name: maplibre-rs

View File

@ -19,6 +19,12 @@ runs:
- name: Build - name: Build
shell: bash shell: bash
run: cd apple/xcode && xcodebuild -scheme "example (macOS)" build CODE_SIGNING_ALLOWED=NO MACOSX_DEPLOYMENT_TARGET=10.9 -derivedDataPath build 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 - uses: actions/upload-artifact@v3
with: with:
name: maplibre-x86_64-apple-darwin-demo name: maplibre-x86_64-apple-darwin-demo

View File

@ -22,7 +22,10 @@ runs:
- name: Build - name: Build
shell: bash shell: bash
run: cargo build -p maplibre-demo --release --target x86_64-pc-windows-msvc 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 shell: bash
run: just test maplibre-demo x86_64-pc-windows-msvc run: just test maplibre-demo x86_64-pc-windows-msvc
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3

18
.github/actions/tests/action.yml vendored Normal file
View 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

View File

@ -18,3 +18,8 @@ runs:
- name: Check - name: Check
shell: bash 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 "web-webgl"

View File

@ -17,4 +17,9 @@ runs:
run: just web-demo build run: just web-demo build
- name: Check - name: Check
shell: bash 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 ""

View File

@ -17,6 +17,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: ./.github/actions/benchmarks - uses: ./.github/actions/benchmarks
run-tests:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/tests
build-android: build-android:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
@ -57,7 +62,7 @@ jobs:
source: docs/book/. source: docs/book/.
destination: docs destination: docs
key: ${{ secrets.SSH_KEY_MAXAMMANN_ORG }} key: ${{ secrets.SSH_KEY_MAXAMMANN_ORG }}
build-ios: build-apple:
runs-on: macos-11 runs-on: macos-11
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -17,6 +17,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: ./.github/actions/benchmarks - uses: ./.github/actions/benchmarks
run-tests:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/tests
build-android: build-android:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
@ -37,7 +42,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: ./.github/actions/docs - uses: ./.github/actions/docs
build-ios: build-apple:
runs-on: macos-11 runs-on: macos-11
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

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

@ -20,9 +20,10 @@
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/docs/book" /> <excludeFolder url="file://$MODULE_DIR$/docs/book" />
<excludeFolder url="file://$MODULE_DIR$/web/lib/.parcel-cache" /> <excludeFolder url="file://$MODULE_DIR$/web/lib/.parcel-cache" />
<excludeFolder url="file://$MODULE_DIR$/web/lib/src/wasm-pack" />
<excludeFolder url="file://$MODULE_DIR$/maplibre-cache" /> <excludeFolder url="file://$MODULE_DIR$/maplibre-cache" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

19
.idea/runConfigurations/Run_Tests.xml generated Normal file
View 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>

View File

@ -45,12 +45,18 @@ web-lib TARGET: nightly-toolchain (web-install "lib")
web-demo TARGET: (web-install "demo") web-demo TARGET: (web-install "demo")
cd web/demo && npm run {{TARGET}} 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: #profile-bench:
# cargo flamegraph --bench render -- --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 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 # language=bash
print-android-env: print-android-env:
#!/usr/bin/env bash #!/usr/bin/env bash

View File

@ -14,7 +14,7 @@ trace = ["maplibre/trace", "tracing-subscriber", "tracing-tracy", "tracy-client"
[dependencies] [dependencies]
env_logger = "0.9" 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" } maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
tracing = { version = "0.1" } tracing = { version = "0.1" }

View File

@ -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::http_client::ReqwestHttpClient;
use maplibre::platform::run_multithreaded; use maplibre::platform::run_multithreaded;
use maplibre::platform::schedule_method::TokioScheduleMethod; 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::MapBuilder;
use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow}; use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
use std::any::Any;
use std::collections::HashSet;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
fn enable_tracing() { fn enable_tracing() {
@ -13,6 +29,27 @@ fn enable_tracing() {
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); 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() { fn run_in_window() {
run_multithreaded(async { 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() { fn main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
enable_tracing(); enable_tracing();
run_in_window() //run_headless();
run_in_window();
} }

View File

@ -1,14 +1,14 @@
use instant::Instant; use instant::Instant;
use maplibre::error::Error; use maplibre::error::Error;
use maplibre::io::scheduler::ScheduleMethod; use maplibre::io::scheduler::ScheduleMethod;
use maplibre::io::source_client::HTTPClient; use maplibre::io::source_client::HttpClient;
use std::borrow::BorrowMut; use std::borrow::BorrowMut;
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode, WindowEvent}; use winit::event::{ElementState, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::ControlFlow; use winit::event_loop::ControlFlow;
use crate::input::{InputController, UpdateState}; use crate::input::{InputController, UpdateState};
use maplibre::map_schedule::MapSchedule; use maplibre::map_schedule::InteractiveMapSchedule;
use maplibre::window::{MapWindow, MapWindowConfig, Runnable}; use maplibre::window::{EventLoop, HeadedMapWindow, MapWindow, MapWindowConfig};
use winit::event::Event; use winit::event::Event;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@ -47,10 +47,6 @@ impl WinitMapWindowConfig {
} }
} }
impl MapWindowConfig for WinitMapWindowConfig {
type MapWindow = WinitMapWindow;
}
pub struct WinitMapWindow { pub struct WinitMapWindow {
window: WinitWindow, window: WinitWindow,
event_loop: Option<WinitEventLoop>, event_loop: Option<WinitEventLoop>,
@ -69,13 +65,17 @@ impl WinitMapWindow {
///* Input (Mouse/Keyboard) ///* Input (Mouse/Keyboard)
///* Platform Events like suspend/resume ///* Platform Events like suspend/resume
///* Render a new frame ///* 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 where
MWC: MapWindowConfig<MapWindow = WinitMapWindow>, MWC: MapWindowConfig<MapWindow = WinitMapWindow>,
SM: ScheduleMethod, 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 last_render_time = Instant::now();
let mut current_frame: u64 = 0; let mut current_frame: u64 = 0;
@ -85,13 +85,13 @@ where
.unwrap() .unwrap()
.run(move |event, _, control_flow| { .run(move |event, _, control_flow| {
#[cfg(target_os = "android")] #[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::runtime::Handle;
use tokio::task; use tokio::task;
let state = task::block_in_place(|| { let state = task::block_in_place(|| {
Handle::current().block_on(async { Handle::current().block_on(async {
map_state.late_init().await; map_schedule.late_init().await;
}) })
}); });
return; return;
@ -122,10 +122,10 @@ where
.. ..
} => *control_flow = ControlFlow::Exit, } => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => { 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, .. } => { 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; let dt = now - last_render_time;
last_render_time = now; last_render_time = now;
{ input_controller.update_state(map_schedule.view_state_mut(), dt);
input_controller.update_state(map_state.view_state_mut(), dt);
}
match map_state.update_and_redraw() { match map_schedule.update_and_redraw() {
Ok(_) => {} Ok(_) => {}
Err(Error::Render(e)) => { Err(Error::Render(e)) => {
eprintln!("{}", e); eprintln!("{}", e);
@ -161,10 +159,10 @@ where
} }
} }
Event::Suspended => { Event::Suspended => {
map_state.suspend(); map_schedule.suspend();
} }
Event::Resumed => { Event::Resumed => {
map_state.resume(&self); map_schedule.resume(&self);
} }
Event::MainEventsCleared => { Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually // RedrawRequested will only trigger once, unless we manually

View File

@ -10,26 +10,9 @@ use super::WinitMapWindow;
use super::WinitWindow; use super::WinitWindow;
use super::WinitMapWindowConfig; use super::WinitMapWindowConfig;
use maplibre::window::{MapWindow, WindowSize}; use maplibre::window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};
impl MapWindow for WinitMapWindow { 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 { fn size(&self) -> WindowSize {
let size = self.window.inner_size(); let size = self.window.inner_size();
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@ -43,8 +26,28 @@ impl MapWindow for WinitMapWindow {
WindowSize::new(size.width, size.height).expect("failed to get window dimensions."); WindowSize::new(size.width, size.height).expect("failed to get window dimensions.");
window_size window_size
} }
}
impl HeadedMapWindow for WinitMapWindow {
type RawWindow = WinitWindow;
fn inner(&self) -> &Self::Window { fn inner(&self) -> &Self::RawWindow {
&self.window &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),
}
}
}

View File

@ -4,38 +4,43 @@ use super::WinitEventLoop;
use super::WinitMapWindow; use super::WinitMapWindow;
use super::WinitMapWindowConfig; use super::WinitMapWindowConfig;
use super::WinitWindow; use super::WinitWindow;
use maplibre::window::HeadedMapWindow;
use maplibre::window::MapWindowConfig;
use maplibre::window::{MapWindow, WindowSize}; use maplibre::window::{MapWindow, WindowSize};
use winit::platform::web::WindowBuilderExtWebSys; use winit::platform::web::WindowBuilderExtWebSys;
impl MapWindow for WinitMapWindow { impl MapWindowConfig for WinitMapWindowConfig {
type EventLoop = WinitEventLoop; type MapWindow = WinitMapWindow;
type Window = WinitWindow;
type MapWindowConfig = WinitMapWindowConfig;
fn create(map_window_config: &Self::MapWindowConfig) -> Self { fn create(&self) -> Self::MapWindow {
let event_loop = WinitEventLoop::new(); let event_loop = WinitEventLoop::new();
let window: winit::window::Window = WindowBuilder::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) .build(&event_loop)
.unwrap(); .unwrap();
let size = get_body_size().unwrap(); let size = get_body_size().unwrap();
window.set_inner_size(size); window.set_inner_size(size);
Self { Self::MapWindow {
window, window,
event_loop: Some(event_loop), event_loop: Some(event_loop),
} }
} }
}
impl MapWindow for WinitMapWindow {
fn size(&self) -> WindowSize { fn size(&self) -> WindowSize {
let size = self.window.inner_size(); let size = self.window.inner_size();
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.") WindowSize::new(size.width, size.height).expect("failed to get window dimensions.")
} }
}
impl HeadedMapWindow for WinitMapWindow {
type RawWindow = WinitWindow;
fn inner(&self) -> &Self::Window { fn inner(&self) -> &Self::RawWindow {
&self.window &self.window
} }
} }

View File

@ -14,6 +14,7 @@ web-webgl = ["wgpu/webgl"]
trace = [ "tracing-subscriber", "tracing-tracy", "tracy-client"] trace = [ "tracing-subscriber", "tracing-tracy", "tracy-client"]
no-thread-safe-futures = [] no-thread-safe-futures = []
embed-static-tiles = ["maplibre-build-tools/sqlite"] 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] [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" async-trait = "0.1"
instant = { version = "0.1", features = ["wasm-bindgen"] } # FIXME: Untrusted dependency instant = { version = "0.1", features = ["wasm-bindgen"] } # FIXME: Untrusted dependency
raw-window-handle = "0.4" # Tracing
tracing = { version = "0.1" } tracing = { version = "0.1" }
tracing-subscriber = { version = "0.3", optional = true } tracing-subscriber = { version = "0.3", optional = true }
# Maths
cgmath = "0.18" cgmath = "0.18"
# Geo
geo = { version = "0.19" } geo = { version = "0.19" }
geo-types = { version = "0.7", features = ["use-rstar_0_9"] } geo-types = { version = "0.7", features = ["use-rstar_0_9"] }
rstar = { version = "0.9" } rstar = { version = "0.9" }
prost = "0.10.1" prost = "0.10.1"
geozero = { version = "0.9.4", default-features = false, features = ["with-mvt", "with-geo"]} geozero = { version = "0.9.4", default-features = false, features = ["with-mvt", "with-geo"]}
tile-grid = "0.3" tile-grid = "0.3"
# Rendering # Rendering
wgpu = { version = "0.12" } wgpu = { version = "0.12" }
lyon = { version = "0.17", features = [] } lyon = { version = "0.17", features = [] }
raw-window-handle = "0.4"
# cached = "0.32" # cached = "0.32"
@ -61,16 +63,24 @@ log = "0.4"
bytemuck = "1.2.0" bytemuck = "1.2.0"
bytemuck_derive = "1.0" bytemuck_derive = "1.0"
# Static tiles inclusion
include_dir = "0.7.2" include_dir = "0.7.2"
# JSON
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
# Colors
csscolorparser = { version = "0.5", features = ["serde", "cint"]} csscolorparser = { version = "0.5", features = ["serde", "cint"]}
cint = "0.2" cint = "0.2"
# Required by bevy renderer
thiserror = "1" thiserror = "1"
downcast-rs = "1.2" downcast-rs = "1.2"
smallvec = "1.8" smallvec = "1.8"
# Headless
png = { version = "0.17", optional = true }
[build-dependencies] [build-dependencies]
maplibre-build-tools = { path = "../maplibre-build-tools", version = "0.1.0", features = ["sqlite"] } maplibre-build-tools = { path = "../maplibre-build-tools", version = "0.1.0", features = ["sqlite"] }

View File

@ -1,7 +1,5 @@
use crate::coords::{Zoom, ZoomLevel, TILE_SIZE}; use crate::coords::{Zoom, ZoomLevel, TILE_SIZE};
use crate::io::shared_thread_state::SharedThreadState; use crate::io::tile_repository::TileRepository;
use crate::io::tile_cache::TileCache;
use crate::io::TessellateMessage;
use crate::render::camera::{Camera, Perspective, ViewProjection}; use crate::render::camera::{Camera, Perspective, ViewProjection};
use crate::util::ChangeObserver; use crate::util::ChangeObserver;
use crate::{Renderer, ScheduleMethod, Style, WindowSize}; use crate::{Renderer, ScheduleMethod, Style, WindowSize};
@ -57,14 +55,11 @@ impl ViewState {
} }
} }
/// Stores the context of the map.
pub struct MapContext { pub struct MapContext {
pub view_state: ViewState, pub view_state: ViewState,
pub style: Style, pub style: Style,
pub tile_cache: TileCache, pub tile_repository: TileRepository,
pub renderer: Renderer, pub renderer: Renderer,
pub scheduler: Box<dyn ScheduleMethod>,
pub message_receiver: mpsc::Receiver<TessellateMessage>,
pub shared_thread_state: SharedThreadState,
} }

View File

@ -1,96 +1,21 @@
//! Handles IO related processing as well as multithreading. //! Handles IO related processing as well as multithreading.
use crate::coords::WorldTileCoords; use crate::coords::WorldTileCoords;
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
use crate::render::ShaderVertex; use crate::render::ShaderVertex;
use geozero::mvt::tile; use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt; use std::fmt;
pub mod scheduler; pub mod scheduler;
pub mod source_client; pub mod source_client;
pub mod static_tile_fetcher; pub mod static_tile_fetcher;
pub mod geometry_index; pub mod geometry_index;
pub mod shared_thread_state; pub mod pipeline;
pub mod tile_cache; pub mod tile_pipelines;
pub mod tile_repository;
pub mod tile_request_state; pub mod tile_request_state;
/// Contains a `Tile` if the fetch was successful otherwise `Unavailable`. pub use geozero::mvt::tile::Layer as RawLayer;
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,
}
}
}
/// A request for a tile at the given coordinates and in the given layers. /// A request for a tile at the given coordinates and in the given layers.
#[derive(Clone)] #[derive(Clone)]

243
maplibre/src/io/pipeline.rs Normal file
View 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);
}
}

View File

@ -5,8 +5,6 @@ use std::pin::Pin;
use crate::error::Error; use crate::error::Error;
use crate::io::shared_thread_state::SharedThreadState;
/// Async/await scheduler. /// Async/await scheduler.
pub struct Scheduler<SM> pub struct Scheduler<SM>
where where
@ -26,30 +24,23 @@ where
pub fn schedule_method(&self) -> &SM { pub fn schedule_method(&self) -> &SM {
&self.schedule_method &self.schedule_method
} }
pub fn take(self) -> SM {
self.schedule_method
}
} }
/// Can schedule a task from a future factory and a shared state. /// 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 { pub trait ScheduleMethod: 'static {
#[cfg(not(feature = "no-thread-safe-futures"))] #[cfg(not(feature = "no-thread-safe-futures"))]
fn schedule( fn schedule<T>(
&self, &self,
shared_thread_state: SharedThreadState, future_factory: impl (FnOnce() -> T) + Send + 'static,
future_factory: Box< ) -> Result<(), Error>
(dyn (FnOnce(SharedThreadState) -> Pin<Box<dyn Future<Output = ()> + Send>>) + Send), where
>, T: Future<Output = ()> + Send + 'static;
) -> Result<(), Error>;
#[cfg(feature = "no-thread-safe-futures")] #[cfg(feature = "no-thread-safe-futures")]
fn schedule( fn schedule<T>(
&self, &self,
shared_thread_state: SharedThreadState, future_factory: impl (FnOnce() -> T) + Send + 'static,
future_factory: Box< ) -> Result<(), Error>
(dyn (FnOnce(SharedThreadState) -> Pin<Box<dyn Future<Output = ()>>>) + Send), where
>, T: Future<Output = ()> + 'static;
) -> Result<(), Error>;
} }

View File

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

View File

@ -16,7 +16,7 @@ pub type HTTPClientFactory<HC> = dyn Fn() -> HC;
/// the future "no-thread-safe-futures". Tokio futures are thread-safe. /// the future "no-thread-safe-futures". Tokio futures are thread-safe.
#[cfg_attr(feature = "no-thread-safe-futures", async_trait(?Send))] #[cfg_attr(feature = "no-thread-safe-futures", async_trait(?Send))]
#[cfg_attr(not(feature = "no-thread-safe-futures"), async_trait)] #[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>; async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error>;
} }
@ -25,7 +25,7 @@ pub trait HTTPClient: Clone + Sync + Send + 'static {
#[derive(Clone)] #[derive(Clone)]
pub struct HttpSourceClient<HC> pub struct HttpSourceClient<HC>
where where
HC: HTTPClient, HC: HttpClient,
{ {
inner_client: HC, inner_client: HC,
} }
@ -35,7 +35,7 @@ where
#[derive(Clone)] #[derive(Clone)]
pub enum SourceClient<HC> pub enum SourceClient<HC>
where where
HC: HTTPClient, HC: HttpClient,
{ {
Http(HttpSourceClient<HC>), Http(HttpSourceClient<HC>),
Mbtiles { Mbtiles {
@ -45,7 +45,7 @@ where
impl<HC> SourceClient<HC> impl<HC> SourceClient<HC>
where where
HC: HTTPClient, HC: HttpClient,
{ {
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> { pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
match self { match self {
@ -57,7 +57,7 @@ where
impl<HC> HttpSourceClient<HC> impl<HC> HttpSourceClient<HC>
where where
HC: HTTPClient, HC: HttpClient,
{ {
pub fn new(http_client: HC) -> Self { pub fn new(http_client: HC) -> Self {
Self { Self {

View 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,
);
}
}

View File

@ -1,18 +1,49 @@
//! Tile cache. //! Tile cache.
use crate::coords::{Quadkey, WorldTileCoords}; use crate::coords::{Quadkey, WorldTileCoords};
use crate::render::ShaderVertex;
use crate::io::LayerTessellateMessage; use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
use geozero::mvt::tile;
use std::collections::{btree_map, BTreeMap, HashSet}; use std::collections::{btree_map, BTreeMap, HashSet};
/// Stores the multiple [crate::io::LayerTessellateMessage] of a cached tile. /// A layer which is stored for future use.
pub struct CachedTile { pub enum StoredLayer {
layers: Vec<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 CachedTile { impl StoredLayer {
pub fn new(first_layer: LayerTessellateMessage) -> Self { 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 { Self {
layers: vec![first_layer], 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. /// Stores and provides access to a quad tree of cached tiles with world tile coords.
#[derive(Default)] #[derive(Default)]
pub struct TileCache { pub struct TileRepository {
cache: BTreeMap<Quadkey, CachedTile>, tree: BTreeMap<Quadkey, StoredTile>,
} }
impl TileCache { impl TileRepository {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
cache: BTreeMap::new(), tree: BTreeMap::new(),
} }
} }
/// Inserts a tessellated layer into the quad tree at its world tile coords. /// Inserts a tessellated layer into the quad tree at its world tile coords.
/// If the space is vacant, the tessellated layer is inserted into a new /// If the space is vacant, the tessellated layer is inserted into a new
/// [crate::io::tile_cache::CachedTile]. /// [crate::io::tile_repository::CachedTile].
/// If the space is occupied, the tessellated layer is added to the current /// If the space is occupied, the tessellated layer is added to the current
/// [crate::io::tile_cache::CachedTile]. /// [crate::io::tile_repository::CachedTile].
pub fn put_tessellated_layer(&mut self, message: LayerTessellateMessage) { pub fn put_tessellated_layer(&mut self, layer: StoredLayer) {
if let Some(entry) = message if let Some(entry) = layer
.get_coords() .get_coords()
.build_quad_key() .build_quad_key()
.map(|key| self.cache.entry(key)) .map(|key| self.tree.entry(key))
{ {
match entry { match entry {
btree_map::Entry::Vacant(entry) => { btree_map::Entry::Vacant(entry) => {
entry.insert(CachedTile::new(message)); entry.insert(StoredTile::new(layer));
} }
btree_map::Entry::Occupied(mut entry) => { 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( pub fn iter_tessellated_layers_at(
&self, &self,
coords: &WorldTileCoords, coords: &WorldTileCoords,
) -> Option<impl Iterator<Item = &LayerTessellateMessage> + '_> { ) -> Option<impl Iterator<Item = &StoredLayer> + '_> {
coords coords
.build_quad_key() .build_quad_key()
.and_then(|key| self.cache.get(&key)) .and_then(|key| self.tree.get(&key))
.map(|results| results.layers.iter()) .map(|results| results.layers.iter())
} }
@ -73,7 +104,7 @@ impl TileCache {
coords: &WorldTileCoords, coords: &WorldTileCoords,
layers: &mut HashSet<String>, 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 let tessellated_set: HashSet<String> = cached_tile
.layers .layers
.iter() .iter()
@ -86,7 +117,7 @@ impl TileCache {
/// Checks if a layer is missing from the given layers set at the given coords. /// 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 { 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 let tessellated_set: HashSet<&str> = cached_tile
.layers .layers
.iter() .iter()

View File

@ -17,12 +17,12 @@
//! ``` //! ```
use crate::io::scheduler::{ScheduleMethod, Scheduler}; use crate::io::scheduler::{ScheduleMethod, Scheduler};
use crate::io::source_client::HTTPClient; use crate::io::source_client::HttpClient;
use crate::map_schedule::MapSchedule; use crate::map_schedule::{InteractiveMapSchedule, SimpleMapSchedule};
use crate::render::settings::{RendererSettings, WgpuSettings}; use crate::render::settings::{RendererSettings, WgpuSettings};
use crate::render::{RenderState, Renderer}; use crate::render::{RenderState, Renderer};
use crate::style::Style; use crate::style::Style;
use crate::window::{MapWindow, MapWindowConfig, Runnable, WindowSize}; use crate::window::{EventLoop, HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};
pub mod context; pub mod context;
pub mod coords; pub mod coords;
@ -37,43 +37,51 @@ pub mod style;
pub mod window; pub mod window;
// Exposed because of doc-strings // Exposed because of doc-strings
pub mod schedule; pub mod schedule;
// Exposed because of SharedThreadState
pub mod stages;
// Used for benchmarking // Used for benchmarking
pub mod benchmarking; pub mod benchmarking;
// Internal modules // Internal modules
pub(crate) mod stages;
pub(crate) mod tessellation; pub(crate) mod tessellation;
pub(crate) mod util; pub mod util;
/// Map's configuration and execution. /// The [`Map`] defines the public interface of the map renderer.
pub struct Map<W, SM, HC> // DO NOT IMPLEMENT INTERNALS ON THIS STRUCT.
pub struct Map<MWC, SM, HC>
where where
W: MapWindow, MWC: MapWindowConfig,
SM: ScheduleMethod, SM: ScheduleMethod,
HC: HTTPClient, HC: HttpClient,
{ {
map_state: MapSchedule<W::MapWindowConfig, SM, HC>, map_schedule: InteractiveMapSchedule<MWC, SM, HC>,
window: W, window: MWC::MapWindow,
} }
impl<W, SM, HC> Map<W, SM, HC> impl<MWC, SM, HC> Map<MWC, SM, HC>
where where
W: MapWindow + Runnable<W::MapWindowConfig, SM, HC>, MWC: MapWindowConfig,
SM: ScheduleMethod, SM: ScheduleMethod,
HC: HTTPClient, HC: HttpClient,
{ {
/// Starts the [`crate::map_state::MapState`] Runnable with the configured event loop. /// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
pub fn run(self) { pub fn run(self)
where
MWC::MapWindow: EventLoop<MWC, SM, HC>,
{
self.run_with_optionally_max_frames(None); 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 /// # Arguments
/// ///
/// * `max_frames` - Maximum number of frames per second. /// * `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)); self.run_with_optionally_max_frames(Some(max_frames));
} }
@ -82,20 +90,49 @@ where
/// # Arguments /// # Arguments
/// ///
/// * `max_frames` - Optional maximum number of frames per second. /// * `max_frames` - Optional maximum number of frames per second.
pub fn run_with_optionally_max_frames(self, max_frames: Option<u64>) { pub fn run_with_optionally_max_frames(self, max_frames: Option<u64>)
self.window.run(self.map_state, max_frames); 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. /// 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> pub struct UninitializedMap<MWC, SM, HC>
where where
MWC: MapWindowConfig, MWC: MapWindowConfig,
SM: ScheduleMethod, SM: ScheduleMethod,
HC: HTTPClient, HC: HttpClient,
{ {
scheduler: Scheduler<SM>, scheduler: Scheduler<SM>,
http_client: HC, http_client: HC,
@ -110,12 +147,16 @@ impl<MWC, SM, HC> UninitializedMap<MWC, SM, HC>
where where
MWC: MapWindowConfig, MWC: MapWindowConfig,
SM: ScheduleMethod, SM: ScheduleMethod,
HC: HTTPClient, HC: HttpClient,
{ {
/// Initializes the whole rendering pipeline for the given configuration. /// Initializes the whole rendering pipeline for the given configuration.
/// Returns the initialized map, ready to be run. /// Returns the initialized map, ready to be run.
pub async fn initialize(self) -> Map<MWC::MapWindow, SM, HC> { pub async fn initialize(self) -> Map<MWC, SM, HC>
let window = MWC::MapWindow::create(&self.map_window_config); where
MWC: MapWindowConfig,
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
let window = self.map_window_config.create();
let window_size = window.size(); let window_size = window.size();
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@ -129,7 +170,7 @@ where
.await .await
.ok(); .ok();
Map { Map {
map_state: MapSchedule::new( map_schedule: InteractiveMapSchedule::new(
self.map_window_config, self.map_window_config,
window_size, window_size,
renderer, renderer,
@ -142,6 +183,30 @@ where
window, 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> pub struct MapBuilder<MWC, SM, HC>
@ -162,7 +227,7 @@ impl<MWC, SM, HC> MapBuilder<MWC, SM, HC>
where where
MWC: MapWindowConfig, MWC: MapWindowConfig,
SM: ScheduleMethod, SM: ScheduleMethod,
HC: HTTPClient, HC: HttpClient,
{ {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {

View File

@ -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::context::{MapContext, ViewState};
use crate::error::Error; use crate::error::Error;
use crate::io::geometry_index::GeometryIndex; use crate::io::geometry_index::GeometryIndex;
use crate::io::scheduler::Scheduler; use crate::io::scheduler::Scheduler;
use crate::io::shared_thread_state::SharedThreadState; use crate::io::source_client::{HttpClient, HttpSourceClient, SourceClient};
use crate::io::source_client::{HTTPClient, HttpSourceClient, SourceClient}; use crate::io::tile_repository::TileRepository;
use crate::io::tile_cache::TileCache;
use crate::io::tile_request_state::TileRequestState; use crate::io::tile_request_state::TileRequestState;
use crate::io::TessellateMessage;
use crate::render::register_render_stages; use crate::render::register_render_stages;
use crate::schedule::{Schedule, Stage}; use crate::schedule::{Schedule, Stage};
use crate::stages::register_stages; use crate::stages::register_stages;
use crate::style::Style; use crate::style::Style;
use crate::{ use crate::{
MapWindow, MapWindowConfig, Renderer, RendererSettings, ScheduleMethod, WgpuSettings, HeadedMapWindow, MapWindow, MapWindowConfig, Renderer, RendererSettings, ScheduleMethod,
WindowSize, WgpuSettings, WindowSize,
}; };
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem; use std::mem;
use std::sync::{mpsc, Arc, Mutex}; use std::sync::{mpsc, Arc, Mutex};
pub struct PrematureMapContext { pub struct PrematureMapContext {
pub view_state: ViewState, view_state: ViewState,
pub style: Style, style: Style,
pub tile_cache: TileCache, tile_repository: TileRepository,
pub scheduler: Box<dyn ScheduleMethod>,
pub message_receiver: mpsc::Receiver<TessellateMessage>,
pub shared_thread_state: SharedThreadState,
wgpu_settings: WgpuSettings, wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings, renderer_settings: RendererSettings,
@ -38,53 +30,43 @@ pub struct PrematureMapContext {
pub enum EventuallyMapContext { pub enum EventuallyMapContext {
Full(MapContext), Full(MapContext),
Premature(PrematureMapContext), Premature(PrematureMapContext),
Empty, _Uninitialized,
} }
impl EventuallyMapContext { impl EventuallyMapContext {
pub fn make_full(&mut self, renderer: Renderer) { pub fn make_full(&mut self, renderer: Renderer) {
let context = mem::replace(self, EventuallyMapContext::Empty); let context = mem::replace(self, EventuallyMapContext::_Uninitialized);
match context { match context {
EventuallyMapContext::Full(_) => {} EventuallyMapContext::Full(_) => {}
EventuallyMapContext::Premature(PrematureMapContext { EventuallyMapContext::Premature(PrematureMapContext {
view_state, view_state,
style, style,
tile_cache, tile_repository,
scheduler, ..
message_receiver,
shared_thread_state,
wgpu_settings,
renderer_settings,
}) => { }) => {
mem::replace( *self = EventuallyMapContext::Full(MapContext {
self, view_state,
EventuallyMapContext::Full(MapContext { style,
view_state, tile_repository,
style, renderer,
tile_cache, });
renderer,
scheduler,
message_receiver,
shared_thread_state,
}),
);
} }
EventuallyMapContext::Empty => {} EventuallyMapContext::_Uninitialized => {}
} }
} }
} }
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing. /// 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 where
MWC: MapWindowConfig, MWC: MapWindowConfig,
SM: ScheduleMethod, SM: ScheduleMethod,
HC: HTTPClient, HC: HttpClient,
{ {
map_window_config: MWC, map_window_config: MWC,
map_context: EventuallyMapContext, pub map_context: EventuallyMapContext,
schedule: Schedule, schedule: Schedule,
@ -94,11 +76,11 @@ where
suspended: bool, suspended: bool,
} }
impl<MWC, SM, HC> MapSchedule<MWC, SM, HC> impl<MWC, SM, HC> InteractiveMapSchedule<MWC, SM, HC>
where where
MWC: MapWindowConfig, MWC: MapWindowConfig,
SM: ScheduleMethod, SM: ScheduleMethod,
HC: HTTPClient, HC: HttpClient,
{ {
pub fn new( pub fn new(
map_window_config: MWC, map_window_config: MWC,
@ -111,42 +93,29 @@ where
renderer_settings: RendererSettings, renderer_settings: RendererSettings,
) -> Self { ) -> Self {
let view_state = ViewState::new(&window_size); let view_state = ViewState::new(&window_size);
let tile_cache = TileCache::new(); let tile_repository = TileRepository::new();
let mut schedule = Schedule::default(); 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 { Self {
map_window_config, map_window_config,
map_context: match renderer { map_context: match renderer {
None => EventuallyMapContext::Premature(PrematureMapContext { None => EventuallyMapContext::Premature(PrematureMapContext {
view_state, view_state,
style, style,
tile_cache, tile_repository,
scheduler,
shared_thread_state,
wgpu_settings, wgpu_settings,
message_receiver,
renderer_settings, renderer_settings,
}), }),
Some(renderer) => EventuallyMapContext::Full(MapContext { Some(renderer) => EventuallyMapContext::Full(MapContext {
view_state, view_state,
style, style,
tile_cache, tile_repository,
renderer, renderer,
scheduler,
shared_thread_state,
message_receiver,
}), }),
}, },
schedule, schedule,
@ -183,13 +152,15 @@ where
self.suspended = true; self.suspended = true;
} }
pub fn resume<MW>(&mut self, window: &MW) pub fn resume(&mut self, window: &MWC::MapWindow)
where where
MW: MapWindow, <MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
{ {
if let EventuallyMapContext::Full(map_context) = &mut self.map_context { if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
let mut renderer = &mut map_context.renderer; 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; 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 { match &self.map_context {
EventuallyMapContext::Full(_) => false, EventuallyMapContext::Full(_) => false,
EventuallyMapContext::Premature(PrematureMapContext { EventuallyMapContext::Premature(PrematureMapContext {
view_state,
style,
tile_cache,
scheduler,
message_receiver,
shared_thread_state,
wgpu_settings, wgpu_settings,
renderer_settings, renderer_settings,
..
}) => { }) => {
let window = MWC::MapWindow::create(&self.map_window_config); let window = self.map_window_config.create();
let renderer = let renderer =
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone()) Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
.await .await
.unwrap(); .unwrap();
&self.map_context.make_full(renderer); self.map_context.make_full(renderer);
true 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
}
}

View File

@ -1,5 +1,5 @@
use crate::error::Error; use crate::error::Error;
use crate::HTTPClient; use crate::HttpClient;
use async_trait::async_trait; use async_trait::async_trait;
use reqwest::{Client, StatusCode}; use reqwest::{Client, StatusCode};
use reqwest_middleware::ClientWithMiddleware; use reqwest_middleware::ClientWithMiddleware;
@ -41,7 +41,7 @@ impl ReqwestHttpClient {
} }
#[async_trait] #[async_trait]
impl HTTPClient for ReqwestHttpClient { impl HttpClient for ReqwestHttpClient {
async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error> { async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error> {
let response = self.client.get(url).send().await?; let response = self.client.get(url).send().await?;
match response.error_for_status() { match response.error_for_status() {

View File

@ -1,5 +1,4 @@
use crate::error::Error; use crate::error::Error;
use crate::io::shared_thread_state::SharedThreadState;
use crate::ScheduleMethod; use crate::ScheduleMethod;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
@ -14,16 +13,11 @@ impl TokioScheduleMethod {
} }
impl ScheduleMethod for TokioScheduleMethod { impl ScheduleMethod for TokioScheduleMethod {
fn schedule( fn schedule<T>(&self, future_factory: impl FnOnce() -> T + Send + 'static) -> Result<(), Error>
&self, where
shared_thread_state: SharedThreadState, T: Future<Output = ()> + Send + 'static,
future_factory: Box< {
(dyn (FnOnce(SharedThreadState) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>) tokio::task::spawn((future_factory)());
+ Send
+ 'static),
>,
) -> Result<(), Error> {
tokio::task::spawn((future_factory)(shared_thread_state));
Ok(()) Ok(())
} }
} }

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

View File

@ -27,7 +27,8 @@ use super::EdgeExistence;
/// ///
/// ## Example /// ## Example
/// Here is a simple render graph example with two nodes connected by a node edge. /// 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::graph::{Node, NodeRunError, RenderContext, RenderGraph, RenderGraphContext};
/// # use maplibre::render::{RenderState}; /// # use maplibre::render::{RenderState};
/// # struct MyNode; /// # struct MyNode;
@ -574,10 +575,8 @@ mod tests {
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError, Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
SlotInfo, SlotInfo,
}; };
use crate::render::{ use crate::render::graph::{RenderContext, SlotType};
graph::{RenderContext, SlotType}, use crate::RenderState;
RenderState,
};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Debug)] #[derive(Debug)]
@ -610,9 +609,9 @@ mod tests {
fn run( fn run(
&self, &self,
_: &mut RenderGraphContext, graph: &mut RenderGraphContext,
_: &mut RenderContext, render_context: &mut RenderContext,
_: &RenderState, state: &RenderState,
) -> Result<(), NodeRunError> { ) -> Result<(), NodeRunError> {
Ok(()) Ok(())
} }
@ -683,9 +682,9 @@ mod tests {
impl Node for MyNode { impl Node for MyNode {
fn run( fn run(
&self, &self,
_: &mut RenderGraphContext, graph: &mut RenderGraphContext,
_: &mut RenderContext, render_context: &mut RenderContext,
_: &RenderState, state: &RenderState,
) -> Result<(), NodeRunError> { ) -> Result<(), NodeRunError> {
Ok(()) Ok(())
} }

View File

@ -7,12 +7,21 @@ use crate::render::graph::{Node, NodeRunError, RenderContext, RenderGraphContext
use crate::render::render_commands::{DrawMasks, DrawTiles}; use crate::render::render_commands::{DrawMasks, DrawTiles};
use crate::render::render_phase::{PhaseItem, RenderCommand}; use crate::render::render_phase::{PhaseItem, RenderCommand};
use crate::render::resource::TrackedRenderPass; use crate::render::resource::TrackedRenderPass;
use crate::render::stages::draw_graph;
use crate::render::util::FloatOrd; use crate::render::util::FloatOrd;
use crate::render::Eventually::Initialized; use crate::render::Eventually::Initialized;
use crate::render::RenderState; use crate::render::{draw_graph, main_graph, RenderState};
use std::ops::{Deref, Range}; 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 {} pub struct MainPassNode {}
impl MainPassNode { impl MainPassNode {

View File

@ -27,18 +27,23 @@ use crate::render::shaders::{ShaderFeatureStyle, ShaderLayerMetadata};
use crate::render::tile_view_pattern::{TileInView, TileShape, TileViewPattern}; use crate::render::tile_view_pattern::{TileInView, TileShape, TileViewPattern};
use crate::render::util::Eventually; use crate::render::util::Eventually;
use crate::tessellation::IndexDataType; use crate::tessellation::IndexDataType;
use crate::MapWindow; use crate::{HeadedMapWindow, MapWindow, MapWindowConfig};
use log::info; 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 // Rendering internals
mod graph;
mod graph_runner; mod graph_runner;
mod main_pass; mod main_pass;
mod render_commands; mod render_commands;
mod render_phase; mod render_phase;
mod resource;
mod shaders; mod shaders;
mod stages;
mod tile_pipeline; mod tile_pipeline;
mod tile_view_pattern; mod tile_view_pattern;
mod util; mod util;
@ -52,7 +57,6 @@ pub use stages::register_render_stages;
pub const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType pub const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
#[derive(Default)]
pub struct RenderState { pub struct RenderState {
render_target: Eventually<TextureView>, render_target: Eventually<TextureView>,
@ -76,13 +80,40 @@ pub struct RenderState {
depth_texture: Eventually<Texture>, depth_texture: Eventually<Texture>,
multisampling_texture: Eventually<Option<Texture>>, multisampling_texture: Eventually<Option<Texture>>,
surface: Surface,
mask_phase: RenderPhase<TileInView>, mask_phase: RenderPhase<TileInView>,
tile_phase: RenderPhase<(IndexEntry, TileShape)>, 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 struct Renderer {
pub instance: wgpu::Instance, 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 queue: wgpu::Queue,
pub adapter_info: wgpu::AdapterInfo, pub adapter_info: wgpu::AdapterInfo,
@ -90,7 +121,6 @@ pub struct Renderer {
pub settings: RendererSettings, pub settings: RendererSettings,
pub state: RenderState, pub state: RenderState,
pub surface: Surface,
} }
impl Renderer { impl Renderer {
@ -102,22 +132,15 @@ impl Renderer {
settings: RendererSettings, settings: RendererSettings,
) -> Result<Self, wgpu::RequestDeviceError> ) -> Result<Self, wgpu::RequestDeviceError>
where where
MW: MapWindow, MW: MapWindow + HeadedMapWindow,
{ {
let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all())); let instance = wgpu::Instance::new(wgpu_settings.backends.unwrap_or(wgpu::Backends::all()));
let maybe_surface = match &settings.surface_type { let surface = Surface::from_window(&instance, window, &settings);
SurfaceType::Headless => None,
SurfaceType::Headed => Some(Surface::from_window(&instance, window, &settings)),
};
let compatible_surface = if let Some(surface) = &maybe_surface { let compatible_surface = match &surface.head() {
match &surface.head() { Head::Headed(window_head) => Some(window_head.surface()),
Head::Headed(window_head) => Some(window_head.surface()), Head::Headless(_) => None,
Head::Headless(_) => None,
}
} else {
None
}; };
let (device, queue, adapter_info) = Self::request_device( let (device, queue, adapter_info) = Self::request_device(
@ -131,11 +154,6 @@ impl Renderer {
) )
.await?; .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() { match surface.head() {
Head::Headed(window) => window.configure(&device), Head::Headed(window) => window.configure(&device),
Head::Headless(_) => {} Head::Headless(_) => {}
@ -143,18 +161,51 @@ impl Renderer {
Ok(Self { Ok(Self {
instance, instance,
device, device: Arc::new(device),
queue, queue,
adapter_info, adapter_info,
wgpu_settings, wgpu_settings,
settings, settings,
state: Default::default(), state: RenderState::new(surface),
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) { pub fn resize(&mut self, width: u32, height: u32) {
self.surface.resize(width, height) self.state.surface.resize(width, height)
} }
/// Requests a device /// Requests a device
@ -323,7 +374,7 @@ impl Renderer {
&self.state &self.state
} }
pub fn surface(&self) -> &Surface { pub fn surface(&self) -> &Surface {
&self.surface &self.state.surface
} }
} }
@ -331,10 +382,34 @@ impl Renderer {
mod tests { mod tests {
use crate::render::graph::RenderGraph; use crate::render::graph::RenderGraph;
use crate::render::graph_runner::RenderGraphRunner; 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"))] #[cfg(not(target_arch = "wasm32"))]
#[tokio::test] #[tokio::test]
#[ignore] // FIXME: We do not have a GPU in CI
async fn test_render() { async fn test_render() {
let graph = RenderGraph::default(); let graph = RenderGraph::default();
@ -362,8 +437,41 @@ mod tests {
.ok() .ok()
.unwrap(); .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";
} }
} }

View File

@ -44,7 +44,7 @@ pub struct BufferPool<Q, B, V, I, TM, FM> {
} }
#[derive(Debug)] #[derive(Debug)]
enum BackingBufferType { pub enum BackingBufferType {
Vertices, Vertices,
Indices, Indices,
Metadata, Metadata,
@ -578,12 +578,12 @@ impl RingIndex {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::coords::ZoomLevel;
use crate::style::layer::StyleLayer; use crate::style::layer::StyleLayer;
use lyon::tessellation::VertexBuffers; use lyon::tessellation::VertexBuffers;
use crate::render::resource::buffer_pool::{ use crate::render::resource::buffer_pool::BackingBufferType;
BackingBufferDescriptor, BackingBufferType, BufferPool, Queue, use crate::render::resource::{BackingBufferDescriptor, BufferPool, Queue};
};
#[derive(Debug)] #[derive(Debug)]
struct TestBuffer { struct TestBuffer {
@ -639,7 +639,7 @@ mod tests {
for _ in 0..2 { for _ in 0..2 {
pool.allocate_layer_geometry( pool.allocate_layer_geometry(
&queue, &queue,
(0, 0, 0).into(), (0, 0, ZoomLevel::default()).into(),
style_layer.clone(), style_layer.clone(),
&data48bytes_aligned, &data48bytes_aligned,
2, 2,
@ -653,7 +653,7 @@ mod tests {
pool.allocate_layer_geometry( pool.allocate_layer_geometry(
&queue, &queue,
(0, 0, 0).into(), (0, 0, ZoomLevel::default()).into(),
style_layer.clone(), style_layer.clone(),
&data24bytes_aligned, &data24bytes_aligned,
2, 2,
@ -667,7 +667,7 @@ mod tests {
pool.allocate_layer_geometry( pool.allocate_layer_geometry(
&queue, &queue,
(0, 0, 0).into(), (0, 0, ZoomLevel::default()).into(),
style_layer.clone(), style_layer.clone(),
&data24bytes_aligned, &data24bytes_aligned,
2, 2,
@ -679,7 +679,7 @@ mod tests {
pool.allocate_layer_geometry( pool.allocate_layer_geometry(
&queue, &queue,
(0, 0, 0).into(), (0, 0, ZoomLevel::default()).into(),
style_layer.clone(), style_layer.clone(),
&data24bytes_aligned, &data24bytes_aligned,
2, 2,
@ -690,7 +690,7 @@ mod tests {
pool.allocate_layer_geometry( pool.allocate_layer_geometry(
&queue, &queue,
(0, 0, 0).into(), (0, 0, ZoomLevel::default()).into(),
style_layer.clone(), style_layer.clone(),
&data24bytes_aligned, &data24bytes_aligned,
2, 2,
@ -701,7 +701,7 @@ mod tests {
pool.allocate_layer_geometry( pool.allocate_layer_geometry(
&queue, &queue,
(0, 0, 0).into(), (0, 0, ZoomLevel::default()).into(),
style_layer, style_layer,
&data24bytes_aligned, &data24bytes_aligned,
2, 2,

View File

@ -4,14 +4,18 @@
use crate::render::resource::texture::TextureView; use crate::render::resource::texture::TextureView;
use crate::render::settings::RendererSettings; use crate::render::settings::RendererSettings;
use crate::render::util::HasChanged; 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::mem::size_of;
use std::sync::Arc;
struct BufferDimensions { pub struct BufferDimensions {
width: usize, pub width: usize,
height: usize, pub height: usize,
unpadded_bytes_per_row: usize, pub unpadded_bytes_per_row: usize,
padded_bytes_per_row: usize, pub padded_bytes_per_row: usize,
} }
impl BufferDimensions { impl BufferDimensions {
@ -42,7 +46,7 @@ impl WindowHead {
pub fn recreate_surface<MW>(&mut self, window: &MW, instance: &wgpu::Instance) pub fn recreate_surface<MW>(&mut self, window: &MW, instance: &wgpu::Instance)
where where
MW: MapWindow, MW: MapWindow + HeadedMapWindow,
{ {
self.surface = unsafe { instance.create_surface(window.inner()) }; self.surface = unsafe { instance.create_surface(window.inner()) };
} }
@ -52,14 +56,63 @@ impl WindowHead {
} }
pub struct BufferedTextureHead { pub struct BufferedTextureHead {
texture: wgpu::Texture, pub texture: wgpu::Texture,
output_buffer: wgpu::Buffer, pub output_buffer: wgpu::Buffer,
buffer_dimensions: BufferDimensions, 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 { pub enum Head {
Headed(WindowHead), Headed(WindowHead),
Headless(BufferedTextureHead), Headless(Arc<BufferedTextureHead>),
} }
pub struct Surface { pub struct Surface {
@ -74,7 +127,7 @@ impl Surface {
settings: &RendererSettings, settings: &RendererSettings,
) -> Self ) -> Self
where where
MW: MapWindow, MW: MapWindow + HeadedMapWindow,
{ {
let size = window.size(); let size = window.size();
let surface_config = wgpu::SurfaceConfiguration { 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 pub fn from_image<MW>(device: &wgpu::Device, window: &MW, settings: &RendererSettings) -> Self
where where
MW: MapWindow, MW: MapWindow,
@ -111,7 +165,7 @@ impl Surface {
BufferDimensions::new(size.width() as usize, size.height() as usize); BufferDimensions::new(size.width() as usize, size.height() as usize);
// The output buffer lets us retrieve the data as an array // The output buffer lets us retrieve the data as an array
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor { 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, size: (buffer_dimensions.padded_bytes_per_row * buffer_dimensions.height) as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
@ -133,11 +187,11 @@ impl Surface {
Self { Self {
size, size,
head: Head::Headless(BufferedTextureHead { head: Head::Headless(Arc::new(BufferedTextureHead {
texture, texture,
output_buffer, output_buffer,
buffer_dimensions, buffer_dimensions,
}), })),
} }
} }
@ -149,7 +203,7 @@ impl Surface {
let frame = match surface.get_current_texture() { let frame = match surface.get_current_texture() {
Ok(view) => view, Ok(view) => view,
Err(wgpu::SurfaceError::Outdated) => { Err(wgpu::SurfaceError::Outdated) => {
tracing::trace!("surface outdated"); log::warn!("surface outdated");
window.configure(device); window.configure(device);
surface surface
.get_current_texture() .get_current_texture()
@ -159,7 +213,8 @@ impl Surface {
}; };
frame.into() frame.into()
} }
Head::Headless(BufferedTextureHead { texture, .. }) => texture Head::Headless(arc) => arc
.texture
.create_view(&wgpu::TextureViewDescriptor::default()) .create_view(&wgpu::TextureViewDescriptor::default())
.into(), .into(),
} }
@ -193,11 +248,13 @@ impl Surface {
pub fn recreate<MW>(&mut self, window: &MW, instance: &wgpu::Instance) pub fn recreate<MW>(&mut self, window: &MW, instance: &wgpu::Instance)
where where
MW: MapWindow, MW: MapWindow + HeadedMapWindow,
{ {
match &mut self.head { match &mut self.head {
Head::Headed(head) => { Head::Headed(window_head) => {
head.recreate_surface(window, instance); if window_head.has_changed(&(self.size.width(), self.size.height())) {
window_head.recreate_surface(window, instance);
}
} }
Head::Headless(_) => {} Head::Headless(_) => {}
} }
@ -216,6 +273,6 @@ impl HasChanged for WindowHead {
type Criteria = (u32, u32); type Criteria = (u32, u32);
fn has_changed(&self, criteria: &Self::Criteria) -> bool { 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
} }
} }

View File

@ -202,7 +202,7 @@ impl<'a> TrackedRenderPass<'a> {
/// Push a new debug group over the internal stack. Subsequent render commands and debug /// 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. /// markers are grouped into this new group, until [`pop_debug_group`] is called.
/// ///
/// ```ignore /// ```
/// # fn example(mut pass: maplibre::render::resource::TrackedRenderPass<'static>) { /// # fn example(mut pass: maplibre::render::resource::TrackedRenderPass<'static>) {
/// pass.push_debug_group("Render the car"); /// pass.push_debug_group("Render the car");
/// // [setup pipeline etc...] /// // [setup pipeline etc...]

View File

@ -4,6 +4,10 @@ use crate::platform::COLOR_TEXTURE_FORMAT;
use std::borrow::Cow; use std::borrow::Cow;
pub use wgpu::Backends; 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), /// 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) /// [`Device::limits`](crate::renderer::Device::limits), and the [`WgpuAdapterInfo`](crate::render_resource::WgpuAdapterInfo)
@ -11,17 +15,17 @@ pub use wgpu::Backends;
#[derive(Clone)] #[derive(Clone)]
pub struct WgpuSettings { pub struct WgpuSettings {
pub device_label: Option<Cow<'static, str>>, pub device_label: Option<Cow<'static, str>>,
pub backends: Option<wgpu::Backends>, pub backends: Option<Backends>,
pub power_preference: wgpu::PowerPreference, pub power_preference: PowerPreference,
/// The features to ensure are enabled regardless of what the adapter/backend supports. /// The features to ensure are enabled regardless of what the adapter/backend supports.
/// Setting these explicitly may cause renderer initialization to fail. /// 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 /// 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. /// The imposed limits.
pub limits: wgpu::Limits, pub limits: Limits,
/// The constraints on limits allowed regardless of what the adapter/backend supports /// 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 /// Whether a trace is recorded an stored in the current working directory
pub record_trace: bool, 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 backends = Some(wgpu::util::backend_bits_from_env().unwrap_or(wgpu::Backends::all()));
let limits = if cfg!(feature = "web-webgl") { let limits = if cfg!(feature = "web-webgl") {
wgpu::Limits { Limits {
max_texture_dimension_2d: 4096, max_texture_dimension_2d: 4096,
..wgpu::Limits::downlevel_webgl2_defaults() ..Limits::downlevel_webgl2_defaults()
} }
} else if cfg!(target_os = "android") { } else if cfg!(target_os = "android") {
wgpu::Limits { Limits {
max_storage_textures_per_shader_stage: 4, max_storage_textures_per_shader_stage: 4,
max_compute_workgroups_per_dimension: 0, max_compute_workgroups_per_dimension: 0,
max_compute_workgroup_size_z: 0, max_compute_workgroup_size_z: 0,
@ -45,19 +49,19 @@ impl Default for WgpuSettings {
max_compute_workgroup_size_x: 0, max_compute_workgroup_size_x: 0,
max_compute_workgroup_storage_size: 0, max_compute_workgroup_storage_size: 0,
max_compute_invocations_per_workgroup: 0, max_compute_invocations_per_workgroup: 0,
..wgpu::Limits::downlevel_defaults() ..Limits::downlevel_defaults()
} }
} else { } else {
wgpu::Limits { Limits {
..wgpu::Limits::default() ..Limits::default()
} }
}; };
Self { Self {
device_label: Default::default(), device_label: Default::default(),
backends, backends,
power_preference: wgpu::PowerPreference::HighPerformance, power_preference: PowerPreference::HighPerformance,
features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, features: Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES,
disabled_features: None, disabled_features: None,
limits, limits,
constrained_limits: None, constrained_limits: None,
@ -101,8 +105,7 @@ impl Default for Msaa {
#[derive(Clone)] #[derive(Clone)]
pub struct RendererSettings { pub struct RendererSettings {
pub msaa: Msaa, pub msaa: Msaa,
pub texture_format: wgpu::TextureFormat, pub texture_format: TextureFormat,
pub surface_type: SurfaceType,
} }
impl Default for RendererSettings { impl Default for RendererSettings {
@ -110,7 +113,6 @@ impl Default for RendererSettings {
Self { Self {
msaa: Msaa::default(), msaa: Msaa::default(),
texture_format: COLOR_TEXTURE_FORMAT, texture_format: COLOR_TEXTURE_FORMAT,
surface_type: SurfaceType::Headed,
} }
} }
} }

View 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);
}
}
}
}

View File

@ -1,10 +1,5 @@
//! Executes the [`RenderGraph`] current render graph. //! 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::context::MapContext;
use crate::render::graph::{EmptyNode, RenderGraph}; use crate::render::graph::{EmptyNode, RenderGraph};
use crate::render::graph_runner::RenderGraphRunner; use crate::render::graph_runner::RenderGraphRunner;
@ -14,42 +9,13 @@ use crate::schedule::Stage;
use crate::Renderer; use crate::Renderer;
use log::error; 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. /// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.
pub struct GraphRunnerStage { pub struct GraphRunnerStage {
graph: RenderGraph, graph: RenderGraph,
} }
impl Default for GraphRunnerStage { impl GraphRunnerStage {
fn default() -> Self { pub fn new(graph: RenderGraph) -> 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();
Self { graph } Self { graph }
} }
} }

View File

@ -1,21 +1,28 @@
//! Rendering specific [Stages](Stage) //! Rendering specific [Stages](Stage)
use crate::context::MapContext; 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 crate::schedule::{MultiStage, Schedule, Stage, StageLabel};
use graph_runner_stage::GraphRunnerStage; use graph_runner_stage::GraphRunnerStage;
use resource_stage::ResourceStage; use resource_stage::ResourceStage;
use upload_stage::UploadStage; use upload_stage::UploadStage;
mod extract_stage;
mod graph_runner_stage; mod graph_runner_stage;
mod phase_sort_stage; mod phase_sort_stage;
mod queue_stage; mod queue_stage;
mod resource_stage; mod resource_stage;
mod upload_stage; mod upload_stage;
use crate::multi_stage; #[cfg(feature = "headless")]
use crate::render::stages::phase_sort_stage::PhaseSortStage; // Exposed because it should be addable conditionally
use crate::render::stages::queue_stage::QueueStage; pub mod write_surface_buffer_stage;
pub use graph_runner_stage::{draw_graph, node};
/// The labels of the default App rendering stages. /// The labels of the default App rendering stages.
#[derive(Debug, Hash, PartialEq, Eq, Clone)] #[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::Prepare, PrepareStage::default());
schedule.add_stage(RenderStageLabel::Queue, QueueStage::default()); schedule.add_stage(RenderStageLabel::Queue, QueueStage::default());
schedule.add_stage(RenderStageLabel::PhaseSort, PhaseSortStage::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(())
} }

View File

@ -2,8 +2,7 @@
use crate::context::MapContext; use crate::context::MapContext;
use crate::coords::{ViewRegion, Zoom}; use crate::coords::{ViewRegion, Zoom};
use crate::io::tile_cache::TileCache; use crate::io::tile_repository::TileRepository;
use crate::io::LayerTessellateMessage;
use crate::render::camera::ViewProjection; use crate::render::camera::ViewProjection;
use crate::render::render_phase::RenderPhase; use crate::render::render_phase::RenderPhase;
use crate::render::resource::IndexEntry; use crate::render::resource::IndexEntry;

View File

@ -2,8 +2,7 @@
use crate::context::MapContext; use crate::context::MapContext;
use crate::coords::{ViewRegion, Zoom}; use crate::coords::{ViewRegion, Zoom};
use crate::io::tile_cache::TileCache; use crate::io::tile_repository::TileRepository;
use crate::io::LayerTessellateMessage;
use crate::render::camera::ViewProjection; use crate::render::camera::ViewProjection;
use crate::render::resource::IndexEntry; use crate::render::resource::IndexEntry;
use crate::render::shaders::{ use crate::render::shaders::{
@ -23,15 +22,27 @@ impl Stage for QueueStage {
fn run( fn run(
&mut self, &mut self,
MapContext { MapContext {
renderer: Renderer { state, .. }, view_state,
renderer:
Renderer {
state:
RenderState {
mask_phase,
tile_phase,
tile_view_pattern,
buffer_pool,
..
},
..
},
.. ..
}: &mut MapContext, }: &mut MapContext,
) { ) {
state.mask_phase.items.clear(); mask_phase.items.clear();
state.tile_phase.items.clear(); tile_phase.items.clear();
if let (Initialized(tile_view_pattern), Initialized(buffer_pool)) = 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(); let index = buffer_pool.index();
@ -43,7 +54,7 @@ impl Stage for QueueStage {
let shape_to_render = fallback.as_ref().unwrap_or(shape); let shape_to_render = fallback.as_ref().unwrap_or(shape);
// Draw mask // 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) { if let Some(entries) = index.get_layers(&shape_to_render.coords) {
let mut layers_to_render: Vec<&IndexEntry> = Vec::from_iter(entries); 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 { for entry in layers_to_render {
// Draw tile // Draw tile
state tile_phase.add((entry.clone(), shape_to_render.clone()))
.tile_phase
.add((entry.clone(), shape_to_render.clone()))
} }
} else { } else {
tracing::trace!("No layers found at {}", &shape_to_render.coords); tracing::trace!("No layers found at {}", &shape_to_render.coords);

View File

@ -28,13 +28,14 @@ impl Stage for ResourceStage {
Renderer { Renderer {
settings, settings,
device, device,
surface,
state, state,
.. ..
}, },
.. ..
}: &mut MapContext, }: &mut MapContext,
) { ) {
let surface = &mut state.surface;
let size = surface.size(); let size = surface.size();
surface.reconfigure(device); surface.reconfigure(device);

View File

@ -2,8 +2,7 @@
use crate::context::MapContext; use crate::context::MapContext;
use crate::coords::{ViewRegion, Zoom}; use crate::coords::{ViewRegion, Zoom};
use crate::io::tile_cache::TileCache; use crate::io::tile_repository::{StoredLayer, TileRepository};
use crate::io::LayerTessellateMessage;
use crate::render::camera::ViewProjection; use crate::render::camera::ViewProjection;
use crate::render::resource::IndexEntry; use crate::render::resource::IndexEntry;
use crate::render::shaders::{ use crate::render::shaders::{
@ -26,16 +25,8 @@ impl Stage for UploadStage {
MapContext { MapContext {
view_state, view_state,
style, style,
tile_cache, tile_repository,
renderer: renderer: Renderer { queue, state, .. },
Renderer {
settings: _,
device: _,
queue,
surface: _,
state,
..
},
.. ..
}: &mut MapContext, }: &mut MapContext,
) { ) {
@ -67,10 +58,8 @@ impl Stage for UploadStage {
.map(|bounding_box| ViewRegion::new(bounding_box, 0, *view_state.zoom, visible_level)); .map(|bounding_box| ViewRegion::new(bounding_box, 0, *view_state.zoom, visible_level));
if let Some(view_region) = &view_region { if let Some(view_region) = &view_region {
let zoom = view_state.zoom(); self.upload_tile_geometry(state, queue, tile_repository, style, view_region);
self.upload_tile_view_pattern(state, queue, &view_proj);
self.upload_tile_geometry(state, queue, tile_cache, style, view_region);
self.update_tile_view_pattern(state, queue, view_region, &view_proj, zoom);
self.update_metadata(); self.update_metadata();
} }
} }
@ -99,7 +88,7 @@ impl UploadStage {
/*let source_layer = entry.style_layer.source_layer.as_ref().unwrap(); /*let source_layer = entry.style_layer.source_layer.as_ref().unwrap();
if let Some(result) = scheduler if let Some(result) = scheduler
.get_tile_cache() .get_tile_repository()
.iter_tessellated_layers_at(&world_coords) .iter_tessellated_layers_at(&world_coords)
.unwrap() .unwrap()
.find(|layer| source_layer.as_str() == layer.layer_name()) .find(|layer| source_layer.as_str() == layer.layer_name())
@ -147,22 +136,15 @@ impl UploadStage {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub fn update_tile_view_pattern( pub fn upload_tile_view_pattern(
&self, &self,
RenderState { RenderState {
tile_view_pattern, tile_view_pattern, ..
buffer_pool,
..
}: &mut RenderState, }: &mut RenderState,
queue: &wgpu::Queue, queue: &wgpu::Queue,
view_region: &ViewRegion,
view_proj: &ViewProjection, view_proj: &ViewProjection,
zoom: Zoom,
) { ) {
if let (Initialized(tile_view_pattern), Initialized(buffer_pool)) = if let Initialized(tile_view_pattern) = tile_view_pattern {
(tile_view_pattern, buffer_pool)
{
tile_view_pattern.update_pattern(view_region, buffer_pool, zoom);
tile_view_pattern.upload_pattern(queue, view_proj); tile_view_pattern.upload_pattern(queue, view_proj);
} }
} }
@ -172,7 +154,7 @@ impl UploadStage {
&self, &self,
RenderState { buffer_pool, .. }: &mut RenderState, RenderState { buffer_pool, .. }: &mut RenderState,
queue: &wgpu::Queue, queue: &wgpu::Queue,
tile_cache: &TileCache, tile_repository: &TileRepository,
style: &Style, style: &Style,
view_region: &ViewRegion, view_region: &ViewRegion,
) { ) {
@ -182,7 +164,7 @@ impl UploadStage {
let loaded_layers = buffer_pool let loaded_layers = buffer_pool
.get_loaded_layers_at(&world_coords) .get_loaded_layers_at(&world_coords)
.unwrap_or_default(); .unwrap_or_default();
if let Some(available_layers) = tile_cache if let Some(available_layers) = tile_repository
.iter_tessellated_layers_at(&world_coords) .iter_tessellated_layers_at(&world_coords)
.map(|layers| { .map(|layers| {
layers layers
@ -204,10 +186,10 @@ impl UploadStage {
.map(|color| color.into()); .map(|color| color.into());
match message { match message {
LayerTessellateMessage::UnavailableLayer { coords: _, .. } => { StoredLayer::UnavailableLayer { coords: _, .. } => {
/*self.buffer_pool.mark_layer_unavailable(*coords);*/ /*self.buffer_pool.mark_layer_unavailable(*coords);*/
} }
LayerTessellateMessage::TessellatedLayer { StoredLayer::TessellatedLayer {
coords, coords,
feature_indices, feature_indices,
layer_data, layer_data,

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

View File

@ -79,7 +79,7 @@ where
}; };
if should_replace { 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)] #[tracing::instrument(name = "initialize", skip_all)]
pub fn initialize(&mut self, f: impl FnOnce() -> T) { pub fn initialize(&mut self, f: impl FnOnce() -> T) {
if let Eventually::Uninitialized = self { if let Eventually::Uninitialized = self {
mem::replace(self, Eventually::Initialized(f())); *self = Eventually::Initialized(f());
} }
} }

View 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,
}
)
}
}

View File

@ -1,15 +1,188 @@
//! [Stages](Stage) for requesting and preparing data //! [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::schedule::Schedule;
use crate::stages::message::{
LayerTessellateMessage, MessageReceiver, MessageSender, TessellateMessage,
TileTessellateMessage,
};
use crate::stages::populate_tile_store_stage::PopulateTileStore; 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 request_stage::RequestStage;
use std::collections::HashSet;
use std::fmt;
use std::sync::{mpsc, Arc, Mutex};
mod message;
mod populate_tile_store_stage; mod populate_tile_store_stage;
mod request_stage; mod request_stage;
pub fn register_stages<HC: HTTPClient>(schedule: &mut Schedule, source_client: SourceClient<HC>) { /// Register stages required for requesting and preparing new tiles.
schedule.add_stage("request", RequestStage::new(source_client)); pub fn register_stages<HC: HttpClient, SM: ScheduleMethod>(
schedule.add_stage("populate_tile_store", PopulateTileStore::default()); 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!()
}
}
} }

View File

@ -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::context::MapContext;
use crate::io::{TessellateMessage, TileTessellateMessage}; use crate::io::tile_repository::StoredLayer;
use crate::schedule::Stage; 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 { impl Stage for PopulateTileStore {
fn run( fn run(
&mut self, &mut self,
MapContext { MapContext {
tile_cache, tile_repository, ..
shared_thread_state,
message_receiver,
..
}: &mut MapContext, }: &mut MapContext,
) { ) {
if let Ok(result) = message_receiver.try_recv() { if let Ok(result) = self.message_receiver.try_recv() {
match result { match result {
TessellateMessage::Layer(layer_result) => { TessellateMessage::Layer(layer_result) => {
let layer: StoredLayer = layer_result.into();
tracing::trace!( tracing::trace!(
"Layer {} at {} reached main thread", "Layer {} at {} reached main thread",
layer_result.layer_name(), layer.layer_name(),
layer_result.get_coords() layer.get_coords()
); );
tile_cache.put_tessellated_layer(layer_result); tile_repository.put_tessellated_layer(layer);
} }
TessellateMessage::Tile(TileTessellateMessage { request_id, coords }) => loop { TessellateMessage::Tile(TileTessellateMessage { request_id, coords }) => loop {
if let Ok(mut tile_request_state) = 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); tile_request_state.finish_tile_request(request_id);
tracing::trace!("Tile at {} finished loading", coords); tracing::trace!("Tile at {} finished loading", coords);

View File

@ -3,46 +3,55 @@
use crate::context::MapContext; use crate::context::MapContext;
use crate::coords::{ViewRegion, WorldTileCoords}; use crate::coords::{ViewRegion, WorldTileCoords};
use crate::error::Error; use crate::error::Error;
use crate::io::shared_thread_state::SharedThreadState; use crate::io::source_client::{HttpSourceClient, SourceClient};
use crate::io::source_client::SourceClient; use crate::io::tile_repository::TileRepository;
use crate::io::tile_cache::TileCache;
use crate::io::TileRequest; use crate::io::TileRequest;
use crate::schedule::Stage; use crate::schedule::Stage;
use crate::{HTTPClient, ScheduleMethod, Style}; use crate::stages::SharedThreadState;
use crate::{HttpClient, ScheduleMethod, Scheduler, Style};
use std::collections::HashSet; use std::collections::HashSet;
pub struct RequestStage<HC> pub struct RequestStage<SM, HC>
where where
HC: HTTPClient, SM: ScheduleMethod,
HC: HttpClient,
{ {
pub source_client: SourceClient<HC>, shared_thread_state: SharedThreadState,
pub try_failed: bool, scheduler: Scheduler<SM>,
http_source_client: HttpSourceClient<HC>,
try_failed: bool,
} }
impl<HC> RequestStage<HC> impl<SM, HC> RequestStage<SM, HC>
where 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 { Self {
source_client, shared_thread_state,
scheduler,
http_source_client,
try_failed: false, try_failed: false,
} }
} }
} }
impl<HC> Stage for RequestStage<HC> impl<SM, HC> Stage for RequestStage<SM, HC>
where where
HC: HTTPClient, SM: ScheduleMethod,
HC: HttpClient,
{ {
fn run( fn run(
&mut self, &mut self,
MapContext { MapContext {
view_state, view_state,
style, style,
tile_cache, tile_repository,
scheduler,
shared_thread_state,
.. ..
}: &mut MapContext, }: &mut MapContext,
) { ) {
@ -59,13 +68,7 @@ where
{ {
if let Some(view_region) = &view_region { if let Some(view_region) = &view_region {
// FIXME: We also need to request tiles from layers above if we are over the maximum zoom level // FIXME: We also need to request tiles from layers above if we are over the maximum zoom level
self.try_failed = self.request_tiles_in_view( self.try_failed = self.request_tiles_in_view(tile_repository, style, view_region);
tile_cache,
style,
shared_thread_state,
scheduler,
view_region,
);
} }
} }
@ -74,18 +77,17 @@ where
} }
} }
impl<HC> RequestStage<HC> impl<SM, HC> RequestStage<SM, HC>
where where
HC: HTTPClient, SM: ScheduleMethod,
HC: HttpClient,
{ {
/// Request tiles which are currently in view. /// Request tiles which are currently in view.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn request_tiles_in_view( fn request_tiles_in_view(
&self, &self,
tile_cache: &TileCache, tile_repository: &TileRepository,
style: &Style, style: &Style,
shared_thread_state: &SharedThreadState,
scheduler: &Box<dyn ScheduleMethod>,
view_region: &ViewRegion, view_region: &ViewRegion,
) -> bool { ) -> bool {
let mut try_failed = false; let mut try_failed = false;
@ -99,13 +101,7 @@ where
if coords.build_quad_key().is_some() { if coords.build_quad_key().is_some() {
// TODO: Make tesselation depend on style? // TODO: Make tesselation depend on style?
try_failed = self try_failed = self
.try_request_tile( .try_request_tile(tile_repository, &coords, &source_layers)
tile_cache,
shared_thread_state,
scheduler,
&coords,
&source_layers,
)
.unwrap(); .unwrap();
} }
} }
@ -114,17 +110,15 @@ where
fn try_request_tile( fn try_request_tile(
&self, &self,
tile_cache: &TileCache, tile_repository: &TileRepository,
shared_thread_state: &SharedThreadState,
scheduler: &Box<dyn ScheduleMethod>,
coords: &WorldTileCoords, coords: &WorldTileCoords,
layers: &HashSet<String>, layers: &HashSet<String>,
) -> Result<bool, Error> { ) -> Result<bool, Error> {
if !tile_cache.is_layers_missing(coords, layers) { if !tile_repository.is_layers_missing(coords, layers) {
return Ok(false); 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 { if let Some(request_id) = tile_request_state.start_tile_request(TileRequest {
coords: *coords, coords: *coords,
layers: layers.clone(), 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; let coords = *coords;
scheduler let state = self.shared_thread_state.clone();
.schedule( self.scheduler
shared_thread_state.clone(), .schedule_method()
Box::new(move |state: SharedThreadState| { .schedule(Box::new(move || {
Box::pin(async move { Box::pin(async move {
match client.fetch(&coords).await { match client.fetch(&coords).await {
Ok(data) => state Ok(data) => state
.process_tile(request_id, data.into_boxed_slice()) .process_tile(request_id, data.into_boxed_slice())
.unwrap(), .unwrap(),
Err(e) => { Err(e) => {
log::error!("{:?}", &e); log::error!("{:?}", &e);
state.tile_unavailable(&coords, request_id).unwrap() state.tile_unavailable(&coords, request_id).unwrap()
}
} }
}) }
}), })
) }))
.unwrap(); .unwrap();
} }

View File

@ -33,7 +33,7 @@ impl Default for Style {
minzoom: None, minzoom: None,
metadata: None, metadata: None,
paint: Some(LayerPaint::Line(LinePaint { paint: Some(LayerPaint::Line(LinePaint {
line_color: Some(Color::from_str("lightgreen").unwrap()), line_color: Some(Color::from_str("#c8facc").unwrap()),
})), })),
source: None, source: None,
source_layer: Some("park".to_string()), source_layer: Some("park".to_string()),
@ -46,7 +46,7 @@ impl Default for Style {
minzoom: None, minzoom: None,
metadata: None, metadata: None,
paint: Some(LayerPaint::Line(LinePaint { paint: Some(LayerPaint::Line(LinePaint {
line_color: Some(Color::from_str("lightgreen").unwrap()), line_color: Some(Color::from_str("#e0dfdf").unwrap()),
})), })),
source: None, source: None,
source_layer: Some("landuse".to_string()), source_layer: Some("landuse".to_string()),
@ -59,20 +59,20 @@ impl Default for Style {
minzoom: None, minzoom: None,
metadata: None, metadata: None,
paint: Some(LayerPaint::Line(LinePaint { paint: Some(LayerPaint::Line(LinePaint {
line_color: Some(Color::from_str("lightgreen").unwrap()), line_color: Some(Color::from_str("#aedfa3").unwrap()),
})), })),
source: None, source: None,
source_layer: Some("landcover".to_string()), source_layer: Some("landcover".to_string()),
}, },
StyleLayer { StyleLayer {
index: 3, index: 3,
id: "1transportation".to_string(), id: "transportation".to_string(),
typ: "line".to_string(), typ: "line".to_string(),
maxzoom: None, maxzoom: None,
minzoom: None, minzoom: None,
metadata: None, metadata: None,
paint: Some(LayerPaint::Line(LinePaint { paint: Some(LayerPaint::Line(LinePaint {
line_color: Some(Color::from_str("violet").unwrap()), line_color: Some(Color::from_str("#ffffff").unwrap()),
})), })),
source: None, source: None,
source_layer: Some("transportation".to_string()), source_layer: Some("transportation".to_string()),
@ -85,7 +85,7 @@ impl Default for Style {
minzoom: None, minzoom: None,
metadata: None, metadata: None,
paint: Some(LayerPaint::Line(LinePaint { paint: Some(LayerPaint::Line(LinePaint {
line_color: Some(Color::from_str("grey").unwrap()), line_color: Some(Color::from_str("#d9d0c9").unwrap()),
})), })),
source: None, source: None,
source_layer: Some("building".to_string()), source_layer: Some("building".to_string()),
@ -98,7 +98,7 @@ impl Default for Style {
minzoom: None, minzoom: None,
metadata: None, metadata: None,
paint: Some(LayerPaint::Line(LinePaint { paint: Some(LayerPaint::Line(LinePaint {
line_color: Some(Color::from_str("blue").unwrap()), line_color: Some(Color::from_str("#aad3df").unwrap()),
})), })),
source: None, source: None,
source_layer: Some("water".to_string()), source_layer: Some("water".to_string()),
@ -111,7 +111,7 @@ impl Default for Style {
minzoom: None, minzoom: None,
metadata: None, metadata: None,
paint: Some(LayerPaint::Line(LinePaint { paint: Some(LayerPaint::Line(LinePaint {
line_color: Some(Color::from_str("blue").unwrap()), line_color: Some(Color::from_str("#aad3df").unwrap()),
})), })),
source: None, source: None,
source_layer: Some("waterway".to_string()), source_layer: Some("waterway".to_string()),

View File

@ -4,7 +4,7 @@ use std::time::Duration;
/// Measures the frames per second. /// Measures the frames per second.
/// ///
/// # Example /// # Example
/// ```ignore /// ```
/// use maplibre::util::FPSMeter; /// use maplibre::util::FPSMeter;
/// ///
/// let mut meter = FPSMeter::new(); /// let mut meter = FPSMeter::new();

View File

@ -51,7 +51,7 @@ where
/// ///
/// # Example /// # Example
/// ///
/// ```ignore /// ```
/// # use maplibre::define_label; /// # use maplibre::define_label;
/// define_label!(MyNewLabelTrait); /// define_label!(MyNewLabelTrait);
/// ``` /// ```

View File

@ -1,31 +1,38 @@
//! Utilities for the window system. //! 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 { 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 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 { 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 where
MWC: MapWindowConfig, MWC: MapWindowConfig,
SM: ScheduleMethod, 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. /// Window size with a width and an height in pixels.

View File

@ -35,3 +35,6 @@ wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
console_log = { version = "0.2", features = ["color"] } console_log = { version = "0.2", features = ["color"] }
tracing-wasm = { version = "0.2", optional = true } # FIXME: Low quality dependency tracing-wasm = { version = "0.2", optional = true } # FIXME: Low quality dependency
[dev-dependencies]
wasm-bindgen-test = "0.3"

View File

@ -28,7 +28,6 @@
"../lib": { "../lib": {
"name": "maplibre-rs", "name": "maplibre-rs",
"version": "0.0.1", "version": "0.0.1",
"hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"spectorjs": "^0.9.27", "spectorjs": "^0.9.27",
@ -36,10 +35,9 @@
}, },
"devDependencies": { "devDependencies": {
"@chialab/esbuild-plugin-env": "^0.15.3", "@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": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1", "esbuild-plugin-inline-worker": "^0.1.1",
"patch-package": "^6.4.7",
"ts-loader": "^9.2.8", "ts-loader": "^9.2.8",
"typescript": "^4.5.4", "typescript": "^4.5.4",
"wasm-pack": "^0.10.2" "wasm-pack": "^0.10.2"
@ -5892,10 +5890,9 @@
"version": "file:../lib", "version": "file:../lib",
"requires": { "requires": {
"@chialab/esbuild-plugin-env": "^0.15.3", "@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": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1", "esbuild-plugin-inline-worker": "^0.1.1",
"patch-package": "^6.4.7",
"spectorjs": "^0.9.27", "spectorjs": "^0.9.27",
"ts-loader": "^9.2.8", "ts-loader": "^9.2.8",
"typescript": "^4.5.4", "typescript": "^4.5.4",

View File

@ -7,7 +7,6 @@
"": { "": {
"name": "maplibre-rs", "name": "maplibre-rs",
"version": "0.0.1", "version": "0.0.1",
"hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"spectorjs": "^0.9.27", "spectorjs": "^0.9.27",
@ -18,7 +17,6 @@
"@chialab/esbuild-plugin-meta-url": "^0.15.28", "@chialab/esbuild-plugin-meta-url": "^0.15.28",
"esbuild": "^0.14.38", "esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1", "esbuild-plugin-inline-worker": "^0.1.1",
"patch-package": "^6.4.7",
"ts-loader": "^9.2.8", "ts-loader": "^9.2.8",
"typescript": "^4.5.4", "typescript": "^4.5.4",
"wasm-pack": "^0.10.2" "wasm-pack": "^0.10.2"
@ -288,12 +286,6 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true "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": { "node_modules/acorn": {
"version": "8.7.0", "version": "8.7.0",
"dev": true, "dev": true,
@ -315,18 +307,6 @@
"acorn": "^8" "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": { "node_modules/axios": {
"version": "0.21.4", "version": "0.21.4",
"dev": true, "dev": true,
@ -424,20 +404,6 @@
"license": "CC-BY-4.0", "license": "CC-BY-4.0",
"peer": true "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": { "node_modules/chownr": {
"version": "2.0.0", "version": "2.0.0",
"dev": true, "dev": true,
@ -455,27 +421,6 @@
"node": ">=6.0" "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": { "node_modules/commondir": {
"version": "1.0.1", "version": "1.0.1",
"dev": true, "dev": true,
@ -486,31 +431,6 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/detect-libc": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@ -614,15 +534,6 @@
"node": ">=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": { "node_modules/eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"dev": true, "dev": true,
@ -726,15 +637,6 @@
"node": ">=8" "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": { "node_modules/follow-redirects": {
"version": "1.14.9", "version": "1.14.9",
"dev": true, "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": { "node_modules/fs-minipass": {
"version": "2.1.0", "version": "2.1.0",
"dev": true, "dev": true,
@ -814,15 +702,6 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/inflight": {
"version": "1.0.6", "version": "1.0.6",
"dev": true, "dev": true,
@ -837,33 +716,6 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/is-number": {
"version": "7.0.0", "version": "7.0.0",
"dev": true, "dev": true,
@ -872,24 +724,6 @@
"node": ">=0.12.0" "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": { "node_modules/jest-worker": {
"version": "27.5.1", "version": "27.5.1",
"dev": true, "dev": true,
@ -934,24 +768,6 @@
"license": "MIT", "license": "MIT",
"peer": true "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": { "node_modules/loader-runner": {
"version": "4.3.0", "version": "4.3.0",
"dev": true, "dev": true,
@ -1055,12 +871,6 @@
"node": "*" "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": { "node_modules/minipass": {
"version": "3.1.6", "version": "3.1.6",
"dev": true, "dev": true,
@ -1101,12 +911,6 @@
"license": "MIT", "license": "MIT",
"peer": true "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": { "node_modules/node-releases": {
"version": "2.0.3", "version": "2.0.3",
"dev": true, "dev": true,
@ -1121,31 +925,6 @@
"wrappy": "1" "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": { "node_modules/p-limit": {
"version": "2.3.0", "version": "2.3.0",
"dev": true, "dev": true,
@ -1179,54 +958,6 @@
"node": ">=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": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"dev": true, "dev": true,
@ -1243,15 +974,6 @@
"node": ">=0.10.0" "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": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"dev": true, "dev": true,
@ -1355,36 +1077,6 @@
"randombytes": "^2.1.0" "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": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"dev": true, "dev": true,
@ -1408,18 +1100,6 @@
"version": "0.9.27", "version": "0.9.27",
"license": "MIT" "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": { "node_modules/tapable": {
"version": "2.2.1", "version": "2.2.1",
"dev": true, "dev": true,
@ -1560,18 +1240,6 @@
"node": ">= 8" "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": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"dev": true, "dev": true,
@ -1677,15 +1345,6 @@
"node": ">=4.2.0" "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": { "node_modules/uri-js": {
"version": "4.4.1", "version": "4.4.1",
"dev": true, "dev": true,
@ -1829,18 +1488,6 @@
"url": "https://opencollective.com/webpack" "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": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"dev": true, "dev": true,
@ -2076,12 +1723,6 @@
"dev": true, "dev": true,
"peer": 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": { "acorn": {
"version": "8.7.0", "version": "8.7.0",
"dev": true, "dev": true,
@ -2093,15 +1734,6 @@
"peer": true, "peer": true,
"requires": {} "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": { "axios": {
"version": "0.21.4", "version": "0.21.4",
"dev": true, "dev": true,
@ -2159,17 +1791,6 @@
"dev": true, "dev": true,
"peer": 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": { "chownr": {
"version": "2.0.0", "version": "2.0.0",
"dev": true "dev": true
@ -2179,27 +1800,6 @@
"dev": true, "dev": true,
"peer": 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": { "commondir": {
"version": "1.0.1", "version": "1.0.1",
"dev": true "dev": true
@ -2208,27 +1808,6 @@
"version": "0.0.1", "version": "0.0.1",
"dev": true "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": { "detect-libc": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@ -2297,12 +1876,6 @@
"dev": true, "dev": true,
"peer": 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": { "eslint-scope": {
"version": "5.1.1", "version": "5.1.1",
"dev": true, "dev": true,
@ -2371,30 +1944,10 @@
"path-exists": "^4.0.0" "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": { "follow-redirects": {
"version": "1.14.9", "version": "1.14.9",
"dev": true "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": { "fs-minipass": {
"version": "2.1.0", "version": "2.1.0",
"dev": true, "dev": true,
@ -2427,12 +1980,6 @@
"version": "4.2.10", "version": "4.2.10",
"dev": true "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": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"dev": true, "dev": true,
@ -2445,40 +1992,10 @@
"version": "2.0.4", "version": "2.0.4",
"dev": true "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": { "is-number": {
"version": "7.0.0", "version": "7.0.0",
"dev": true "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": { "jest-worker": {
"version": "27.5.1", "version": "27.5.1",
"dev": true, "dev": true,
@ -2509,24 +2026,6 @@
"dev": true, "dev": true,
"peer": 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": { "loader-runner": {
"version": "4.3.0", "version": "4.3.0",
"dev": true, "dev": true,
@ -2592,12 +2091,6 @@
"brace-expansion": "^1.1.7" "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": { "minipass": {
"version": "3.1.6", "version": "3.1.6",
"dev": true, "dev": true,
@ -2622,12 +2115,6 @@
"dev": true, "dev": true,
"peer": 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": { "node-releases": {
"version": "2.0.3", "version": "2.0.3",
"dev": true, "dev": true,
@ -2640,22 +2127,6 @@
"wrappy": "1" "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": { "p-limit": {
"version": "2.3.0", "version": "2.3.0",
"dev": true, "dev": true,
@ -2674,44 +2145,6 @@
"version": "2.2.0", "version": "2.2.0",
"dev": true "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": { "path-exists": {
"version": "4.0.0", "version": "4.0.0",
"dev": true "dev": true
@ -2720,12 +2153,6 @@
"version": "1.0.1", "version": "1.0.1",
"dev": true "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": { "picocolors": {
"version": "1.0.0", "version": "1.0.0",
"dev": true, "dev": true,
@ -2782,27 +2209,6 @@
"randombytes": "^2.1.0" "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": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"dev": true, "dev": true,
@ -2820,15 +2226,6 @@
"spectorjs": { "spectorjs": {
"version": "0.9.27" "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": { "tapable": {
"version": "2.2.1", "version": "2.2.1",
"dev": true "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": { "to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"dev": true, "dev": true,
@ -2983,12 +2371,6 @@
"version": "4.6.3", "version": "4.6.3",
"dev": true "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": { "uri-js": {
"version": "4.4.1", "version": "4.4.1",
"dev": true, "dev": true,
@ -3086,15 +2468,6 @@
"dev": true, "dev": true,
"peer": 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": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"dev": true "dev": true

View File

@ -65,3 +65,15 @@ pub async fn run(scheduler_ptr: *mut Scheduler<WebWorkerPoolScheduleMethod>) {
// std::mem::forget(scheduler); // 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);
}
}

View File

@ -1,5 +1,5 @@
use js_sys::{ArrayBuffer, Uint8Array}; use js_sys::{ArrayBuffer, Uint8Array};
use maplibre::io::source_client::HTTPClient; use maplibre::io::source_client::HttpClient;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
@ -61,7 +61,7 @@ impl Clone for WHATWGFetchHttpClient {
} }
#[async_trait(?Send)] #[async_trait(?Send)]
impl HTTPClient for WHATWGFetchHttpClient { impl HttpClient for WHATWGFetchHttpClient {
async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error> { async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error> {
self.fetch_bytes(url) self.fetch_bytes(url)
.await .await

View File

@ -5,9 +5,9 @@ use wasm_bindgen::prelude::*;
use maplibre::coords::TileCoords; use maplibre::coords::TileCoords;
use maplibre::io::scheduler::Scheduler; use maplibre::io::scheduler::Scheduler;
use maplibre::io::shared_thread_state::SharedThreadState;
use maplibre::io::TileRequestID; use maplibre::io::TileRequestID;
use maplibre::stages::SharedThreadState;
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {

View File

@ -8,7 +8,6 @@ use web_sys::Worker;
use maplibre::error::Error; use maplibre::error::Error;
use maplibre::io::scheduler::ScheduleMethod; use maplibre::io::scheduler::ScheduleMethod;
use maplibre::io::shared_thread_state::SharedThreadState;
use super::pool::WorkerPool; use super::pool::WorkerPool;
@ -35,19 +34,17 @@ impl WebWorkerPoolScheduleMethod {
} }
impl ScheduleMethod for WebWorkerPoolScheduleMethod { impl ScheduleMethod for WebWorkerPoolScheduleMethod {
fn schedule( fn schedule<T>(
&self, &self,
shared_thread_state: SharedThreadState, future_factory: impl (FnOnce() -> T) + Send + 'static,
future_factory: Box< ) -> Result<(), Error>
(dyn (FnOnce(SharedThreadState) -> Pin<Box<dyn Future<Output = ()> + 'static>>) where
+ Send T: Future<Output = ()> + 'static,
+ 'static), {
>,
) -> Result<(), Error> {
self.pool self.pool
.run(move || { .run(move || {
wasm_bindgen_futures::future_to_promise(async move { wasm_bindgen_futures::future_to_promise(async move {
future_factory(shared_thread_state).await; future_factory().await;
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}) })
}) })