Merge pull request #174 from maxammann/wasm-executor

* Experiment with single threaded executor

* Avoid exiting process

* Use custom profile and remove wasm-pack

* Update run configs

* Prepare JS lib

* Remove run config

* Disable geometry index

* Conditionally enable multithreaded implementation

* Add run config

* Add conditional compilation

* Introduce environment

* Remove ScheduleMethod and replace with Scheduler

* Introduce AsyncProcedureCall

* Update run config

* Add experiment for unsync scheduling

* Always import memory

* Add first working version which does not need shared memory on web

* Fix linux CI script

* Remove maximum memory limits

* Fix multithreading in web

* Fix tracy dependency versions

* Run formatter

* Add a lot of TODO notes

* Deduplicate workflow for more demos

* Fix formatting

* Selectively install targets

* Fix wasm-bindgen install

* Do not build std lib by default

* Restructure web module to include JS code for multithreaded and non-multithreaded

* Make multithreading named consistantly

* Add more jobs

* Do not rebuild std on check and test

* Mark functions unsafe

* Add runner for wasm32 again

* Fix check expression

* Finish CI setup

* Add type for the tag

* Update documentation

* Switch to TS config for webpack

* Fix android checks
This commit is contained in:
Max Ammann 2022-09-24 18:01:58 +02:00 committed by GitHub
commit 2696d8133b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 2273 additions and 1737 deletions

View File

@ -2,10 +2,20 @@
rustflags = [
# Enabled unstable APIs from web_sys
"--cfg=web_sys_unstable_apis",
# Enables features which are required for shared-memory
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
# Enables the possibility to import memory into wasm.
# Without --shared-memory it is not possible to use shared WebAssembly.Memory.
"-C", "link-args=--shared-memory --import-memory",
"-C", "link-args=--import-memory",
]
runner = 'wasm-bindgen-test-runner'
[profile.wasm-dev]
inherits = "dev"
opt-level = 's'
debug = true
debug-assertions = true
overflow-checks = true
panic = 'abort'
[profile.wasm-release]
inherits = "release"
opt-level = 's'
lto = true
panic = 'abort'

View File

@ -1,5 +1,5 @@
name: deploy
description: Deploy on maxammann.org
description: Deploy on cloudflare
inputs:
project:

View File

@ -17,7 +17,7 @@ jobs:
- uses: extractions/setup-just@v1
- name: Install toolchain
shell: bash
run: just default-toolchain
run: just stable-toolchain
- uses: Swatinem/rust-cache@v1
- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1

View File

@ -12,14 +12,16 @@ jobs:
- uses: extractions/setup-just@v1
- name: Install toolchain
shell: bash
run: just default-toolchain
run: |
just stable-toolchain
just stable-targets x86_64-unknown-linux-gnu
- uses: Swatinem/rust-cache@v1
- name: Install Dependencies
shell: bash
run: sudo apt-get install -y libwayland-dev libxkbcommon-dev # Required for winit
- name: Build
shell: bash
run: cargo build -p maplibre-demo
run: cargo build -p maplibre-demo --release --target x86_64-unknown-linux-gnu
- name: Check
shell: bash
run: just check maplibre-demo x86_64-unknown-linux-gnu
@ -29,4 +31,4 @@ jobs:
- uses: actions/upload-artifact@v2
with:
name: maplibre-rs
path: target/x86_64-unknown-linux-gnu/debug/maplibre-demo
path: target/x86_64-unknown-linux-gnu/release/maplibre-demo

View File

@ -14,7 +14,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install toolchain
shell: bash
run: just default-toolchain
run: |
just stable-toolchain
just stable-targets x86_64-apple-darwin
- uses: Swatinem/rust-cache@v1
- name: Build
shell: bash

View File

@ -12,7 +12,9 @@ jobs:
- uses: extractions/setup-just@v1
- name: Install toolchain
shell: bash
run: just default-toolchain
run: |
just stable-toolchain
just stable-targets x86_64-pc-windows-msvc
- uses: Swatinem/rust-cache@v1
- uses: ilammy/msvc-dev-cmd@v1 # Provide access to lib.exe
- name: Build

View File

@ -12,7 +12,9 @@ jobs:
- uses: extractions/setup-just@v1
- name: Install nightly toolchain
shell: bash
run: just nightly-toolchain-android
run: |
just nightly-toolchain
just nightly-targets x86_64-linux-android aarch64-linux-android i686-linux-android
- uses: Swatinem/rust-cache@v1
- name: Set NDK Version
shell: bash
@ -30,13 +32,13 @@ jobs:
run: |
env "AR_x86_64-linux-android=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" \
env "CC_x86_64-linux-android=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android30-clang" \
just check maplibre-android x86_64-linux-android
just nightly-check maplibre-android x86_64-linux-android ""
- name: Check aarch64
shell: bash
run: |
env "AR_aarch64-linux-android=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar" \
env "CC_aarch64-linux-android=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang" \
just check maplibre-android aarch64-linux-android
just nightly-check maplibre-android aarch64-linux-android ""
# FIXME: Requires cross-compilation
#- name: Test
# shell: bash

View File

@ -15,7 +15,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install toolchain
shell: bash
run: just default-toolchain
run: |
just stable-toolchain
just stable-targets x86_64-apple-darwin aarch64-apple-darwin x86_64-apple-ios aarch64-apple-ios aarch64-apple-ios-sim
- uses: Swatinem/rust-cache@v1
- name: Build
shell: bash

View File

@ -11,70 +11,58 @@ on:
deploy:
required: true
type: boolean
name:
required: true
type: string
webgl:
required: true
type: boolean
multithreaded:
required: true
type: boolean
jobs:
library-webgl:
name: Build WebGL
build:
name: Build
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: extractions/setup-just@v1
- name: Install nightly toolchain
shell: bash
run: just nightly-toolchain
run: |
just nightly-toolchain
just nightly-targets wasm32-unknown-unknown
- name: Install rust sources (build-std)
if: inputs.multithreaded
shell: bash
run: |
just nightly-install-src
- name: Install wasm-bindgen
shell: bash
run: |
# Install wasm-bindgen with test runner
cargo install wasm-bindgen-cli # We want the latest version, as Cargo uses the latest version of wasm-bindgen
- uses: Swatinem/rust-cache@v1
- name: Build lib
shell: bash
run: just web-lib build-webgl
run: just web-lib build --release ${{ inputs.webgl && '--webgl' || '' }} ${{ inputs.multithreaded && '--multithreaded' || '' }}
- name: Build demo
shell: bash
run: just web-demo build
- name: Check
shell: bash
run: just web-check "web-webgl"
run: just nightly-check web wasm32-unknown-unknown ${{ inputs.webgl && 'web-webgl' || '""' }}
- name: Test
shell: bash
run: |
# Install test runner
cargo install wasm-bindgen-cli # We want the latest version, as Cargo uses the latest version of wasm-bindgen
just web-test "web-webgl"
run: just web-test "web-webgl"
- uses: actions/upload-artifact@v2
with:
name: webgl-demo
path: web/demo/dist/
library-webgpu:
name: Build WebGPU
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: extractions/setup-just@v1
- name: Install nightly toolchain
shell: bash
run: just nightly-toolchain
- uses: Swatinem/rust-cache@v1
- name: Build lib
shell: bash
run: just web-lib build
- name: Build demo
shell: bash
run: just web-demo build
- name: Check
shell: bash
run: just web-check ""
- name: Test
shell: bash
run: |
# Install test runner
cargo install wasm-bindgen-cli # We want the latest version, as Cargo uses the latest version of wasm-bindgen
just web-test ""
- uses: actions/upload-artifact@v2
with:
name: webgpu-demo
name: ${{ inputs.name }}
path: web/demo/dist/
deploy:
needs: [library-webgl, library-webgpu]
needs: [build]
if: inputs.deploy
name: Deploy
runs-on: ubuntu-20.04
@ -82,13 +70,10 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
with:
name: webgl-demo
path: demo/webgl
- uses: actions/download-artifact@v2
with:
name: webgpu-demo
path: demo/webgpu
name: ${{ inputs.name }}
path: demo
- name: Set HTTP Headers for Cloudflare
if: inputs.multithreaded
shell: bash
run: |
echo "/*
@ -97,7 +82,7 @@ jobs:
- name: Deploy
uses: ./.github/actions/cloudflare-deploy
with:
project: maplibre-rs-demos
project: ${{ inputs.name }}
source: demo
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}

View File

@ -24,9 +24,28 @@ jobs:
secrets: inherit
library-android:
uses: ./.github/workflows/library-android.yml
library-web:
library-web-webgl:
uses: ./.github/workflows/library-web.yml
with:
name: maplibre-rs-demo-webgl
webgl: true
multithreaded: false
deploy: true
secrets: inherit
library-web-webgl-multithreaded:
uses: ./.github/workflows/library-web.yml
with:
name: maplibre-rs-demo-webgl-multithreaded
webgl: true
multithreaded: true
deploy: true
secrets: inherit
library-web-webgpu:
uses: ./.github/workflows/library-web.yml
with:
name: maplibre-rs-demo-webgpu
webgl: false
multithreaded: false
deploy: true
secrets: inherit
library-apple:

View File

@ -19,9 +19,26 @@ jobs:
deploy: false
library-android:
uses: ./.github/workflows/library-android.yml
library-web:
library-web-webgl:
uses: ./.github/workflows/library-web.yml
with:
name: maplibre-rs-demo-webgl
webgl: true
multithreaded: false
deploy: false
library-web-webgl-multithreaded:
uses: ./.github/workflows/library-web.yml
with:
name: maplibre-rs-demo-webgl-multithreaded
webgl: true
multithreaded: true
deploy: false
library-web-webgpu:
uses: ./.github/workflows/library-web.yml
with:
name: maplibre-rs-demo-webgpu
webgl: false
multithreaded: false
deploy: false
library-apple:
uses: ./.github/workflows/library-apple.yml

View File

@ -12,7 +12,9 @@ jobs:
- uses: extractions/setup-just@v1
- name: Install toolchain
shell: bash
run: just default-toolchain
run: |
just stable-toolchain
just stable-targets x86_64-unknown-linux-gnu
- uses: Swatinem/rust-cache@v1
- name: Install GPU Drivers
uses: ./.github/actions/install-driver

View File

@ -12,7 +12,7 @@ jobs:
- uses: extractions/setup-just@v1
- name: Install toolchain
shell: bash
run: just default-toolchain
run: just stable-toolchain
- uses: Swatinem/rust-cache@v1
- name: Format
shell: bash

View File

@ -12,7 +12,9 @@ jobs:
- uses: extractions/setup-just@v1
- name: Install toolchain
shell: bash
run: just default-toolchain
run: |
just stable-toolchain
just stable-targets x86_64-unknown-linux-gnu
- uses: Swatinem/rust-cache@v1
- name: Install GPU Drivers
uses: ./.github/actions/install-driver

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build WASM" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="build -p web --features web-webgl --lib --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort" />
<configuration default="false" name="Build WASM (single-threaded)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="build -p web --features web-webgl --lib --profile wasm-dev --target wasm32-unknown-unknown" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run demo (debug " type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo" />
<configuration default="false" name="Run demo (debug)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo -- headed" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
@ -9,7 +9,9 @@
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs />
<envs>
<env name="RUST_LOG" value="debug" />
</envs>
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">

View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run headed demo (debug+enable-tracing)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<configuration default="false" name="Run demo (debug+enable-tracing)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo --features trace -- headed" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />

View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run headed demo (release+enable-tracing)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<configuration default="false" name="Run demo (release+enable-tracing)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo --release --features trace -- headed" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />

View File

@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run headless demo (debug+enable-tracing) " type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="run -p maplibre-demo --features trace -- headless" />
<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

@ -20,7 +20,6 @@ lto = true
codegen-units = 1
opt-level = 's'
panic = "abort"
strip = "debuginfo"
[profile.bench]

View File

@ -3,13 +3,11 @@ use std::ffi::CString;
use jni::{objects::JClass, JNIEnv};
use log::Level;
use maplibre::{
platform::{
http_client::ReqwestHttpClient, run_multithreaded, schedule_method::TokioScheduleMethod,
},
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
render::settings::{Backends, WgpuSettings},
MapBuilder,
};
use maplibre_winit::winit::WinitMapWindowConfig;
use maplibre_winit::winit::{run_headed_map, WinitMapWindowConfig};
#[cfg(not(target_os = "android"))]
compile_error!("android works only on android.");
@ -18,20 +16,8 @@ compile_error!("android works only on android.");
pub fn android_main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
run_multithreaded(async {
MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre android".to_string()))
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.with_wgpu_settings(WgpuSettings {
backends: Some(Backends::VULKAN),
..WgpuSettings::default()
})
.build()
.initialize()
.await
.run()
})
// TODO: Maybe requires: Some(Backends::VULKAN)
run_headed_map(None);
}
#[no_mangle]

View File

@ -1,10 +1,12 @@
use maplibre::{
platform::{
http_client::ReqwestHttpClient, run_multithreaded, schedule_method::TokioScheduleMethod,
},
io::apc::SchedulerAsyncProcedureCall,
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
MapBuilder,
};
use maplibre_winit::winit::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
use maplibre_winit::winit::{
run_headed_map, WinitEnvironment, WinitEventLoop, WinitMapWindow, WinitMapWindowConfig,
WinitWindow,
};
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
compile_error!("apple works only on macOS and iOS.");
@ -13,14 +15,5 @@ compile_error!("apple works only on macOS and iOS.");
pub fn maplibre_apple_main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
run_multithreaded(async {
MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre apple".to_string()))
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.build()
.initialize()
.await
.run()
})
run_headed_map(None);
}

View File

@ -38,7 +38,7 @@ fn parse_tile(c: &mut Criterion) {
.unwrap()
.into_boxed_slice();
ParseTile::default().process(
(request, 0, data),
(request, data),
&mut PipelineContext::new(DummyPipelineProcessor),
);
})
@ -58,7 +58,7 @@ fn tessellate_tile(c: &mut Criterion) {
.unwrap()
.into_boxed_slice();
let parsed = ParseTile::default().process(
(request, 0, data),
(request, data),
&mut PipelineContext::new(DummyPipelineProcessor),
);

View File

@ -4,16 +4,15 @@ use criterion::{criterion_group, criterion_main, Criterion};
use maplibre::{
coords::{WorldTileCoords, ZoomLevel},
error::Error,
headless::{utils::HeadlessPipelineProcessor, HeadlessMapWindowConfig},
headless::{utils::HeadlessPipelineProcessor, HeadlessEnvironment, HeadlessMapWindowConfig},
io::{
apc::SchedulerAsyncProcedureCall,
pipeline::{PipelineContext, Processable},
source_client::HttpSourceClient,
tile_pipelines::build_vector_tile_pipeline,
TileRequest,
},
platform::{
http_client::ReqwestHttpClient, run_multithreaded, schedule_method::TokioScheduleMethod,
},
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
render::settings::{RendererSettings, TextureFormat},
window::WindowSize,
MapBuilder,
@ -22,12 +21,20 @@ use maplibre::{
fn headless_render(c: &mut Criterion) {
c.bench_function("headless_render", |b| {
let mut map = run_multithreaded(async {
let mut map = MapBuilder::new()
let client = ReqwestHttpClient::new(None);
let mut map = MapBuilder::<
HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>,
>::new()
.with_map_window_config(HeadlessMapWindowConfig {
size: WindowSize::new(1000, 1000).unwrap(),
})
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.with_http_client(client.clone())
.with_apc(SchedulerAsyncProcedureCall::new(
client,
TokioScheduler::new(),
))
.with_scheduler(TokioScheduler::new())
.with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,
..RendererSettings::default()

View File

@ -11,7 +11,7 @@ module it can resolve WebAssembly files or WebWorkers dynamically.
The following syntax is used to resolve referenced WebWorkers:
```ts
new Worker(new URL("./pool.worker.ts", import.meta.url), {
new Worker(new URL("./multithreaded-pool.worker.ts", import.meta.url), {
type: 'module'
});
```
@ -107,7 +107,7 @@ See config in `web/lib/build.mjs` for an example usage.
### Babel & TypeScript
Babel and TypeScript both can produce ESM modules, but they **fail with transforming references within the source code**
like `new URL("./pool.worker.ts", import.meta.url)`. There exist some Babel plugins, but none of them is stable.
like `new URL("./multithreaded-pool.worker.ts", import.meta.url)`. There exist some Babel plugins, but none of them is stable.
Therefore, we actually need a proper bundler which supports outputting ESM modules.
The only stable solution to this is Parcel. Parcel also has good documentation around the bundling of WebWorkers.

View File

@ -102,18 +102,35 @@ click on run. This will start the MacOS application.
## Web (WebGL, WebGPU)
If you have a browser which already supports a recent version of the WebGPU specification then you can start a
development server using the following commands.
You need to first build the library for WebGL or WebGPU. Optionally, you can also enabled multi-threading support,
which requires that the library is used in a secure environment:
[isSecureContext](https://developer.mozilla.org/en-US/docs/Web/API/isSecureContext)
and [crossOriginIsolated](https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated).
The demo runs this such an environment.
If you have a browser which already supports a recent version of the WebGPU specification then you can build the library
with WebGPU:
```bash
cd web
npm run start
just web-lib build # WebGPU
```
If you want to run maplibre-rs with WebGL which is supported on every major browser, then you have to use the following
command.
If not, then you must enable WebGL support:
```bash
just web-lib build --webgl # WebGL
just web-lib build --webgl --multithreaded # WebGL + multithreaded
```
Instead of building it is also possible to watch for changes. The same flags like with `web-lib build` are supported:
```bash
just web-lib watch --webgl
```
After building the library you can run the demo server:
```bash
just web-lib build
just web-demo start
```

View File

@ -5,15 +5,38 @@
set shell := ["bash", "-c"]
export NIGHTLY_TOOLCHAIN := "nightly-2022-07-03"
export STABLE_TOOLCHAIN := "1.62"
export CARGO_TERM_COLOR := "always"
export RUST_BACKTRACE := "1"
install-clippy:
rustup component add clippy
install-nightly-clippy:
stable-toolchain:
rustup toolchain install $STABLE_TOOLCHAIN
stable-targets *FLAGS:
rustup toolchain install $STABLE_TOOLCHAIN --target {{FLAGS}}
stable-install-clippy:
rustup component add clippy --toolchain $STABLE_TOOLCHAIN
nightly-toolchain:
rustup toolchain install $NIGHTLY_TOOLCHAIN
nightly-targets *FLAGS:
rustup toolchain install $NIGHTLY_TOOLCHAIN --target {{FLAGS}}
nightly-install-rustfmt: nightly-toolchain
rustup component add rustfmt --toolchain $NIGHTLY_TOOLCHAIN
nightly-install-src: nightly-toolchain
rustup component add rust-src --toolchain $NIGHTLY_TOOLCHAIN
nightly-install-clippy:
rustup component add clippy --toolchain $NIGHTLY_TOOLCHAIN
fixup:
cargo clippy --no-deps -p maplibre --fix
cargo clippy --allow-dirty --no-deps -p maplibre-winit --fix
@ -27,36 +50,25 @@ fixup:
cargo clippy --allow-dirty --no-deps -p maplibre-winit --target x86_64-linux-android --fix
cargo clippy --allow-dirty --no-deps -p maplibre-android --target x86_64-linux-android --fix
check PROJECT ARCH: install-clippy
check PROJECT ARCH: stable-install-clippy
cargo clippy --no-deps -p {{PROJECT}} --target {{ARCH}}
nightly-check PROJECT ARCH FEATURES: nightly-toolchain nightly-install-clippy
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo clippy --no-deps -p {{PROJECT}} --features "{{FEATURES}}" --target {{ARCH}}
test PROJECT ARCH:
cargo test -p {{PROJECT}} --target {{ARCH}}
benchmark:
cargo bench -p benchmarks
install-rustfmt: nightly-toolchain
rustup component add rustfmt --toolchain $NIGHTLY_TOOLCHAIN
fmt: install-rustfmt
fmt: nightly-install-rustfmt
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo fmt
fmt-check: install-rustfmt
fmt-check: nightly-install-rustfmt
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo fmt -- --check
default-toolchain:
# Setups the toolchain from rust-toolchain.toml
cargo --version > /dev/null
nightly-toolchain:
rustup install $NIGHTLY_TOOLCHAIN
rustup component add rust-src --toolchain $NIGHTLY_TOOLCHAIN
nightly-toolchain-android: nightly-toolchain
rustup target add --toolchain $NIGHTLY_TOOLCHAIN x86_64-linux-android
rustup target add --toolchain $NIGHTLY_TOOLCHAIN aarch64-linux-android
rustup target add --toolchain $NIGHTLY_TOOLCHAIN i686-linux-android
web-install PROJECT:
cd web/{{PROJECT}} && npm install
@ -65,19 +77,16 @@ web-install PROJECT:
# Example: just web-lib build-webgl
# Example: just web-lib watch
# Example: just web-lib watch-webgl
web-lib TARGET: nightly-toolchain (web-install "lib")
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd web/lib && npm run {{TARGET}}
web-lib TARGET *FLAGS: nightly-toolchain (web-install "lib")
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd web/lib && npm run {{TARGET}} -- {{FLAGS}}
# Example: just web-demo start
# Example: just web-demo build
web-demo TARGET: (web-install "demo")
cd web/demo && npm run {{TARGET}}
web-check FEATURES: nightly-toolchain install-nightly-clippy
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo clippy --no-deps -p web --features "{{FEATURES}}" --target wasm32-unknown-unknown -Z build-std=std,panic_abort
web-demo TARGET *FLAGS: (web-install "demo")
cd web/demo && npm run {{TARGET}} -- {{FLAGS}}
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
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo test -p web --features "{{FEATURES}}" --target wasm32-unknown-unknown
#profile-bench:
# cargo flamegraph --bench render -- --bench

View File

@ -10,7 +10,7 @@ readme = "../README.md"
[features]
web-webgl = ["maplibre/web-webgl"]
trace = ["maplibre/trace", "tracing-subscriber", "tracing-tracy", "tracy-client"]
trace = ["maplibre/trace"]
[dependencies]
env_logger = "0.9.0"
@ -19,10 +19,4 @@ maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
tile-grid = "0.3"
tracing = "0.1.35"
tracing-subscriber = { version = "0.3.14", optional = true }
tracing-tracy = { version = "0.10", optional = true }
tracy-client = { version = "0.14", optional = true }
clap = { version = "3.2.12", features = ["derive"] }

View File

@ -1,16 +0,0 @@
use maplibre::{
platform::{http_client::ReqwestHttpClient, schedule_method::TokioScheduleMethod},
MapBuilder,
};
use maplibre_winit::winit::WinitMapWindowConfig;
pub async fn run_headed() {
MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.build()
.initialize()
.await
.run()
}

View File

@ -1,22 +1,30 @@
use maplibre::{
coords::{LatLon, WorldTileCoords},
error::Error,
headless::HeadlessMapWindowConfig,
platform::{http_client::ReqwestHttpClient, schedule_method::TokioScheduleMethod},
headless::{HeadlessEnvironment, HeadlessMapWindowConfig},
io::apc::SchedulerAsyncProcedureCall,
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
render::settings::{RendererSettings, TextureFormat},
util::grid::google_mercator,
window::WindowSize,
MapBuilder,
};
use maplibre_winit::winit::WinitEnvironment;
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
let mut map = MapBuilder::new()
let client = ReqwestHttpClient::new(None);
let mut map =
MapBuilder::<HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>>::new()
.with_map_window_config(HeadlessMapWindowConfig {
size: WindowSize::new(tile_size, tile_size).unwrap(),
})
.with_http_client(ReqwestHttpClient::new(None))
.with_schedule_method(TokioScheduleMethod::new())
.with_http_client(client.clone())
.with_apc(SchedulerAsyncProcedureCall::new(
client,
TokioScheduler::new(),
)) // FIXME (wasm-executor): avoid passing client and scheduler here
.with_scheduler(TokioScheduler::new())
.with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,
..RendererSettings::default()

View File

@ -2,21 +2,12 @@ use std::io::ErrorKind;
use clap::{builder::ValueParser, Parser, Subcommand};
use maplibre::{coords::LatLon, platform::run_multithreaded};
use maplibre_winit::winit::run_headed_map;
use crate::{headed::run_headed, headless::run_headless};
use crate::headless::run_headless;
mod headed;
mod headless;
#[cfg(feature = "trace")]
fn enable_tracing() {
use tracing_subscriber::{layer::SubscriberExt, Registry};
let subscriber = Registry::default().with(tracing_tracy::TracyLayer::new());
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
}
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)]
@ -63,16 +54,14 @@ fn main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
#[cfg(feature = "trace")]
enable_tracing();
maplibre::platform::trace::enable_tracing();
let cli = Cli::parse();
// You can check for the existence of subcommands, and if found use their
// matches just as you would the top level cmd
match &cli.command {
Commands::Headed {} => {
run_multithreaded(async { run_headed().await });
}
Commands::Headed {} => run_headed_map(None),
Commands::Headless {
tile_size,
min,

View File

@ -21,5 +21,5 @@ wasm-bindgen-futures = "0.4.31"
maplibre = { path = "../maplibre", version = "0.0.2" }
winit = { version = "0.27.2", default-features = false }
cgmath = "0.18.0"
instant = { version = "0.1.12", features = ["wasm-bindgen"] } # FIXME: Untrusted dependency
instant = { version = "0.1.12", features = ["wasm-bindgen"] } # TODO: Untrusted dependency
log = "0.4.17"

View File

@ -71,9 +71,9 @@ impl UpdateState for QueryHandler {
&inverted_view_proj,
false,
) {
// TODO reenable
/*state
.scheduler()
.schedule_method()
.schedule(state.scheduler(), move |thread_local| async move {
if let Some(geometries) = thread_local.query_point(
&WorldCoords {

View File

@ -1,7 +1,15 @@
use std::{cell::RefCell, marker::PhantomData, ops::Deref, rc::Rc};
use instant::Instant;
use maplibre::{
environment::Environment,
error::Error,
io::{scheduler::ScheduleMethod, source_client::HttpClient},
io::{
apc::{AsyncProcedureCall, Message},
scheduler::Scheduler,
source_client::HttpClient,
transferables::{DefaultTransferables, Transferables},
},
map_schedule::InteractiveMapSchedule,
window::{EventLoop, HeadedMapWindow, MapWindowConfig},
};
@ -52,28 +60,48 @@ pub struct WinitMapWindow {
event_loop: Option<WinitEventLoop>,
}
pub type WinitWindow = winit::window::Window;
pub type WinitEventLoop = winit::event_loop::EventLoop<()>;
impl WinitMapWindow {
pub fn take_event_loop(&mut self) -> Option<WinitEventLoop> {
self.event_loop.take()
}
}
pub type WinitWindow = winit::window::Window;
pub type WinitEventLoop = winit::event_loop::EventLoop<()>;
pub struct WinitEnvironment<
S: Scheduler,
HC: HttpClient,
T: Transferables,
APC: AsyncProcedureCall<T, HC>,
> {
phantom_s: PhantomData<S>,
phantom_hc: PhantomData<HC>,
phantom_t: PhantomData<T>,
phantom_apc: PhantomData<APC>,
}
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
for WinitEnvironment<S, HC, T, APC>
{
type MapWindowConfig = WinitMapWindowConfig;
type AsyncProcedureCall = APC;
type Scheduler = S;
type HttpClient = HC;
type Transferables = T;
}
///Main (platform-specific) main loop which handles:
///* Input (Mouse/Keyboard)
///* Platform Events like suspend/resume
///* Render a new frame
impl<MWC, SM, HC> EventLoop<MWC, SM, HC> for WinitMapWindow
impl<E: Environment> EventLoop<E> for WinitMapWindow
where
MWC: MapWindowConfig<MapWindow = WinitMapWindow>,
SM: ScheduleMethod,
HC: HttpClient,
E::MapWindowConfig: MapWindowConfig<MapWindow = WinitMapWindow>,
{
fn run(
mut self,
mut map_schedule: InteractiveMapSchedule<MWC, SM, HC>,
map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
max_frames: Option<u64>,
) {
let mut last_render_time = Instant::now();
@ -84,6 +112,8 @@ where
self.take_event_loop()
.unwrap()
.run(move |event, _, control_flow| {
let mut map_schedule = map_schedule.deref().borrow_mut();
#[cfg(target_os = "android")]
if !map_schedule.is_initialized() && event == Event::Resumed {
use tokio::{runtime::Handle, task};

View File

@ -3,10 +3,16 @@
//! * Platform Events like suspend/resume
//! * Render a new frame
use maplibre::window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};
use maplibre::{
io::apc::SchedulerAsyncProcedureCall,
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
MapBuilder,
};
use winit::window::WindowBuilder;
use super::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
use crate::winit::WinitEnvironment;
impl MapWindow for WinitMapWindow {
fn size(&self) -> WindowSize {
@ -47,3 +53,21 @@ impl MapWindowConfig for WinitMapWindowConfig {
}
}
}
pub fn run_headed_map(cache_path: Option<String>) {
run_multithreaded(async {
let client = ReqwestHttpClient::new(cache_path);
MapBuilder::<WinitEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>>::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(client.clone())
.with_apc(SchedulerAsyncProcedureCall::new(
client,
TokioScheduler::new(),
))
.with_scheduler(TokioScheduler::new())
.build()
.initialize()
.await
.run()
})
}

View File

@ -19,6 +19,7 @@ headless = ["png"]
[target.'cfg(any(target_os = "macos", target_os = "ios", target_os = "linux", target_os = "android", target_os = "windows"))'.dependencies]
tokio = { version = "1.20.1", features = ["macros", "rt", "rt-multi-thread", "sync", "time"] }
tokio-util = { version = "0.7.1", features = ["rt"] }
env_logger = "0.9.0"
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "gzip"] }
reqwest-middleware-cache = "0.1.1" # FIXME: Untrusted dependency
@ -32,7 +33,7 @@ reqwest = { version = "0.11.11", default-features = false, features = ["rustls-t
[dependencies]
async-trait = "0.1.57"
instant = { version = "0.1.12", features = ["wasm-bindgen"] } # FIXME: Untrusted dependency
instant = { version = "0.1.12", features = ["wasm-bindgen"] } # TODO: Untrusted dependency
# Tracing
tracing = "0.1.36"

View File

@ -6,7 +6,9 @@ use std::{
fmt::{Display, Formatter},
};
use bytemuck_derive::{Pod, Zeroable};
use cgmath::{num_traits::Pow, AbsDiffEq, Matrix4, Point3, Vector3};
use serde::{Deserialize, Serialize};
use crate::{
style::source::TileAddressingScheme,
@ -67,7 +69,23 @@ impl fmt::Debug for Quadkey {
}
}
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone, Debug, Default)]
// FIXME: does Pod and Zeroable make sense?
#[derive(
Ord,
PartialOrd,
Eq,
PartialEq,
Hash,
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
Pod,
Zeroable,
)]
#[repr(C)]
pub struct ZoomLevel(u8);
impl ZoomLevel {
@ -83,7 +101,7 @@ impl std::ops::Add<u8> for ZoomLevel {
type Output = ZoomLevel;
fn add(self, rhs: u8) -> Self::Output {
let zoom_level = self.0.checked_add(rhs).unwrap();
let zoom_level = self.0.checked_add(rhs).expect("zoom level overflowed");
ZoomLevel(zoom_level)
}
}
@ -92,7 +110,7 @@ impl std::ops::Sub<u8> for ZoomLevel {
type Output = ZoomLevel;
fn sub(self, rhs: u8) -> Self::Output {
let zoom_level = self.0.checked_sub(rhs).unwrap();
let zoom_level = self.0.checked_sub(rhs).expect("zoom level underflowed");
ZoomLevel(zoom_level)
}
}
@ -289,7 +307,22 @@ impl From<(u32, u32, ZoomLevel)> for TileCoords {
/// # Coordinate System Origin
///
/// The origin of the coordinate system is in the upper-left corner.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
// FIXME: does Zeroable make sense?
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Default,
Serialize,
Deserialize,
Zeroable,
)]
#[repr(C)]
pub struct WorldTileCoords {
pub x: i32,
pub y: i32,

View File

@ -0,0 +1,19 @@
use crate::{
io::{
apc::AsyncProcedureCall,
transferables::{
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, Transferables,
},
},
HttpClient, MapWindowConfig, Scheduler,
};
pub trait Environment: 'static {
type MapWindowConfig: MapWindowConfig;
type AsyncProcedureCall: AsyncProcedureCall<Self::Transferables, Self::HttpClient>;
type Scheduler: Scheduler;
type HttpClient: HttpClient;
type Transferables: Transferables;
}

View File

@ -4,6 +4,7 @@ use std::{
future::Future,
io::Write,
iter,
marker::PhantomData,
ops::{Deref, Range},
sync::Arc,
};
@ -17,11 +18,12 @@ use crate::{
error::Error,
headless::utils::HeadlessPipelineProcessor,
io::{
apc::{AsyncProcedureCall, SchedulerAsyncProcedureCall},
pipeline::{PipelineContext, Processable},
source_client::HttpSourceClient,
tile_pipelines::build_vector_tile_pipeline,
tile_repository::{StoredLayer, TileRepository},
tile_request_state::TileRequestState,
transferables::{DefaultTransferables, Transferables},
TileRequest,
},
render::{
@ -35,7 +37,7 @@ use crate::{
RenderState,
},
schedule::{Schedule, Stage},
HttpClient, MapWindow, MapWindowConfig, Renderer, ScheduleMethod, Scheduler, Style, WindowSize,
Environment, HttpClient, MapWindow, MapWindowConfig, Renderer, Scheduler, Style, WindowSize,
};
pub struct HeadlessMapWindowConfig {
@ -60,56 +62,57 @@ impl MapWindow for HeadlessMapWindow {
}
}
pub struct HeadlessMap<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
pub struct HeadlessEnvironment<
S: Scheduler,
HC: HttpClient,
{
pub map_schedule: HeadlessMapSchedule<MWC, SM, HC>,
pub window: MWC::MapWindow,
T: Transferables,
APC: AsyncProcedureCall<T, HC>,
> {
phantom_s: PhantomData<S>,
phantom_hc: PhantomData<HC>,
phantom_t: PhantomData<T>,
phantom_apc: PhantomData<APC>,
}
impl<MWC, SM, HC> HeadlessMap<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
for HeadlessEnvironment<S, HC, T, APC>
{
pub fn map_schedule_mut(&mut self) -> &mut HeadlessMapSchedule<MWC, SM, HC> {
type MapWindowConfig = HeadlessMapWindowConfig;
type AsyncProcedureCall = APC;
type Scheduler = S;
type HttpClient = HC;
type Transferables = T;
}
pub struct HeadlessMap<E: Environment> {
pub map_schedule: HeadlessMapSchedule<E>,
pub window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
}
impl<E: Environment> HeadlessMap<E> {
pub fn map_schedule_mut(&mut self) -> &mut HeadlessMapSchedule<E> {
&mut self.map_schedule
}
}
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
pub struct HeadlessMapSchedule<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
map_window_config: MWC,
pub struct HeadlessMapSchedule<E: Environment> {
map_window_config: E::MapWindowConfig,
pub map_context: MapContext,
schedule: Schedule,
scheduler: Scheduler<SM>,
http_client: HC,
tile_request_state: TileRequestState,
scheduler: E::Scheduler,
http_client: E::HttpClient,
}
impl<MWC, SM, HC> HeadlessMapSchedule<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
impl<E: Environment> HeadlessMapSchedule<E> {
pub fn new(
map_window_config: MWC,
map_window_config: E::MapWindowConfig,
window_size: WindowSize,
renderer: Renderer,
scheduler: Scheduler<SM>,
http_client: HC,
scheduler: E::Scheduler,
http_client: E::HttpClient,
style: Style,
) -> Self {
let view_state = ViewState::new(
@ -122,12 +125,12 @@ where
let tile_repository = TileRepository::new();
let mut schedule = Schedule::default();
let mut graph = create_default_render_graph().unwrap();
let draw_graph = graph.get_sub_graph_mut(draw_graph::NAME).unwrap();
let mut graph = create_default_render_graph().unwrap(); // TODO: remove unwrap
let draw_graph = graph.get_sub_graph_mut(draw_graph::NAME).unwrap(); // TODO: remove unwrap
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
draw_graph
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
.unwrap();
.unwrap(); // TODO: remove unwrap
register_default_render_stages(graph, &mut schedule);
@ -147,7 +150,6 @@ where
schedule,
scheduler,
http_client,
tile_request_state: Default::default(),
}
}
@ -160,10 +162,10 @@ where
pub fn schedule(&self) -> &Schedule {
&self.schedule
}
pub fn scheduler(&self) -> &Scheduler<SM> {
pub fn scheduler(&self) -> &E::Scheduler {
&self.scheduler
}
pub fn http_client(&self) -> &HC {
pub fn http_client(&self) -> &E::HttpClient {
&self.http_client
}
@ -176,13 +178,13 @@ where
.filter_map(|layer| layer.source_layer.clone())
.collect();
let http_source_client: HttpSourceClient<HC> =
let http_source_client: HttpSourceClient<E::HttpClient> =
HttpSourceClient::new(self.http_client.clone());
let data = http_source_client
.fetch(&coords)
.await
.unwrap()
.unwrap() // TODO: remove unwrap
.into_boxed_slice();
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
@ -193,15 +195,11 @@ where
layers: source_layers,
};
let request_id = self
.tile_request_state
.start_tile_request(request.clone())?;
pipeline.process((request, request_id, data), &mut pipeline_context);
self.tile_request_state.finish_tile_request(request_id);
pipeline.process((request, data), &mut pipeline_context);
let mut processor = pipeline_context
.take_processor::<HeadlessPipelineProcessor>()
.unwrap();
.unwrap(); // TODO: remove unwrap
if let Eventually::Initialized(pool) = self.map_context.renderer.state.buffer_pool_mut() {
pool.clear();
@ -210,6 +208,9 @@ where
self.map_context.tile_repository.clear();
while let Some(layer) = processor.layers.pop() {
self.map_context
.tile_repository
.create_tile(&layer.get_coords());
self.map_context
.tile_repository
.put_tessellated_layer(layer);
@ -261,7 +262,7 @@ impl Node for CopySurfaceBufferNode {
std::num::NonZeroU32::new(
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
)
.unwrap(),
.unwrap(), // TODO: remove unwrap
),
rows_per_image: None,
},
@ -340,9 +341,9 @@ pub mod utils {
) {
self.layers.push(StoredLayer::TessellatedLayer {
coords: *coords,
layer_name: layer_data.name,
buffer,
feature_indices,
layer_data,
})
}
}

121
maplibre/src/io/apc.rs Normal file
View File

@ -0,0 +1,121 @@
use std::{
collections::HashMap,
future::Future,
pin::Pin,
sync::{
mpsc,
mpsc::{Receiver, Sender},
},
};
use serde::{Deserialize, Serialize};
use crate::{
coords::WorldTileCoords,
io::{
source_client::{HttpSourceClient, SourceClient},
transferables::{DefaultTransferables, Transferables},
TileRequest,
},
Environment, HttpClient, Scheduler,
};
/// The result of the tessellation of a tile.
/// `TessellatedLayer` contains the result of the tessellation for a specific layer, otherwise
/// `UnavailableLayer` if the layer doesn't exist.
#[derive(Clone)]
pub enum Message<T: Transferables> {
TileTessellated(T::TileTessellated),
UnavailableLayer(T::UnavailableLayer),
TessellatedLayer(T::TessellatedLayer),
}
#[derive(Clone, Serialize, Deserialize)]
pub enum Input {
TileRequest(TileRequest),
}
pub trait Context<T: Transferables, HC: HttpClient>: Send + 'static {
fn send(&self, data: Message<T>);
fn source_client(&self) -> &SourceClient<HC>;
}
#[cfg(not(feature = "no-thread-safe-futures"))]
pub type AsyncProcedureFuture = Pin<Box<(dyn Future<Output = ()> + Send + 'static)>>;
#[cfg(feature = "no-thread-safe-futures")]
pub type AsyncProcedureFuture = Pin<Box<(dyn Future<Output = ()> + 'static)>>;
pub type AsyncProcedure<C> = fn(input: Input, context: C) -> AsyncProcedureFuture;
pub trait AsyncProcedureCall<T: Transferables, HC: HttpClient>: 'static {
type Context: Context<T, HC> + Send;
fn receive(&mut self) -> Option<Message<T>>;
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>);
}
#[derive(Clone)]
pub struct SchedulerContext<T: Transferables, HC: HttpClient> {
sender: Sender<Message<T>>,
source_client: SourceClient<HC>,
}
impl<T: Transferables, HC: HttpClient> Context<T, HC> for SchedulerContext<T, HC> {
fn send(&self, data: Message<T>) {
self.sender.send(data).unwrap(); // FIXME (wasm-executor): Remove unwrap
}
fn source_client(&self) -> &SourceClient<HC> {
&self.source_client
}
}
pub struct SchedulerAsyncProcedureCall<HC: HttpClient, S: Scheduler> {
channel: (
Sender<Message<DefaultTransferables>>,
Receiver<Message<DefaultTransferables>>,
),
http_client: HC,
scheduler: S,
}
impl<HC: HttpClient, S: Scheduler> SchedulerAsyncProcedureCall<HC, S> {
pub fn new(http_client: HC, scheduler: S) -> Self {
Self {
channel: mpsc::channel(),
http_client,
scheduler,
}
}
}
impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<DefaultTransferables, HC>
for SchedulerAsyncProcedureCall<HC, S>
{
type Context = SchedulerContext<DefaultTransferables, HC>;
fn receive(&mut self) -> Option<Message<DefaultTransferables>> {
let transferred = self.channel.1.try_recv().ok()?;
Some(transferred)
}
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
let sender = self.channel.0.clone();
let client = self.http_client.clone(); // FIXME (wasm-executor): do not clone each time
self.scheduler
.schedule(move || async move {
(procedure)(
input,
SchedulerContext {
sender,
source_client: SourceClient::Http(HttpSourceClient::new(client)),
},
)
.await;
})
.unwrap();
}
}

View File

@ -2,8 +2,11 @@
use std::{collections::HashSet, fmt};
use serde::{Deserialize, Serialize};
use crate::coords::WorldTileCoords;
pub mod apc;
pub mod geometry_index;
pub mod pipeline;
pub mod scheduler;
@ -12,12 +15,12 @@ pub mod source_client;
pub mod static_tile_fetcher;
pub mod tile_pipelines;
pub mod tile_repository;
pub mod tile_request_state;
pub mod transferables;
pub use geozero::mvt::tile::Layer as RawLayer;
/// A request for a tile at the given coordinates and in the given layers.
#[derive(Clone)]
#[derive(Clone, Serialize, Deserialize)]
pub struct TileRequest {
pub coords: WorldTileCoords,
pub layers: HashSet<String>,
@ -28,6 +31,3 @@ impl fmt::Debug for TileRequest {
write!(f, "TileRequest({}, {:?})", &self.coords, &self.layers)
}
}
/// The ID format for a tile request.
pub type TileRequestID = u32;

View File

@ -5,14 +5,14 @@ use geozero::mvt::tile;
use crate::{
coords::WorldTileCoords,
io::{geometry_index::IndexedGeometry, TileRequestID},
io::geometry_index::IndexedGeometry,
render::ShaderVertex,
tessellation::{IndexDataType, OverAlignedVertexBuffer},
};
/// Processes events which happen during the pipeline execution
pub trait PipelineProcessor: Downcast {
fn tile_finished(&mut self, _request_id: TileRequestID, _coords: &WorldTileCoords) {}
fn tile_finished(&mut self, _coords: &WorldTileCoords) {}
fn layer_unavailable(&mut self, _coords: &WorldTileCoords, _layer_name: &str) {}
fn layer_tesselation_finished(
&mut self,
@ -30,8 +30,6 @@ pub trait PipelineProcessor: Downcast {
}
}
impl_downcast!(PipelineProcessor);
/// Context which is available to each step within a [`DataPipeline`]
pub struct PipelineContext {
processor: Box<dyn PipelineProcessor>,

View File

@ -5,28 +5,8 @@ use std::future::Future;
use crate::error::Error;
/// Async/await scheduler.
pub struct Scheduler<SM>
where
SM: ScheduleMethod,
{
schedule_method: SM,
}
impl<SM> Scheduler<SM>
where
SM: ScheduleMethod,
{
pub fn new(schedule_method: SM) -> Self {
Self { schedule_method }
}
pub fn schedule_method(&self) -> &SM {
&self.schedule_method
}
}
/// Can schedule a task from a future factory and a shared state.
pub trait ScheduleMethod: 'static {
pub trait Scheduler: 'static {
#[cfg(not(feature = "no-thread-safe-futures"))]
fn schedule<T>(
&self,
@ -43,3 +23,14 @@ pub trait ScheduleMethod: 'static {
where
T: Future<Output = ()> + 'static;
}
pub struct NopScheduler;
impl Scheduler for NopScheduler {
fn schedule<T>(&self, future_factory: impl FnOnce() -> T + Send + 'static) -> Result<(), Error>
where
T: Future<Output = ()> + 'static,
{
Err(Error::Schedule)
}
}

View File

@ -46,7 +46,7 @@ impl StaticTileFetcher {
.ok_or_else(|| {
Error::Network("Failed to load tile from within the binary".to_string())
})?;
Ok(Vec::from(tile.contents())) // TODO: Unnecessary copy
Ok(Vec::from(tile.contents()))
}
}

View File

@ -7,7 +7,7 @@ use crate::{
io::{
geometry_index::IndexProcessor,
pipeline::{DataPipeline, PipelineContext, PipelineEnd, Processable},
TileRequest, TileRequestID,
TileRequest,
},
tessellation::{zero_tessellator::ZeroTessellator, IndexDataType},
};
@ -16,17 +16,17 @@ use crate::{
pub struct ParseTile;
impl Processable for ParseTile {
type Input = (TileRequest, TileRequestID, Box<[u8]>);
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile);
type Input = (TileRequest, Box<[u8]>);
type Output = (TileRequest, geozero::mvt::Tile);
// TODO (perf): Maybe force inline
fn process(
&self,
(tile_request, request_id, data): Self::Input,
(tile_request, 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)
(tile_request, tile)
}
}
@ -34,13 +34,13 @@ impl Processable for ParseTile {
pub struct IndexLayer;
impl Processable for IndexLayer {
type Input = (TileRequest, TileRequestID, geozero::mvt::Tile);
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile);
type Input = (TileRequest, geozero::mvt::Tile);
type Output = (TileRequest, geozero::mvt::Tile);
// TODO (perf): Maybe force inline
fn process(
&self,
(tile_request, request_id, tile): Self::Input,
(tile_request, tile): Self::Input,
context: &mut PipelineContext,
) -> Self::Output {
let index = IndexProcessor::new();
@ -48,7 +48,7 @@ impl Processable for IndexLayer {
context
.processor_mut()
.layer_indexing_finished(&tile_request.coords, index.get_geometries());
(tile_request, request_id, tile)
(tile_request, tile)
}
}
@ -56,13 +56,13 @@ impl Processable for IndexLayer {
pub struct TessellateLayer;
impl Processable for TessellateLayer {
type Input = (TileRequest, TileRequestID, geozero::mvt::Tile);
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile);
type Input = (TileRequest, geozero::mvt::Tile);
type Output = (TileRequest, geozero::mvt::Tile);
// TODO (perf): Maybe force inline
fn process(
&self,
(tile_request, request_id, mut tile): Self::Input,
(tile_request, mut tile): Self::Input,
context: &mut PipelineContext,
) -> Self::Output {
let coords = &tile_request.coords;
@ -118,11 +118,9 @@ impl Processable for TessellateLayer {
tracing::info!("tile tessellated at {} finished", &tile_request.coords);
context
.processor_mut()
.tile_finished(request_id, &tile_request.coords);
context.processor_mut().tile_finished(&tile_request.coords);
(tile_request, request_id, tile)
(tile_request, tile)
}
}
@ -159,7 +157,6 @@ mod tests {
coords: (0, 0, ZoomLevel::default()).into(),
layers: Default::default(),
},
0,
Box::new([0]),
),
&mut context,

View File

@ -18,10 +18,10 @@ pub enum StoredLayer {
},
TessellatedLayer {
coords: WorldTileCoords,
layer_name: String,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
/// Holds for each feature the count of indices.
feature_indices: Vec<u32>,
layer_data: tile::Layer,
},
}
@ -36,20 +36,29 @@ impl StoredLayer {
pub fn layer_name(&self) -> &str {
match self {
StoredLayer::UnavailableLayer { layer_name, .. } => layer_name.as_str(),
StoredLayer::TessellatedLayer { layer_data, .. } => &layer_data.name,
StoredLayer::TessellatedLayer { layer_name, .. } => layer_name.as_str(),
}
}
}
#[derive(Eq, PartialEq)]
pub enum TileStatus {
Pending,
Failed,
Success,
}
/// Stores multiple [StoredLayers](StoredLayer).
pub struct StoredTile {
layers: Vec<StoredLayer>,
status: TileStatus,
}
impl StoredTile {
pub fn new(first_layer: StoredLayer) -> Self {
pub fn new() -> Self {
Self {
layers: vec![first_layer],
layers: vec![],
status: TileStatus::Pending,
}
}
}
@ -84,7 +93,7 @@ impl TileRepository {
{
match entry {
btree_map::Entry::Vacant(entry) => {
entry.insert(StoredTile::new(layer));
panic!("Can not add a tessellated layer if no request has been started before.")
}
btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().layers.push(layer);
@ -105,6 +114,46 @@ impl TileRepository {
.map(|results| results.layers.iter())
}
/// Create a new tile.
pub fn create_tile(&mut self, coords: &WorldTileCoords) -> bool {
if let Some(entry) = coords.build_quad_key().map(|key| self.tree.entry(key)) {
match entry {
btree_map::Entry::Vacant(entry) => {
entry.insert(StoredTile::new());
}
_ => {}
}
}
true
}
/// Checks if a layer has been fetched.
pub fn needs_fetching(&self, coords: &WorldTileCoords) -> bool {
if let Some(_) = coords.build_quad_key().and_then(|key| self.tree.get(&key)) {
return false;
}
true
}
pub fn success(&mut self, coords: &WorldTileCoords) {
if let Some(cached_tile) = coords
.build_quad_key()
.and_then(|key| self.tree.get_mut(&key))
{
cached_tile.status = TileStatus::Success;
}
}
/// Checks if a layer has been fetched.
pub fn fail(&mut self, coords: &WorldTileCoords) {
if let Some(cached_tile) = coords
.build_quad_key()
.and_then(|key| self.tree.get_mut(&key))
{
cached_tile.status = TileStatus::Failed;
}
}
/// Removes all the cached tessellate layers that are not contained within the given
/// layers hashset.
pub fn retain_missing_layer_names(

View File

@ -1,53 +0,0 @@
//! Tile request state.
use std::collections::{HashMap, HashSet};
use crate::{
coords::WorldTileCoords,
io::{TileRequest, TileRequestID},
};
/// Stores a map of pending requests, coords and the current tile being requested.
#[derive(Default)]
pub struct TileRequestState {
current_id: TileRequestID,
pending_tile_requests: HashMap<TileRequestID, TileRequest>,
pending_coords: HashSet<WorldTileCoords>,
}
impl TileRequestState {
pub fn new() -> Self {
Self {
current_id: 1,
pending_tile_requests: Default::default(),
pending_coords: Default::default(),
}
}
pub fn is_tile_request_pending(&self, coords: &WorldTileCoords) -> bool {
self.pending_coords.contains(coords)
}
pub fn start_tile_request(&mut self, tile_request: TileRequest) -> Option<TileRequestID> {
if self.is_tile_request_pending(&tile_request.coords) {
return None;
}
self.pending_coords.insert(tile_request.coords);
let id = self.current_id;
self.pending_tile_requests.insert(id, tile_request);
self.current_id += 1;
Some(id)
}
pub fn finish_tile_request(&mut self, id: TileRequestID) -> Option<TileRequest> {
self.pending_tile_requests.remove(&id).map(|request| {
self.pending_coords.remove(&request.coords);
request
})
}
pub fn get_tile_request(&self, id: TileRequestID) -> Option<&TileRequest> {
self.pending_tile_requests.get(&id)
}
}

View File

@ -0,0 +1,110 @@
use geozero::mvt::{tile, tile::Layer};
use crate::{
coords::WorldTileCoords,
io::tile_repository::StoredLayer,
render::ShaderVertex,
tessellation::{IndexDataType, OverAlignedVertexBuffer},
};
pub trait TileTessellated: Send {
fn new(coords: WorldTileCoords) -> Self;
fn coords(&self) -> &WorldTileCoords;
}
pub trait UnavailableLayer: Send {
fn new(coords: WorldTileCoords, layer_name: String) -> Self;
fn to_stored_layer(self) -> StoredLayer;
}
pub trait TessellatedLayer: Send {
fn new(
coords: WorldTileCoords,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
feature_indices: Vec<u32>,
layer_data: tile::Layer,
) -> Self;
fn to_stored_layer(self) -> StoredLayer;
}
pub struct DefaultTileTessellated {
pub coords: WorldTileCoords,
}
impl TileTessellated for DefaultTileTessellated {
fn new(coords: WorldTileCoords) -> Self {
Self { coords }
}
fn coords(&self) -> &WorldTileCoords {
&self.coords
}
}
pub struct DefaultUnavailableLayer {
pub coords: WorldTileCoords,
pub layer_name: String,
}
impl UnavailableLayer for DefaultUnavailableLayer {
fn new(coords: WorldTileCoords, layer_name: String) -> Self {
Self { coords, layer_name }
}
fn to_stored_layer(self) -> StoredLayer {
StoredLayer::UnavailableLayer {
coords: self.coords,
layer_name: self.layer_name,
}
}
}
pub struct DefaultTessellatedLayer {
pub coords: WorldTileCoords,
pub buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
/// Holds for each feature the count of indices.
pub feature_indices: Vec<u32>,
pub layer_data: Layer, // FIXME (wasm-executor): Introduce a better structure for this
}
impl TessellatedLayer for DefaultTessellatedLayer {
fn new(
coords: WorldTileCoords,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
feature_indices: Vec<u32>,
layer_data: Layer,
) -> Self {
Self {
coords,
buffer,
feature_indices,
layer_data,
}
}
fn to_stored_layer(self) -> StoredLayer {
StoredLayer::TessellatedLayer {
coords: self.coords,
layer_name: self.layer_data.name,
buffer: self.buffer,
feature_indices: self.feature_indices,
}
}
}
pub trait Transferables: 'static {
type TileTessellated: TileTessellated;
type UnavailableLayer: UnavailableLayer;
type TessellatedLayer: TessellatedLayer;
}
#[derive(Copy, Clone)]
pub struct DefaultTransferables;
impl Transferables for DefaultTransferables {
type TileTessellated = DefaultTileTessellated;
type UnavailableLayer = DefaultUnavailableLayer;
type TessellatedLayer = DefaultTessellatedLayer;
}

View File

@ -16,11 +16,15 @@
//! maplibre = "0.0.2"
//! ```
use std::{
borrow::{Borrow, BorrowMut},
cell::RefCell,
rc::Rc,
};
use crate::{
io::{
scheduler::{ScheduleMethod, Scheduler},
source_client::HttpClient,
},
environment::Environment,
io::{scheduler::Scheduler, source_client::HttpClient},
map_schedule::InteractiveMapSchedule,
render::{
settings::{RendererSettings, WgpuSettings},
@ -56,29 +60,24 @@ pub mod benchmarking;
// Internal modules
pub(crate) mod tessellation;
pub mod environment;
pub use geozero::mvt::tile;
/// The [`Map`] defines the public interface of the map renderer.
// DO NOT IMPLEMENT INTERNALS ON THIS STRUCT.
pub struct Map<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
map_schedule: InteractiveMapSchedule<MWC, SM, HC>,
window: MWC::MapWindow,
pub struct Map<E: Environment> {
// FIXME (wasm-executor): Avoid RefCell, change ownership model!
map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
window: RefCell<Option<<E::MapWindowConfig as MapWindowConfig>::MapWindow>>,
}
impl<MWC, SM, HC> Map<MWC, SM, HC>
impl<E: Environment> Map<E>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
<E::MapWindowConfig as MapWindowConfig>::MapWindow: EventLoop<E>,
{
/// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
pub fn run(self)
where
MWC::MapWindow: EventLoop<MWC, SM, HC>,
{
pub fn run(&self) {
self.run_with_optionally_max_frames(None);
}
@ -87,10 +86,7 @@ where
/// # Arguments
///
/// * `max_frames` - Maximum number of frames per second.
pub fn run_with_max_frames(self, max_frames: u64)
where
MWC::MapWindow: EventLoop<MWC, SM, HC>,
{
pub fn run_with_max_frames(&self, max_frames: u64) {
self.run_with_optionally_max_frames(Some(max_frames));
}
@ -99,51 +95,42 @@ where
/// # Arguments
///
/// * `max_frames` - Optional maximum number of frames per second.
pub fn run_with_optionally_max_frames(self, max_frames: Option<u64>)
where
MWC::MapWindow: EventLoop<MWC, SM, HC>,
{
self.window.run(self.map_schedule, max_frames);
pub fn run_with_optionally_max_frames(&self, max_frames: Option<u64>) {
self.window
.borrow_mut()
.take()
.unwrap() // FIXME (wasm-executor): Remove unwrap
.run(self.map_schedule.clone(), max_frames);
}
pub fn map_schedule(&self) -> &InteractiveMapSchedule<MWC, SM, HC> {
&self.map_schedule
pub fn map_schedule(&self) -> Rc<RefCell<InteractiveMapSchedule<E>>> {
self.map_schedule.clone()
}
pub fn map_schedule_mut(&mut self) -> &mut InteractiveMapSchedule<MWC, SM, HC> {
/* pub fn map_schedule_mut(&mut self) -> &mut InteractiveMapSchedule<E> {
&mut self.map_schedule
}
}*/
}
/// Stores the map configuration before the map's state has been fully initialized.
pub struct UninitializedMap<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
scheduler: Scheduler<SM>,
http_client: HC,
pub struct UninitializedMap<E: Environment> {
scheduler: E::Scheduler,
apc: E::AsyncProcedureCall,
http_client: E::HttpClient,
style: Style,
wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings,
map_window_config: MWC,
map_window_config: E::MapWindowConfig,
}
impl<MWC, SM, HC> UninitializedMap<MWC, SM, HC>
impl<E: Environment> UninitializedMap<E>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
/// Initializes the whole rendering pipeline for the given configuration.
/// Returns the initialized map, ready to be run.
pub async fn initialize(self) -> Map<MWC, SM, HC>
where
MWC: MapWindowConfig,
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
pub async fn initialize(self) -> Map<E> {
let window = self.map_window_config.create();
let window_size = window.size();
@ -158,22 +145,25 @@ where
.await
.ok();
Map {
map_schedule: InteractiveMapSchedule::new(
map_schedule: Rc::new(RefCell::new(InteractiveMapSchedule::new(
self.map_window_config,
window_size,
renderer,
self.scheduler,
self.apc,
self.http_client,
self.style,
self.wgpu_settings,
self.renderer_settings,
),
window,
))),
window: RefCell::new(Some(window)),
}
}
}
#[cfg(feature = "headless")]
pub async fn initialize_headless(self) -> headless::HeadlessMap<MWC, SM, HC> {
impl<E: Environment> UninitializedMap<E> {
pub async fn initialize_headless(self) -> headless::HeadlessMap<E> {
let window = self.map_window_config.create();
let window_size = window.size();
@ -198,30 +188,22 @@ where
}
}
pub struct MapBuilder<MWC, SM, HC>
where
SM: ScheduleMethod,
{
schedule_method: Option<SM>,
scheduler: Option<Scheduler<SM>>,
http_client: Option<HC>,
pub struct MapBuilder<E: Environment> {
scheduler: Option<E::Scheduler>,
apc: Option<E::AsyncProcedureCall>,
http_client: Option<E::HttpClient>,
style: Option<Style>,
map_window_config: Option<MWC>,
map_window_config: Option<E::MapWindowConfig>,
wgpu_settings: Option<WgpuSettings>,
renderer_settings: Option<RendererSettings>,
}
impl<MWC, SM, HC> MapBuilder<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
impl<E: Environment> MapBuilder<E> {
pub fn new() -> Self {
Self {
schedule_method: None,
scheduler: None,
apc: None,
http_client: None,
style: None,
map_window_config: None,
@ -230,7 +212,7 @@ where
}
}
pub fn with_map_window_config(mut self, map_window_config: MWC) -> Self {
pub fn with_map_window_config(mut self, map_window_config: E::MapWindowConfig) -> Self {
self.map_window_config = Some(map_window_config);
self
}
@ -245,40 +227,36 @@ where
self
}
pub fn with_schedule_method(mut self, schedule_method: SM) -> Self {
self.schedule_method = Some(schedule_method);
self
}
pub fn with_http_client(mut self, http_client: HC) -> Self {
self.http_client = Some(http_client);
self
}
pub fn with_existing_scheduler(mut self, scheduler: Scheduler<SM>) -> Self {
pub fn with_scheduler(mut self, scheduler: E::Scheduler) -> Self {
self.scheduler = Some(scheduler);
self
}
pub fn with_apc(mut self, apc: E::AsyncProcedureCall) -> Self {
self.apc = Some(apc);
self
}
pub fn with_http_client(mut self, http_client: E::HttpClient) -> Self {
self.http_client = Some(http_client);
self
}
pub fn with_style(mut self, style: Style) -> Self {
self.style = Some(style);
self
}
/// Builds the UninitializedMap with the given configuration.
pub fn build(self) -> UninitializedMap<MWC, SM, HC> {
let scheduler = self
.scheduler
.unwrap_or_else(|| Scheduler::new(self.schedule_method.unwrap()));
let style = self.style.unwrap_or_default();
pub fn build(self) -> UninitializedMap<E> {
UninitializedMap {
scheduler,
http_client: self.http_client.unwrap(),
style,
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
apc: self.apc.unwrap(), // TODO: Remove unwrap
http_client: self.http_client.unwrap(), // TODO: Remove unwrap
style: self.style.unwrap_or_default(),
wgpu_settings: self.wgpu_settings.unwrap_or_default(),
renderer_settings: self.renderer_settings.unwrap_or_default(),
map_window_config: self.map_window_config.unwrap(),
map_window_config: self.map_window_config.unwrap(), // TODO: Remove unwrap
}
}
}

View File

@ -1,4 +1,4 @@
use std::{marker::PhantomData, mem};
use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc};
use crate::{
context::{MapContext, ViewState},
@ -13,41 +13,32 @@ use crate::{
schedule::{Schedule, Stage},
stages::register_stages,
style::Style,
HeadedMapWindow, MapWindowConfig, Renderer, RendererSettings, ScheduleMethod, WgpuSettings,
Environment, HeadedMapWindow, MapWindowConfig, Renderer, RendererSettings, WgpuSettings,
WindowSize,
};
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
pub struct InteractiveMapSchedule<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
map_window_config: MWC,
pub struct InteractiveMapSchedule<E: Environment> {
map_window_config: E::MapWindowConfig,
// FIXME (wasm-executor): avoid RefCell, change ownership model
pub apc: Rc<RefCell<E::AsyncProcedureCall>>,
map_context: EventuallyMapContext,
schedule: Schedule,
phantom_sm: PhantomData<SM>,
phantom_hc: PhantomData<HC>,
suspended: bool,
}
impl<MWC, SM, HC> InteractiveMapSchedule<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
impl<E: Environment> InteractiveMapSchedule<E> {
pub fn new(
map_window_config: MWC,
map_window_config: E::MapWindowConfig,
window_size: WindowSize,
renderer: Option<Renderer>,
scheduler: Scheduler<SM>,
http_client: HC,
scheduler: E::Scheduler, // TODO: unused
apc: E::AsyncProcedureCall,
http_client: E::HttpClient,
style: Style,
wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings,
@ -63,13 +54,17 @@ where
let tile_repository = TileRepository::new();
let mut schedule = Schedule::default();
let http_source_client: HttpSourceClient<HC> = HttpSourceClient::new(http_client);
register_stages(&mut schedule, http_source_client, Box::new(scheduler));
let apc = Rc::new(RefCell::new(apc));
let graph = create_default_render_graph().unwrap();
let http_source_client: HttpSourceClient<E::HttpClient> =
HttpSourceClient::new(http_client);
register_stages::<E>(&mut schedule, http_source_client, apc.clone());
let graph = create_default_render_graph().unwrap(); // TODO: Remove unwrap
register_default_render_stages(graph, &mut schedule);
Self {
apc,
map_window_config,
map_context: match renderer {
None => EventuallyMapContext::Premature(PrematureMapContext {
@ -87,8 +82,6 @@ where
}),
},
schedule,
phantom_sm: Default::default(),
phantom_hc: Default::default(),
suspended: false,
}
}
@ -116,23 +109,6 @@ where
}
}
pub fn suspend(&mut self) {
self.suspended = true;
}
pub fn resume(&mut self, window: &MWC::MapWindow)
where
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
let renderer = &mut map_context.renderer;
renderer
.state
.recreate_surface::<MWC::MapWindow>(window, &renderer.instance);
self.suspended = false;
}
}
pub fn is_initialized(&self) -> bool {
match &self.map_context {
EventuallyMapContext::Full(_) => true,
@ -140,10 +116,36 @@ where
}
}
pub async fn late_init(&mut self) -> bool
pub fn view_state_mut(&mut self) -> &mut ViewState {
match &mut self.map_context {
EventuallyMapContext::Full(MapContext { view_state, .. }) => view_state,
EventuallyMapContext::Premature(PrematureMapContext { view_state, .. }) => view_state,
_ => panic!("should not happen"),
}
}
pub fn apc(&self) -> &Rc<RefCell<E::AsyncProcedureCall>> {
&self.apc
}
}
impl<E: Environment> InteractiveMapSchedule<E>
where
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
pub fn suspend(&mut self) {
self.suspended = true;
}
pub fn resume(&mut self, window: &<E::MapWindowConfig as MapWindowConfig>::MapWindow) {
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
let renderer = &mut map_context.renderer;
renderer.state.recreate_surface(window, &renderer.instance);
self.suspended = false;
}
}
pub async fn late_init(&mut self) -> bool {
match &self.map_context {
EventuallyMapContext::Full(_) => false,
EventuallyMapContext::Premature(PrematureMapContext {
@ -155,21 +157,13 @@ where
let renderer =
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
.await
.unwrap();
.unwrap(); // TODO: Remove unwrap
self.map_context.make_full(renderer);
true
}
EventuallyMapContext::_Uninitialized => false,
}
}
pub fn view_state_mut(&mut self) -> &mut ViewState {
match &mut self.map_context {
EventuallyMapContext::Full(MapContext { view_state, .. }) => view_state,
EventuallyMapContext::Premature(PrematureMapContext { view_state, .. }) => view_state,
_ => panic!("should not happen"),
}
}
}
pub struct PrematureMapContext {

View File

@ -36,9 +36,14 @@ pub mod http_client {
}
/// Scheduler for non-web targets.
pub mod schedule_method {
pub mod scheduler {
#[cfg(not(target_arch = "wasm32"))]
pub use super::noweb::schedule_method::*;
pub use super::noweb::scheduler::*;
}
pub mod trace {
#[cfg(not(target_arch = "wasm32"))]
pub use super::noweb::trace::*;
}
#[cfg(not(target_arch = "wasm32"))]

View File

@ -3,7 +3,8 @@
use std::future::Future;
pub mod http_client;
pub mod schedule_method;
pub mod scheduler;
pub mod trace;
pub fn run_multithreaded<F: Future>(future: F) -> F::Output {
tokio::runtime::Builder::new_multi_thread()

View File

@ -1,17 +1,17 @@
use std::future::Future;
use crate::{error::Error, ScheduleMethod};
use crate::{error::Error, Scheduler};
/// Multi-threading with Tokio.
pub struct TokioScheduleMethod;
pub struct TokioScheduler;
impl TokioScheduleMethod {
impl TokioScheduler {
pub fn new() -> Self {
Self {}
}
}
impl ScheduleMethod for TokioScheduleMethod {
impl Scheduler for TokioScheduler {
fn schedule<T>(&self, future_factory: impl FnOnce() -> T + Send + 'static) -> Result<(), Error>
where
T: Future<Output = ()> + Send + 'static,

View File

@ -0,0 +1,8 @@
#[cfg(feature = "trace")]
pub fn enable_tracing() {
use tracing_subscriber::{layer::SubscriberExt, Registry};
let subscriber = Registry::default().with(tracing_tracy::TracyLayer::new());
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
}

View File

@ -191,7 +191,7 @@ impl<Q: Queue<B>, B, V: Pod, I: Pod, TM: Pod, FM: Pod> BufferPool<Q, B, V, I, TM
self.index.get_layers(coords).map(|layers| {
layers
.iter()
.map(|entry| entry.style_layer.source_layer.as_ref().unwrap().as_str())
.map(|entry| entry.style_layer.source_layer.as_ref().unwrap().as_str()) // TODO: Remove unwrap
.collect()
})
}

View File

@ -84,7 +84,7 @@ impl BufferedTextureHead {
let padded_buffer = buffer_slice.get_mapped_range();
let mut png_encoder = png::Encoder::new(
File::create(png_output_path).unwrap(),
File::create(png_output_path).unwrap(), // TODO: Remove unwrap
self.buffer_dimensions.width as u32,
self.buffer_dimensions.height as u32,
);
@ -92,17 +92,17 @@ impl BufferedTextureHead {
png_encoder.set_color(png::ColorType::Rgba);
let mut png_writer = png_encoder
.write_header()
.unwrap()
.unwrap() // TODO: Remove unwrap
.into_stream_writer_with_size(self.buffer_dimensions.unpadded_bytes_per_row)
.unwrap();
.unwrap(); // TODO: Remove 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();
.unwrap(); // TODO: Remove unwrap
}
png_writer.finish().unwrap();
png_writer.finish().unwrap(); // TODO: Remove unwrap
// With the current interface, we have to make sure all mapped views are
// dropped before we unmap the buffer.

View File

@ -44,7 +44,7 @@ impl Stage for UploadStage {
.position
.to_homogeneous()
.cast::<f32>()
.unwrap()
.unwrap() // TODO: Remove unwrap
.into(),
))]),
);
@ -168,7 +168,7 @@ impl UploadStage {
})
{
for style_layer in &style.layers {
let source_layer = style_layer.source_layer.as_ref().unwrap();
let source_layer = style_layer.source_layer.as_ref().unwrap(); // TODO: Remove unwrap
if let Some(message) = available_layers
.iter()
@ -187,7 +187,6 @@ impl UploadStage {
StoredLayer::TessellatedLayer {
coords,
feature_indices,
layer_data,
buffer,
..
} => {
@ -197,9 +196,8 @@ impl UploadStage {
);
let guard = allocate_feature_metadata.enter();
let feature_metadata = layer_data
.features
.iter()
let feature_metadata =
(0..feature_indices.len()) // FIXME: Iterate over actual featrues
.enumerate()
.flat_map(|(i, _feature)| {
iter::repeat(ShaderFeatureStyle {

View File

@ -166,8 +166,8 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
let raw_buffer = bytemuck::cast_slice(buffer.as_slice());
if raw_buffer.len() as wgpu::BufferAddress > self.buffer.inner_size {
/* FIXME: We need to avoid this case by either choosing a proper size
(DEFAULT_TILE_VIEW_SIZE), or resizing the buffer */
/* TODO: We need to avoid this case by either choosing a proper size
TODO: (DEFAULT_TILE_VIEW_SIZE), or resizing the buffer */
panic!("Buffer is too small to store the tile pattern!");
}
queue.write_buffer(&self.buffer.inner, 0, raw_buffer);

View File

@ -250,7 +250,7 @@ impl Schedule {
for label in &self.stage_order {
#[cfg(feature = "trace")]
let _stage_span = tracing::info_span!("stage", name = ?label).entered();
let stage = self.stages.get_mut(label).unwrap();
let stage = self.stages.get_mut(label).unwrap(); // TODO: Remove unwrap
stage.run(context);
}
}

View File

@ -1,75 +0,0 @@
use std::{fmt, sync::mpsc};
use geozero::mvt::tile;
use crate::{
coords::WorldTileCoords,
io::{tile_repository::StoredLayer, TileRequestID},
render::ShaderVertex,
tessellation::{IndexDataType, OverAlignedVertexBuffer},
};
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,6 +1,11 @@
//! [Stages](Stage) for requesting and preparing data
use std::sync::{mpsc, Arc, Mutex};
use std::{
cell::RefCell,
marker::PhantomData,
rc::Rc,
sync::{mpsc, Arc, Mutex},
};
use geozero::{mvt::tile, GeozeroDatasource};
use request_stage::RequestStage;
@ -9,78 +14,61 @@ use crate::{
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
error::Error,
io::{
apc::{AsyncProcedureCall, Context, Message},
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
pipeline::{PipelineContext, PipelineProcessor, Processable},
source_client::HttpSourceClient,
tile_pipelines::build_vector_tile_pipeline,
tile_request_state::TileRequestState,
TileRequest, TileRequestID,
transferables::{
DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
DefaultUnavailableLayer, TessellatedLayer, TileTessellated, Transferables,
UnavailableLayer,
},
TileRequest,
},
render::ShaderVertex,
schedule::Schedule,
stages::{
message::{
LayerTessellateMessage, MessageReceiver, MessageSender, TessellateMessage,
TileTessellateMessage,
},
populate_tile_store_stage::PopulateTileStore,
},
stages::populate_tile_store_stage::PopulateTileStore,
tessellation::{IndexDataType, OverAlignedVertexBuffer},
HttpClient, ScheduleMethod, Scheduler,
Environment, HttpClient, Scheduler,
};
mod message;
mod populate_tile_store_stage;
mod request_stage;
/// Register stages required for requesting and preparing new tiles.
pub fn register_stages<HC: HttpClient, SM: ScheduleMethod>(
pub fn register_stages<E: Environment>(
schedule: &mut Schedule,
http_source_client: HttpSourceClient<HC>,
scheduler: Box<Scheduler<SM>>,
http_source_client: HttpSourceClient<E::HttpClient>,
apc: Rc<RefCell<E::AsyncProcedureCall>>,
) {
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),
RequestStage::<E>::new(http_source_client, apc.clone()),
);
schedule.add_stage("populate_tile_store", PopulateTileStore::<E>::new(apc));
}
pub struct HeadedPipelineProcessor {
state: SharedThreadState,
pub struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<T, HC>> {
context: C,
phantom_t: PhantomData<T>,
phantom_hc: PhantomData<HC>,
}
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();
impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
for HeadedPipelineProcessor<T, HC, C>
{
fn tile_finished(&mut self, coords: &WorldTileCoords) {
self.context
.send(Message::TileTessellated(T::TileTessellated::new(*coords)))
}
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();
self.context
.send(Message::UnavailableLayer(T::UnavailableLayer::new(
*coords,
layer_name.to_owned(),
)))
}
fn layer_tesselation_finished(
@ -90,17 +78,13 @@ impl PipelineProcessor for HeadedPipelineProcessor {
feature_indices: Vec<u32>,
layer_data: tile::Layer,
) {
self.state
.message_sender
.send(TessellateMessage::Layer(
LayerTessellateMessage::TessellatedLayer {
coords: *coords,
self.context
.send(Message::TessellatedLayer(T::TessellatedLayer::new(
*coords,
buffer,
feature_indices,
layer_data,
},
))
.unwrap();
)))
}
fn layer_indexing_finished(
@ -108,63 +92,15 @@ impl PipelineProcessor for HeadedPipelineProcessor {
coords: &WorldTileCoords,
geometries: Vec<IndexedGeometry<f64>>,
) {
if let Ok(mut geometry_index) = self.state.geometry_index.lock() {
// FIXME (wasm-executor): Readd
/* 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(
// FIXME (wasm-executor): Readd
/*pub fn query_point(
&self,
world_coords: &WorldCoords,
z: ZoomLevel,
@ -183,5 +119,5 @@ impl SharedThreadState {
} else {
unimplemented!()
}
}
}
}*/
//}

View File

@ -1,49 +1,69 @@
//! Receives data from async threads and populates the [`crate::io::tile_repository::TileRepository`].
use super::{MessageReceiver, SharedThreadState, TessellateMessage, TileTessellateMessage};
use crate::{context::MapContext, io::tile_repository::StoredLayer, schedule::Stage};
use std::{borrow::BorrowMut, cell::RefCell, ops::Deref, rc::Rc};
pub struct PopulateTileStore {
shared_thread_state: SharedThreadState,
message_receiver: MessageReceiver,
use crate::{
context::MapContext,
io::{
apc::{AsyncProcedureCall, Message},
tile_repository::StoredLayer,
transferables::{TessellatedLayer, TileTessellated, UnavailableLayer},
},
schedule::Stage,
Environment,
};
pub struct PopulateTileStore<E: Environment> {
apc: Rc<RefCell<E::AsyncProcedureCall>>,
}
impl PopulateTileStore {
pub fn new(shared_thread_state: SharedThreadState, message_receiver: MessageReceiver) -> Self {
Self {
shared_thread_state,
message_receiver,
}
impl<E: Environment> PopulateTileStore<E> {
pub fn new(apc: Rc<RefCell<E::AsyncProcedureCall>>) -> Self {
Self { apc }
}
}
impl Stage for PopulateTileStore {
impl<E: Environment> Stage for PopulateTileStore<E> {
fn run(
&mut self,
MapContext {
tile_repository, ..
}: &mut MapContext,
) {
if let Ok(result) = self.message_receiver.try_recv() {
if let Ok(mut apc) = self.apc.deref().try_borrow_mut() {
if let Some(result) = apc.receive() {
match result {
TessellateMessage::Layer(layer_result) => {
let layer: StoredLayer = layer_result.into();
tracing::trace!(
Message::TileTessellated(tranferred) => {
let coords = tranferred.coords();
tile_repository.success(coords);
tracing::trace!("Tile at {} finished loading", coords);
log::warn!("Tile at {} finished loading", coords);
}
// FIXME: deduplicate
Message::UnavailableLayer(tranferred) => {
let layer: StoredLayer = tranferred.to_stored_layer();
tracing::debug!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
tile_repository.put_tessellated_layer(layer);
}
TessellateMessage::Tile(TileTessellateMessage { request_id, coords }) => loop {
if let Ok(mut tile_request_state) =
self.shared_thread_state.tile_request_state.try_lock()
{
tile_request_state.finish_tile_request(request_id);
tracing::trace!("Tile at {} finished loading", coords);
break;
}
},
Message::TessellatedLayer(data) => {
let layer: StoredLayer = data.to_stored_layer();
tracing::debug!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
log::warn!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
tile_repository.put_tessellated_layer(layer);
}
}
}
}
}

View File

@ -1,56 +1,53 @@
//! Requests tiles which are currently in view
use std::collections::HashSet;
use std::{
borrow::Borrow,
cell::RefCell,
collections::{HashMap, HashSet},
future::Future,
ops::Deref,
pin::Pin,
process::Output,
rc::Rc,
str::FromStr,
};
use crate::{
context::MapContext,
coords::{ViewRegion, WorldTileCoords},
coords::{ViewRegion, WorldTileCoords, ZoomLevel},
error::Error,
io::{
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
pipeline::{PipelineContext, Processable},
source_client::{HttpSourceClient, SourceClient},
tile_pipelines::build_vector_tile_pipeline,
tile_repository::TileRepository,
transferables::{Transferables, UnavailableLayer},
TileRequest,
},
schedule::Stage,
stages::SharedThreadState,
HttpClient, ScheduleMethod, Scheduler, Style,
stages::HeadedPipelineProcessor,
Environment, HttpClient, Scheduler, Style,
};
pub struct RequestStage<SM, HC>
where
SM: ScheduleMethod,
HC: HttpClient,
{
shared_thread_state: SharedThreadState,
scheduler: Scheduler<SM>,
http_source_client: HttpSourceClient<HC>,
try_failed: bool,
pub struct RequestStage<E: Environment> {
apc: Rc<RefCell<E::AsyncProcedureCall>>,
http_source_client: HttpSourceClient<E::HttpClient>,
}
impl<SM, HC> RequestStage<SM, HC>
where
SM: ScheduleMethod,
HC: HttpClient,
{
impl<E: Environment> RequestStage<E> {
pub fn new(
shared_thread_state: SharedThreadState,
http_source_client: HttpSourceClient<HC>,
scheduler: Scheduler<SM>,
http_source_client: HttpSourceClient<E::HttpClient>,
apc: Rc<RefCell<E::AsyncProcedureCall>>,
) -> Self {
Self {
shared_thread_state,
scheduler,
apc,
http_source_client,
try_failed: false,
}
}
}
impl<SM, HC> Stage for RequestStage<SM, HC>
where
SM: ScheduleMethod,
HC: HttpClient,
{
impl<E: Environment> Stage for RequestStage<E> {
fn run(
&mut self,
MapContext {
@ -62,11 +59,10 @@ where
) {
let view_region = view_state.create_view_region();
if view_state.camera.did_change(0.05) || view_state.zoom.did_change(0.05) || self.try_failed
{
if view_state.camera.did_change(0.05) || view_state.zoom.did_change(0.05) {
if let Some(view_region) = &view_region {
// FIXME: We also need to request tiles from layers above if we are over the maximum zoom level
self.try_failed = self.request_tiles_in_view(tile_repository, style, view_region);
self.request_tiles_in_view(tile_repository, style, view_region);
}
}
@ -75,20 +71,58 @@ where
}
}
impl<SM, HC> RequestStage<SM, HC>
where
SM: ScheduleMethod,
HC: HttpClient,
{
pub fn schedule<E: Environment, C: Context<E::Transferables, E::HttpClient>>(
input: Input,
context: C,
) -> AsyncProcedureFuture {
// FIXME: improve input handling
let input = match input {
Input::TileRequest(input) => Some(input),
_ => None,
}
.unwrap(); // FIXME (wasm-executor): Remove unwrap
Box::pin(async move {
let coords = input.coords;
let client = context.source_client();
match client.fetch(&coords).await {
Ok(data) => {
let data = data.into_boxed_slice();
let mut pipeline_context = PipelineContext::new(HeadedPipelineProcessor {
context,
phantom_t: Default::default(),
phantom_hc: Default::default(),
});
let pipeline = build_vector_tile_pipeline();
pipeline.process((input, data), &mut pipeline_context);
}
Err(e) => {
log::error!("{:?}", &e);
for to_load in &input.layers {
tracing::warn!("layer {} at {} unavailable", to_load, coords);
context.send(Message::UnavailableLayer(
<E::Transferables as Transferables>::UnavailableLayer::new(
input.coords,
to_load.to_string(),
),
));
}
}
}
})
}
impl<E: Environment> RequestStage<E> {
/// Request tiles which are currently in view.
#[tracing::instrument(skip_all)]
fn request_tiles_in_view(
&self,
tile_repository: &TileRepository,
tile_repository: &mut TileRepository,
style: &Style,
view_region: &ViewRegion,
) -> bool {
let mut try_failed = false;
) {
let source_layers: HashSet<String> = style
.layers
.iter()
@ -98,66 +132,41 @@ where
for coords in view_region.iter() {
if coords.build_quad_key().is_some() {
// TODO: Make tesselation depend on style?
try_failed = self
.try_request_tile(tile_repository, &coords, &source_layers)
.unwrap();
self.request_tile(tile_repository, &coords, &source_layers)
.unwrap(); // TODO: Remove unwrap
}
}
try_failed
}
fn try_request_tile(
fn request_tile(
&self,
tile_repository: &TileRepository,
tile_repository: &mut TileRepository,
coords: &WorldTileCoords,
layers: &HashSet<String>,
) -> Result<bool, Error> {
if !tile_repository.is_layers_missing(coords, layers) {
) -> Result<(), Error> {
/* if !tile_repository.is_layers_missing(coords, layers) {
return Ok(false);
}
if let Ok(mut tile_request_state) = self.shared_thread_state.tile_request_state.try_lock() {
if let Some(request_id) = tile_request_state.start_tile_request(TileRequest {
coords: *coords,
layers: layers.clone(),
}) {
tracing::info!("new tile request: {}", &coords);
// The following snippet can be added instead of the next code block to demonstrate
// an understanable approach of fetching
/*#[cfg(target_arch = "wasm32")]
if let Some(tile_coords) = coords.into_tile(TileAddressingScheme::TMS) {
crate::platform::legacy_webworker_fetcher::request_tile(
request_id,
tile_coords,
);
}*/
let client = SourceClient::Http(self.http_source_client.clone());
let coords = *coords;
if tile_repository.needs_fetching(&coords) {
tile_repository.create_tile(coords);
let state = self.shared_thread_state.clone();
self.scheduler
.schedule_method()
.schedule(Box::new(move || {
Box::pin(async move {
match client.fetch(&coords).await {
Ok(data) => state
.process_tile(request_id, data.into_boxed_slice())
.unwrap(),
Err(e) => {
log::error!("{:?}", &e);
state.tile_unavailable(&coords, request_id).unwrap()
}
}
})
}))
.unwrap();
tracing::info!("new tile request: {}", &coords);
self.apc.deref().borrow().schedule(
Input::TileRequest(TileRequest {
coords: *coords,
layers: layers.clone(),
}),
schedule::<
E,
<E::AsyncProcedureCall as AsyncProcedureCall<
E::Transferables,
E::HttpClient,
>>::Context,
>,
);
}
Ok(false)
} else {
Ok(true)
}
Ok(())
}
}

View File

@ -56,6 +56,21 @@ impl<V, I> OverAlignedVertexBuffer<V, I> {
usable_indices: 0,
}
}
pub fn from_slices(vertices: &[V], indices: &[I], usable_indices: u32) -> Self
where
V: Copy,
I: Copy,
{
// FIXME (wasm-executor), make this fn not needed
let mut buffers = VertexBuffers::with_capacity(0, 0);
buffers.vertices = Vec::from(vertices);
buffers.indices = Vec::from(indices);
Self {
buffer: buffers,
usable_indices,
}
}
}
impl<V: Pod, I: Pod> From<VertexBuffers<V, I>> for OverAlignedVertexBuffer<V, I> {

View File

@ -64,7 +64,7 @@ impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> ZeroTesse
&StrokeOptions::tolerance(DEFAULT_TOLERANCE),
&mut BuffersBuilder::new(&mut self.buffer, VertexConstructor {}),
)
.unwrap();
.unwrap(); // TODO: Remove unwrap
}
fn end(&mut self, close: bool) {
@ -83,7 +83,7 @@ impl<I: std::ops::Add + From<lyon::tessellation::VertexId> + MaxIndex> ZeroTesse
&FillOptions::tolerance(DEFAULT_TOLERANCE).with_fill_rule(FillRule::NonZero),
&mut BuffersBuilder::new(&mut self.buffer, VertexConstructor {}),
)
.unwrap();
.unwrap(); // TODO: Remove unwrap
}
}

View File

@ -1,8 +1,10 @@
//! Utilities for the window system.
use std::{cell::RefCell, rc::Rc};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use crate::{HttpClient, InteractiveMapSchedule, ScheduleMethod};
use crate::{Environment, HttpClient, InteractiveMapSchedule};
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
pub trait MapWindow {
@ -27,13 +29,9 @@ pub trait MapWindowConfig: 'static {
/// The event loop is responsible for processing events and propagating them to the map renderer.
/// Only non-headless windows use an [`EventLoop`].
pub trait EventLoop<MWC, SM, HC>
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
fn run(self, map_schedule: InteractiveMapSchedule<MWC, SM, HC>, max_frames: Option<u64>);
pub trait EventLoop<E: Environment> {
// FIXME (wasm-executor): Avoid Rc, change ownership model
fn run(self, map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>, max_frames: Option<u64>);
}
/// Window size with a width and an height in pixels.

View File

@ -1,7 +1,2 @@
[toolchain]
channel = "1.62"
targets = [
"wasm32-unknown-unknown", "x86_64-unknown-linux-gnu", "x86_64-linux-android", "aarch64-linux-android",
"x86_64-apple-darwin", "aarch64-apple-darwin", "x86_64-apple-ios", "aarch64-apple-ios", "aarch64-apple-ios-sim"
]
components = [ "rust-src" ] # rust-src is required for WASM and android builds which recompile the stdlib

View File

@ -37,7 +37,11 @@ js-sys = "0.3.58"
wasm-bindgen = "0.2.81"
wasm-bindgen-futures = "0.4.31"
console_log = { version = "0.2.0", features = ["color"] }
tracing-wasm = { version = "0.2.1", optional = true } # FIXME: Low quality dependency
tracing-wasm = { version = "0.2.1", optional = true } # TODO: Low quality dependency
# For passing Inputs in AsyncProcedureCalls
serde_json = "1.0.85"
bytemuck = "1.12.1" # FIXME (wasm-executor): Remove
bytemuck_derive = "1.2.1" # FIXME (wasm-executor): Remove
[dev-dependencies]
wasm-bindgen-test = "0.3.31"

View File

@ -12,11 +12,14 @@
"maplibre-rs": "file:../lib"
},
"devDependencies": {
"@types/node": "^18.7.20",
"@types/webpack": "^5.28.0",
"copy-webpack-plugin": "^10.2.4",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"ts-loader": "^9.2.6",
"typescript": "^4.5.4",
"ts-node": "^10.9.1",
"typescript": "^4.8.3",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0"
@ -27,6 +30,7 @@
"version": "0.0.1",
"license": "MIT",
"dependencies": {
"binaryen": "^110.0.0",
"spectorjs": "^0.9.27",
"wasm-feature-detect": "^1.2.11"
},
@ -36,10 +40,31 @@
"esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1",
"typescript": "^4.5.4",
"wasm-pack": "^0.10.2",
"yargs": "^17.5.1"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@ -148,6 +173,30 @@
"node": ">= 8"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"node_modules/@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -263,9 +312,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "18.7.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz",
"integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==",
"version": "18.7.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.20.tgz",
"integrity": "sha512-adzY4vLLr5Uivmx8+zfSJ5fbdgKxX8UMtjtl+17n0B1q1Nz8JEmE151vefMdpD+1gyh+77weN4qEhej/O7budQ==",
"dev": true
},
"node_modules/@types/qs": {
@ -314,6 +363,17 @@
"@types/node": "*"
}
},
"node_modules/@types/webpack": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.0.tgz",
"integrity": "sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w==",
"dev": true,
"dependencies": {
"@types/node": "*",
"tapable": "^2.2.0",
"webpack": "^5"
}
},
"node_modules/@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@ -551,6 +611,15 @@
"acorn": "^8"
}
},
"node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
@ -645,6 +714,12 @@
"node": ">= 8"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"node_modules/array-flatten": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
@ -1088,6 +1163,12 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -1185,6 +1266,15 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -2424,6 +2514,12 @@
"node": ">=10"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/maplibre-rs": {
"resolved": "../lib",
"link": true
@ -3768,6 +3864,49 @@
"webpack": "^5.0.0"
}
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@ -3788,9 +3927,9 @@
}
},
"node_modules/typescript": {
"version": "4.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
"integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@ -3874,6 +4013,12 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -4233,9 +4378,39 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"engines": {
"node": ">=6"
}
}
},
"dependencies": {
"@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"requires": {
"@jridgewell/trace-mapping": "0.3.9"
},
"dependencies": {
"@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
}
}
},
"@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@ -4323,6 +4498,30 @@
"fastq": "^1.6.0"
}
},
"@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true
},
"@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true
},
"@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true
},
"@tsconfig/node16": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"@types/body-parser": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -4438,9 +4637,9 @@
"dev": true
},
"@types/node": {
"version": "18.7.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz",
"integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==",
"version": "18.7.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.20.tgz",
"integrity": "sha512-adzY4vLLr5Uivmx8+zfSJ5fbdgKxX8UMtjtl+17n0B1q1Nz8JEmE151vefMdpD+1gyh+77weN4qEhej/O7budQ==",
"dev": true
},
"@types/qs": {
@ -4489,6 +4688,17 @@
"@types/node": "*"
}
},
"@types/webpack": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.0.tgz",
"integrity": "sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w==",
"dev": true,
"requires": {
"@types/node": "*",
"tapable": "^2.2.0",
"webpack": "^5"
}
},
"@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@ -4702,6 +4912,12 @@
"dev": true,
"requires": {}
},
"acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"dev": true
},
"ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
@ -4763,6 +4979,12 @@
"picomatch": "^2.0.4"
}
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"array-flatten": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
@ -5095,6 +5317,12 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true
},
"create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -5167,6 +5395,12 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -6097,17 +6331,23 @@
"yallist": "^4.0.0"
}
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"maplibre-rs": {
"version": "file:../lib",
"requires": {
"@chialab/esbuild-plugin-meta-url": "^0.15.28",
"binaryen": "^110.0.0",
"chokidar": "^3.5.3",
"esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1",
"spectorjs": "^0.9.27",
"typescript": "^4.5.4",
"wasm-feature-detect": "^1.2.11",
"wasm-pack": "^0.10.2",
"yargs": "^17.5.1"
}
},
@ -7102,6 +7342,27 @@
"semver": "^7.3.4"
}
},
"ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"dev": true,
"requires": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
}
},
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@ -7119,9 +7380,9 @@
}
},
"typescript": {
"version": "4.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
"integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
"dev": true
},
"unpipe": {
@ -7173,6 +7434,12 @@
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
},
"v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -7412,6 +7679,12 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
}
}
}

View File

@ -20,11 +20,14 @@
"maplibre-rs": "file:../lib"
},
"devDependencies": {
"@types/node": "^18.7.20",
"@types/webpack": "^5.28.0",
"copy-webpack-plugin": "^10.2.4",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"ts-loader": "^9.2.6",
"typescript": "^4.5.4",
"ts-node": "^10.9.1",
"typescript": "^4.8.3",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0"

View File

@ -6,5 +6,10 @@
"target": "es5",
"allowJs": true,
"moduleResolution": "node"
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
}
}

View File

@ -1,17 +1,17 @@
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
//const CopyPlugin = require("copy-webpack-plugin");
const webpack = require("webpack");
import * as path from 'path';
import * as webpack from 'webpack';
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
let dist = path.join(__dirname, 'dist/');
module.exports = (env) => ({
const config: (env: any) => webpack.Configuration = (env) => ({
mode: "development",
entry: {
main: "./index.ts",
},
experiments: {
asyncWebAssembly: env.cjs ? false : true
asyncWebAssembly: !env.cjs
},
stats: 'minimal',
performance: {
maxEntrypointSize: 400000,
maxAssetSize: 400000000,
@ -66,3 +66,5 @@ module.exports = (env) => ({
}),
]
});
export default config;

2
web/lib/.gitignore vendored
View File

@ -4,6 +4,6 @@ libs/maplibre*
dist
src/wasm-pack
src/wasm
.parcel-cache

View File

@ -1 +1,2 @@
declare const WEBGL: boolean
declare const MULTITHREADED: boolean

View File

@ -2,35 +2,38 @@ import {build} from 'esbuild';
import metaUrlPlugin from '@chialab/esbuild-plugin-meta-url';
import inlineWorker from 'esbuild-plugin-inline-worker';
import yargs from "yargs";
import process from "process";
import chokidar from "chokidar";
import {spawnSync} from "child_process"
import {unlink} from "fs";
import {dirname} from "path";
import {fileURLToPath} from "url";
let argv = yargs(process.argv.slice(2))
.option('watch', {
alias: 'w',
type: 'boolean',
description: 'Enable watching'
})
.option('release', {
type: 'boolean',
description: 'Release mode'
})
.option('webgl', {
alias: 'g',
type: 'boolean',
description: 'Enable webgl'
})
.option('multithreaded', {
type: 'boolean',
description: 'Enable multithreaded support'
})
.option('esm', {
alias: 'e',
type: 'boolean',
description: 'Enable esm'
})
.option('cjs', {
alias: 'c',
type: 'boolean',
description: 'Enable cjs'
})
.option('iife', {
alias: 'i',
type: 'boolean',
description: 'Enable iife'
})
@ -39,6 +42,8 @@ let argv = yargs(process.argv.slice(2))
let esm = argv.esm;
let iife = argv.iife;
let cjs = argv.cjs;
let release = argv.release;
let multithreaded = argv.multithreaded;
if (!esm && !iife && !cjs) {
console.warn("Enabling ESM bundling as no other bundle is enabled.")
@ -51,19 +56,29 @@ if (webgl) {
console.log("WebGL support enabled.")
}
let baseSettings = {
entryPoints: ['src/index.ts'],
bundle: true,
if (multithreaded) {
console.log("multithreaded support enabled.")
}
let baseConfig = {
platform: "browser",
bundle: true,
assetNames: "assets/[name]",
define: {"WEBGL": `${webgl}`},
define: {
WEBGL: `${webgl}`,
MULTITHREADED: `${multithreaded}`
},
}
let config = {
...baseConfig,
entryPoints:['src/index.ts'],
incremental: argv.watch,
plugins: [
inlineWorker({
format: "cjs", platform: "browser",
...baseConfig,
format: "cjs",
target: 'es2022',
bundle: true,
assetNames: "assets/[name]",
}),
metaUrlPlugin()
],
@ -98,8 +113,8 @@ const emitTypeScript = () => {
"--",
"-m", "es2022",
"-outDir", outDirectory,
"--declaration",
"--emitDeclarationOnly"
], {
cwd: '.',
stdio: 'inherit',
@ -107,36 +122,74 @@ const emitTypeScript = () => {
if (child.status !== 0) {
console.error("Failed to execute tsc")
process.exit(1)
}
}
// TODO: Do not continue if one step fails
const wasmPack = () => {
let outDirectory = `${getLibDirectory()}/src/wasm-pack`;
let outDirectory = `${getLibDirectory()}/src/wasm`;
let profile = release ? "wasm-release" : "wasm-dev"
let child = spawnSync('npm', ["exec",
"wasm-pack","--",
// language=toml
let multithreaded_config = `target.wasm32-unknown-unknown.rustflags = [
# Enables features which are required for shared-memory
"-C", "target-feature=+atomics,+bulk-memory,+mutable-globals",
# Enables the possibility to import memory into wasm.
# Without --shared-memory it is not possible to use shared WebAssembly.Memory.
# Set maximum memory to 200MB
"-C", "link-args=--shared-memory --import-memory --max-memory=209715200"
]`
let cargo = spawnSync('cargo', [
...(multithreaded ? ["--config", multithreaded_config] : []),
"build",
"--out-name", "index",
"--out-dir", outDirectory,
getWebDirectory(),
"--target", "web",
"--",
"--features", `${webgl ? "web-webgl" : ""}`,
"-Z", "build-std=std,panic_abort"
"-p", "web", "--lib",
"--target", "wasm32-unknown-unknown",
"--profile", profile,
"--features", `${webgl ? "web-webgl," : ""}`,
...(multithreaded ? ["-Z", "build-std=std,panic_abort"] : []),
], {
cwd: '.',
stdio: 'inherit',
});
if (child.status !== 0) {
console.error("Failed to execute wasm-pack")
if (cargo.status !== 0) {
console.error("Failed to execute cargo build")
}
let wasmbindgen = spawnSync('wasm-bindgen', [
`${getProjectDirectory()}/target/wasm32-unknown-unknown/${profile}/web.wasm`,
"--out-name", "maplibre",
"--out-dir", outDirectory,
"--typescript",
"--target", "web",
"--debug",
], {
cwd: '.',
stdio: 'inherit',
});
if (wasmbindgen.status !== 0) {
console.error("Failed to execute wasm-bindgen")
}
if (release) {
console.log("Running wasm-opt")
let wasmOpt = spawnSync('npm', ["exec",
"wasm-opt", "--",
`${outDirectory}/maplibre_bg.wasm`,
"-o", `${outDirectory}/maplibre_bg.wasm`,
"-O"
], {
cwd: '.',
stdio: 'inherit',
});
if (wasmOpt.status !== 0) {
console.error("Failed to execute wasm-opt")
}
}
// Having package.json within another npm package is not supported. Remove that.
unlink(`${getLibDirectory()}/src/wasm-pack/package.json`, (err) => {
if (err) throw err;
})
}
const watchResult = async (result) => {
@ -149,6 +202,7 @@ const watchResult = async (result) => {
});
const update = async (path) => {
try {
console.log(`Updating: ${path}`)
if (path.endsWith(".rs")) {
console.log("Rebuilding Rust...")
@ -160,6 +214,10 @@ const watchResult = async (result) => {
console.log("Emitting TypeScript types...")
emitTypeScript();
} catch (e) {
console.error("Error while updating:")
console.error(e)
}
}
console.log("Watching...")
@ -171,7 +229,7 @@ const watchResult = async (result) => {
}
const esbuild = async (name, globalName = undefined) => {
let result = await build({...baseSettings, format: name, globalName, outfile: `dist/esbuild-${name}/module.js`,});
let result = await build({...config, format: name, globalName, outfile: `dist/esbuild-${name}/module.js`,});
if (argv.watch) {
console.log("Watching is enabled.")
@ -180,7 +238,7 @@ const esbuild = async (name, globalName = undefined) => {
}
const start = async () => {
console.log("Running wasm-pack...")
console.log("Creating WASM...")
wasmPack();
if (esm) {

View File

@ -9,6 +9,7 @@
"version": "0.0.1",
"license": "MIT",
"dependencies": {
"binaryen": "^110.0.0",
"spectorjs": "^0.9.27",
"wasm-feature-detect": "^1.2.11"
},
@ -18,7 +19,6 @@
"esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1",
"typescript": "^4.5.4",
"wasm-pack": "^0.10.2",
"yargs": "^17.5.1"
}
},
@ -135,21 +135,6 @@
"node": ">= 8"
}
},
"node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@ -159,28 +144,13 @@
"node": ">=8"
}
},
"node_modules/binary-install": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.1.1.tgz",
"integrity": "sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ==",
"dev": true,
"dependencies": {
"axios": "^0.21.1",
"rimraf": "^3.0.2",
"tar": "^6.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
"node_modules/binaryen": {
"version": "110.0.0",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-110.0.0.tgz",
"integrity": "sha512-b1rQFvnjNIfuW0UYSBgwLIrKD9PaG0iEzWXyoWLBFp/HRdvgD+7LGUnxG+/yKz1+tyiTdRm/lFRxsmYXaULIUg==",
"bin": {
"wasm-opt": "bin/wasm-opt",
"wasm2js": "bin/wasm2js"
}
},
"node_modules/braces": {
@ -222,15 +192,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@ -266,12 +227,6 @@
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@ -707,44 +662,6 @@
"node": ">=8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@ -768,26 +685,6 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@ -800,22 +697,6 @@
"node": ">= 6"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -894,55 +775,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minipass": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz",
"integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true,
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -952,15 +784,6 @@
"node": ">=0.10.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@ -1006,15 +829,6 @@
"node": ">=8"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@ -1060,21 +874,6 @@
"node": ">=0.10.0"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -1115,23 +914,6 @@
"node": ">=8"
}
},
"node_modules/tar": {
"version": "6.1.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
"integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
"dev": true,
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -1162,19 +944,6 @@
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
},
"node_modules/wasm-pack": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.10.3.tgz",
"integrity": "sha512-dg1PPyp+QwWrhfHsgG12K/y5xzwfaAoK1yuVC/DUAuQsDy5JywWDuA7Y/ionGwQz+JBZVw8jknaKBnaxaJfwTA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"binary-install": "^0.1.0"
},
"bin": {
"wasm-pack": "run.js"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@ -1192,12 +961,6 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@ -1207,12 +970,6 @@
"node": ">=10"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yargs": {
"version": "17.5.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
@ -1319,47 +1076,16 @@
"picomatch": "^2.0.4"
}
},
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dev": true,
"requires": {
"follow-redirects": "^1.14.0"
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"binary-install": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.1.1.tgz",
"integrity": "sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ==",
"dev": true,
"requires": {
"axios": "^0.21.1",
"rimraf": "^3.0.2",
"tar": "^6.1.0"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
"binaryen": {
"version": "110.0.0",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-110.0.0.tgz",
"integrity": "sha512-b1rQFvnjNIfuW0UYSBgwLIrKD9PaG0iEzWXyoWLBFp/HRdvgD+7LGUnxG+/yKz1+tyiTdRm/lFRxsmYXaULIUg=="
},
"braces": {
"version": "3.0.2",
@ -1386,12 +1112,6 @@
"readdirp": "~3.6.0"
}
},
"chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true
},
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@ -1424,12 +1144,6 @@
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@ -1657,27 +1371,6 @@
"path-exists": "^4.0.0"
}
},
"follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
"dev": true
},
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@ -1691,20 +1384,6 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@ -1714,22 +1393,6 @@
"is-glob": "^4.0.1"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -1784,55 +1447,12 @@
"semver": "^6.0.0"
}
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minipass": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz",
"integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true,
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
}
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@ -1863,12 +1483,6 @@
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true
},
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@ -1899,15 +1513,6 @@
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -1939,20 +1544,6 @@
"ansi-regex": "^5.0.1"
}
},
"tar": {
"version": "6.1.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
"integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==",
"dev": true,
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -1973,15 +1564,6 @@
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
},
"wasm-pack": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/wasm-pack/-/wasm-pack-0.10.3.tgz",
"integrity": "sha512-dg1PPyp+QwWrhfHsgG12K/y5xzwfaAoK1yuVC/DUAuQsDy5JywWDuA7Y/ionGwQz+JBZVw8jknaKBnaxaJfwTA==",
"dev": true,
"requires": {
"binary-install": "^0.1.0"
}
},
"wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@ -1993,24 +1575,12 @@
"strip-ansi": "^6.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yargs": {
"version": "17.5.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",

View File

@ -16,16 +16,16 @@
"main": "dist/esbuild-cjs/main.js",
"types": "dist/ts/index.d.ts",
"dependencies": {
"binaryen": "^110.0.0",
"spectorjs": "^0.9.27",
"wasm-feature-detect": "^1.2.11"
},
"devDependencies": {
"chokidar": "^3.5.3",
"@chialab/esbuild-plugin-meta-url": "^0.15.28",
"chokidar": "^3.5.3",
"esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1",
"typescript": "^4.5.4",
"wasm-pack": "^0.10.2",
"yargs": "^17.5.1"
},
"repository": {

73
web/lib/src/browser.ts Normal file
View File

@ -0,0 +1,73 @@
import {
bigInt,
bulkMemory,
exceptions,
multiValue,
mutableGlobals,
referenceTypes,
saturatedFloatToInt,
signExtensions,
simd,
tailCall,
threads
} from "wasm-feature-detect"
export const checkRequirements = () => {
if (MULTITHREADED) {
if (!isSecureContext) {
return "isSecureContext is false!"
}
if (!crossOriginIsolated) {
return "crossOriginIsolated is false! " +
"The Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP headers are required."
}
}
if (WEBGL) {
if (!isWebGLSupported()) {
return "WebGL is not supported in this Browser!"
}
} else {
if (!("gpu" in navigator)) {
return "WebGPU is not supported in this Browser!"
}
}
return null
}
export const isWebGLSupported = () => {
try {
const canvas = document.createElement('canvas')
canvas.getContext("webgl")
return true
} catch (x) {
return false
}
}
export const checkWasmFeatures = async () => {
const checkFeature = async function (featureName: string, feature: () => Promise<boolean>) {
let result = await feature();
let msg = `The feature ${featureName} returned: ${result}`;
if (result) {
console.log(msg);
} else {
console.warn(msg);
}
}
await checkFeature("bulkMemory", bulkMemory);
await checkFeature("exceptions", exceptions);
await checkFeature("multiValue", multiValue);
await checkFeature("mutableGlobals", mutableGlobals);
await checkFeature("referenceTypes", referenceTypes);
await checkFeature("saturatedFloatToInt", saturatedFloatToInt);
await checkFeature("signExtensions", signExtensions);
await checkFeature("simd", simd);
await checkFeature("tailCall", tailCall);
await checkFeature("threads", threads);
await checkFeature("bigInt", bigInt);
}

6
web/lib/src/canvas.ts Normal file
View File

@ -0,0 +1,6 @@
export const preventDefaultTouchActions = () => {
document.body.querySelectorAll("canvas").forEach(canvas => {
canvas.addEventListener("touchstart", e => e.preventDefault())
canvas.addEventListener("touchmove", e => e.preventDefault())
})
}

View File

@ -1,148 +1,69 @@
import init, {create_pool_scheduler, run} from "./wasm-pack"
import * as maplibre from "./wasm/maplibre"
import {Spector} from "spectorjs"
import {WebWorkerMessageType} from "./types"
import {
bigInt,
bulkMemory,
exceptions,
multiValue,
mutableGlobals,
referenceTypes,
saturatedFloatToInt,
signExtensions,
simd,
tailCall,
threads
} from "wasm-feature-detect"
// @ts-ignore
import PoolWorker from './pool.worker.js';
const isWebGLSupported = () => {
try {
const canvas = document.createElement('canvas')
canvas.getContext("webgl")
return true
} catch (x) {
return false
}
}
const checkWasmFeatures = async () => {
const checkFeature = async function (featureName: string, feature: () => Promise<boolean>) {
let result = await feature();
let msg = `The feature ${featureName} returned: ${result}`;
if (result) {
console.log(msg);
} else {
console.warn(msg);
}
}
await checkFeature("bulkMemory", bulkMemory);
await checkFeature("exceptions", exceptions);
await checkFeature("multiValue", multiValue);
await checkFeature("mutableGlobals", mutableGlobals);
await checkFeature("referenceTypes", referenceTypes);
await checkFeature("saturatedFloatToInt", saturatedFloatToInt);
await checkFeature("signExtensions", signExtensions);
await checkFeature("simd", simd);
await checkFeature("tailCall", tailCall);
await checkFeature("threads", threads);
await checkFeature("bigInt", bigInt);
}
const alertUser = (message: string) => {
console.error(message)
alert(message)
}
const checkRequirements = () => {
if (!isSecureContext) {
alertUser("isSecureContext is false!")
return false
}
if (!crossOriginIsolated) {
alertUser("crossOriginIsolated is false! " +
"The Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP headers are required.")
return false
}
if (WEBGL) {
if (!isWebGLSupported()) {
alertUser("WebGL is not supported in this Browser!")
return false
}
let spector = new Spector()
spector.displayUI()
} else {
if (!("gpu" in navigator)) {
let message = "WebGPU is not supported in this Browser!"
alertUser(message)
return false
}
}
return true
}
const preventDefaultTouchActions = () => {
document.body.querySelectorAll("canvas").forEach(canvas => {
canvas.addEventListener("touchstart", e => e.preventDefault())
canvas.addEventListener("touchmove", e => e.preventDefault())
})
}
/*
let WORKER_COUNT = 4
const createWorker = (id: number, memory: WebAssembly.Memory) => {
const worker = new Worker(new URL('./legacy.worker.ts', import.meta.url), {
type: "module",
})
worker.postMessage({type: "init", memory} as WebWorkerMessageType)
return worker
}
const setupLegacyWebWorker = (schedulerPtr: number, memory: WebAssembly.Memory) => {
let workers: [number, Worker][] = Array.from(
new Array(WORKER_COUNT).keys(),
(id) => [new_thread_local_state(schedulerPtr), createWorker(id, memory)]
)
window.schedule_tile_request = (url: string, request_id: number) => {
const [state, worker] = workers[Math.floor(Math.random() * workers.length)]
worker.postMessage({
type: "fetch_tile",
threadLocalState: state,
url,
request_id
} as WebWorkerMessageType)
}
}*/
import {checkRequirements, checkWasmFeatures} from "./browser";
import {preventDefaultTouchActions} from "./canvas";
// @ts-ignore esbuild plugin is handling this
import MultithreadedPoolWorker from './multithreaded/multithreaded-pool.worker.js';
// @ts-ignore esbuild plugin is handling this
import PoolWorker from './singlethreaded/pool.worker.js';
export const startMapLibre = async (wasmPath: string | undefined, workerPath: string | undefined) => {
await checkWasmFeatures()
if (!checkRequirements()) {
let message = checkRequirements();
if (message) {
console.error(message)
alert(message)
return
}
if (WEBGL) {
let spector = new Spector()
spector.displayUI()
}
preventDefaultTouchActions();
let MEMORY_PAGES = 16 * 1024
if (MULTITHREADED) {
const MEMORY = 209715200; // 200MB
const PAGES = 64 * 1024;
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY_PAGES, shared: true})
await init(wasmPath, memory)
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY / PAGES, shared: true})
await maplibre.default(wasmPath, memory)
const schedulerPtr = create_pool_scheduler(() => {
return workerPath ? new PoolWorker(workerPath, {
maplibre.run(await maplibre.create_map(() => {
return workerPath ? new Worker(workerPath, {
type: 'module'
}) : MultithreadedPoolWorker();
}))
} else {
const memory = new WebAssembly.Memory({initial: 1024, shared: false})
await maplibre.default(wasmPath, memory);
let callbacks: {worker_callback?: (message: MessageEvent) => void} = {}
let map = await maplibre.create_map(() => {
let worker: Worker = workerPath ? new Worker(workerPath, {
type: 'module'
}) : PoolWorker();
worker.onmessage = (message: MessageEvent) => {
callbacks.worker_callback(message)
}
return worker;
})
// setupLegacyWebWorker(schedulerPtr, memory)
let clonedMap = maplibre.clone_map(map)
await run(schedulerPtr)
callbacks.worker_callback = (message) => {
let tag = message.data[0];
let data = new Uint8Array(message.data[1]);
// @ts-ignore TODO unsync_main_entry may not be defined
maplibre.singlethreaded_main_entry(clonedMap, tag, data)
}
maplibre.run(map)
}
}

View File

@ -1,32 +0,0 @@
/*import init, {InitOutput, tessellate_layers} from "./wasm-pack"
import {WebWorkerMessageType} from "./types"
let module: Promise<InitOutput> = null
onmessage = async message => {
let messageData: WebWorkerMessageType = message.data
console.dir(messageData)
switch (messageData.type) {
case "init":
if (module != null) {
return
}
module = init(undefined, messageData.memory)
break
case "fetch_tile":
let {threadLocalState, url, request_id} = messageData
await module
console.log("Fetching from " + self.name)
let result = await fetch(url)
let buffer = await result.arrayBuffer()
tessellate_layers(threadLocalState, request_id, new Uint8Array(buffer))
break
default:
console.warn("WebWorker received unknown message!")
break
}
}*/

View File

@ -1,7 +1,7 @@
import init, {child_entry_point} from "./wasm-pack"
import * as maplibre from "../wasm/maplibre"
onmessage = async message => {
const initialised = init(message.data[0], message.data[1]).catch(err => {
const initialised = maplibre.default(message.data[0], message.data[1]).catch(err => {
// Propagate to main `onerror`:
setTimeout(() => {
throw err;
@ -13,6 +13,7 @@ onmessage = async message => {
self.onmessage = async message => {
// This will queue further commands up until the module is fully initialised:
await initialised;
child_entry_point(message.data);
// @ts-ignore TODO may not exist
await maplibre.multithreaded_worker_entry(message.data);
};
}

View File

@ -1,17 +0,0 @@
/*
import {registerRoute} from 'workbox-routing';
import {CacheFirst} from 'workbox-strategies';
import {CacheableResponsePlugin} from 'workbox-cacheable-response';
registerRoute(
({url}) => url.pathname.endsWith('pbf'),
new CacheFirst({
cacheName: 'pbf-cache',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
})
]
})
);
*/

View File

@ -0,0 +1,23 @@
import * as maplibre from "../wasm/maplibre"
onmessage = async message => {
const memory = new WebAssembly.Memory({initial: 1024, shared: false})
let module = message.data[0];
const initialised = maplibre.default(module, memory).catch(err => {
// Propagate to main `onerror`:
setTimeout(() => {
throw err;
});
// Rethrow to keep promise rejected and prevent execution of further commands:
throw err;
});
self.onmessage = async message => {
// This will queue further commands up until the module is fully initialised:
await initialised;
let procedure_ptr = message.data[0];
let input = message.data[1];
// @ts-ignore TODO
await maplibre.singlethreaded_worker_entry(procedure_ptr, input);
};
}

View File

@ -1,9 +0,0 @@
export type WebWorkerMessageType = {
type: 'init',
memory: WebAssembly.Memory
} | {
type: 'fetch_tile',
threadLocalState: number,
url: string,
request_id: number,
}

View File

@ -9,7 +9,7 @@ pub struct WebError(pub String);
impl From<JsValue> for WebError {
fn from(maybe_error: JsValue) -> Self {
assert!(maybe_error.is_instance_of::<JSError>());
let error: JSError = maybe_error.dyn_into().unwrap();
WebError(error.message().as_string().unwrap())
let error: JSError = maybe_error.dyn_into().unwrap(); // TODO: Remove unwrap
WebError(error.message().as_string().unwrap()) // TODO: remove unwrap
}
}

View File

@ -1,12 +1,12 @@
use std::panic;
#![feature(allocator_api, new_uninit)]
use maplibre::{io::scheduler::Scheduler, MapBuilder};
use maplibre_winit::winit::WinitMapWindowConfig;
use std::{borrow::BorrowMut, cell::RefCell, mem, ops::Deref, panic, rc::Rc};
use maplibre::{io::scheduler::NopScheduler, Map, MapBuilder};
use maplibre_winit::winit::{WinitEnvironment, WinitMapWindowConfig};
use wasm_bindgen::prelude::*;
use crate::platform::{
http_client::WHATWGFetchHttpClient, schedule_method::WebWorkerPoolScheduleMethod,
};
use crate::platform::http_client::WHATWGFetchHttpClient;
mod error;
mod platform;
@ -38,31 +38,76 @@ pub fn wasm_bindgen_start() {
enable_tracing();
}
#[wasm_bindgen]
pub fn create_pool_scheduler(
new_worker: js_sys::Function,
) -> *mut Scheduler<WebWorkerPoolScheduleMethod> {
let scheduler = Box::new(Scheduler::new(WebWorkerPoolScheduleMethod::new(new_worker)));
#[cfg(not(target_feature = "atomics"))]
pub type MapType = Map<
WinitEnvironment<
NopScheduler,
WHATWGFetchHttpClient,
platform::singlethreaded::transferables::LinearTransferables,
platform::singlethreaded::apc::PassingAsyncProcedureCall,
>,
>;
Box::into_raw(scheduler)
#[cfg(target_feature = "atomics")]
pub type MapType = Map<
WinitEnvironment<
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
WHATWGFetchHttpClient,
maplibre::io::transferables::DefaultTransferables,
maplibre::io::apc::SchedulerAsyncProcedureCall<
WHATWGFetchHttpClient,
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
>,
>,
>;
#[wasm_bindgen]
pub async fn create_map(new_worker: js_sys::Function) -> u32 {
// Either call forget or the main loop to keep worker loop alive
let mut builder = MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(WHATWGFetchHttpClient::new());
#[cfg(target_feature = "atomics")]
{
builder = builder
.with_apc(maplibre::io::apc::SchedulerAsyncProcedureCall::new(
WHATWGFetchHttpClient::new(),
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler::new(
new_worker.clone(),
),
))
.with_scheduler(
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler::new(new_worker),
);
}
#[cfg(not(target_feature = "atomics"))]
{
builder = builder
.with_apc(platform::singlethreaded::apc::PassingAsyncProcedureCall::new(new_worker, 4))
.with_scheduler(NopScheduler);
}
let map: MapType = builder.build().initialize().await;
Rc::into_raw(Rc::new(RefCell::new(map))) as u32
}
#[wasm_bindgen]
pub async fn run(scheduler_ptr: *mut Scheduler<WebWorkerPoolScheduleMethod>) {
let scheduler: Box<Scheduler<WebWorkerPoolScheduleMethod>> =
unsafe { Box::from_raw(scheduler_ptr) };
pub unsafe fn clone_map(map_ptr: *const RefCell<MapType>) -> *const RefCell<MapType> {
let mut map = Rc::from_raw(map_ptr);
let rc = map.clone();
let cloned = Rc::into_raw(rc);
mem::forget(map);
cloned
}
// Either call forget or the main loop to keep worker loop alive
MapBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(WHATWGFetchHttpClient::new())
.with_existing_scheduler(*scheduler)
.build()
.initialize()
.await
.run();
#[wasm_bindgen]
pub unsafe fn run(map_ptr: *const RefCell<MapType>) {
let mut map = Rc::from_raw(map_ptr);
// std::mem::forget(scheduler);
map.deref().borrow().run();
}
#[cfg(test)]

View File

@ -23,12 +23,12 @@ impl WHATWGFetchHttpClient {
// Get the global scope
let global = js_sys::global();
assert!(global.is_instance_of::<WorkerGlobalScope>());
let scope = global.dyn_into::<WorkerGlobalScope>().unwrap();
let scope = global.dyn_into::<WorkerGlobalScope>().unwrap(); // TODO: remove unwrap
// Call fetch on global scope
let maybe_response = JsFuture::from(scope.fetch_with_request(&request)).await?;
assert!(maybe_response.is_instance_of::<Response>());
let response: Response = maybe_response.dyn_into().unwrap();
let response: Response = maybe_response.dyn_into().unwrap(); // TODO: remove unwrap
// Get ArrayBuffer
let maybe_array_buffer = JsFuture::from(response.array_buffer()?).await?;
@ -39,7 +39,7 @@ impl WHATWGFetchHttpClient {
let maybe_array_buffer = Self::fetch_array_buffer(url).await?;
assert!(maybe_array_buffer.is_instance_of::<ArrayBuffer>());
let array_buffer: ArrayBuffer = maybe_array_buffer.dyn_into().unwrap();
let array_buffer: ArrayBuffer = maybe_array_buffer.dyn_into().unwrap(); // TODO: remove unwrap
// Copy data to Vec<u8>
let buffer: Uint8Array = Uint8Array::new(&array_buffer);

View File

@ -1,52 +0,0 @@
use maplibre::{
coords::TileCoords,
io::{scheduler::Scheduler, TileRequestID},
stages::SharedThreadState,
};
use wasm_bindgen::prelude::*;
use super::schedule_method::WebWorkerPoolScheduleMethod;
#[wasm_bindgen]
extern "C" {
fn schedule_tile_request(url: &str, request_id: u32);
}
// FIXME
/*#[wasm_bindgen]
pub fn new_thread_local_state(scheduler_ptr: *mut Scheduler) -> *mut SharedThreadState {
let scheduler: Box<Scheduler> = unsafe { Box::from_raw(scheduler_ptr) };
let state = Box::new(scheduler.new_thread_local_state());
let state_ptr = Box::into_raw(state);
// Call forget such that scheduler does not get deallocated
std::mem::forget(scheduler);
return state_ptr;
}*/
#[wasm_bindgen]
pub fn new_thread_local_state(_scheduler_ptr: *mut Scheduler<WebWorkerPoolScheduleMethod>) -> u32 {
0
}
#[wasm_bindgen]
pub fn tessellate_layers(state_ptr: *mut SharedThreadState, request_id: u32, data: Box<[u8]>) {
let state: Box<SharedThreadState> = unsafe { Box::from_raw(state_ptr) };
state.process_tile(request_id, data).unwrap();
// Call forget such that scheduler does not get deallocated
std::mem::forget(state);
}
pub fn request_tile(request_id: TileRequestID, coords: TileCoords) {
schedule_tile_request(
format!(
"https://maps.tuerantuer.org/europe_germany/{z}/{x}/{y}.pbf",
x = coords.x,
y = coords.y,
z = coords.z,
)
.as_str(),
request_id,
)
}

View File

@ -1,3 +1,10 @@
use std::future::Future;
use maplibre::error::Error;
pub mod http_client;
pub mod pool;
pub mod schedule_method;
#[cfg(target_feature = "atomics")]
pub mod multithreaded;
#[cfg(not(target_feature = "atomics"))]
pub mod singlethreaded;

View File

@ -0,0 +1,2 @@
pub mod pool;
pub mod pool_scheduler;

View File

@ -2,7 +2,7 @@
//! web workers which can be used to execute work.
//! Adopted from [wasm-bindgen example](https://github.com/rustwasm/wasm-bindgen/blob/0eba2efe45801b71f8873bc368c58a8ed8e894ff/examples/raytrace-parallel/src/pool.rs)
use std::{cell::RefCell, rc::Rc};
use std::{borrow::BorrowMut, cell::RefCell, future::Future, rc::Rc};
use js_sys::Promise;
use rand::prelude::*;
@ -23,7 +23,6 @@ pub struct WorkerPool {
struct PoolState {
workers: RefCell<Vec<Worker>>,
callback: Closure<dyn FnMut(Event)>,
}
struct Work {
@ -46,9 +45,6 @@ impl WorkerPool {
new_worker,
state: Rc::new(PoolState {
workers: RefCell::new(Vec::with_capacity(initial)),
callback: Closure::wrap(Box::new(|event: Event| {
log::error!("unhandled event: {}", event.type_());
}) as Box<dyn FnMut(Event)>),
}),
};
for _ in 0..initial {
@ -95,13 +91,13 @@ impl WorkerPool {
/// message is sent to it.
fn worker(&self) -> Result<Worker, JsValue> {
let workers = self.state.workers.borrow();
let result = match workers.choose(&mut rand::thread_rng()) {
let result = match workers.choose(&mut thread_rng()) {
Some(worker) => Some(worker),
None => None,
};
if result.is_none() {
self.spawn();
self.spawn()?;
}
match result {
@ -124,13 +120,13 @@ impl WorkerPool {
/// message is sent to it.
pub fn execute(&self, f: impl (FnOnce() -> Promise) + Send + 'static) -> Result<(), JsValue> {
let worker = self.worker()?;
let work = Box::new(Work { func: Box::new(f) });
let ptr = Box::into_raw(work);
match worker.post_message(&JsValue::from(ptr as u32)) {
let work = Work { func: Box::new(f) };
let work_ptr = Box::into_raw(Box::new(work));
match worker.post_message(&JsValue::from(work_ptr as u32)) {
Ok(()) => Ok(()),
Err(e) => {
unsafe {
drop(Box::from_raw(ptr));
drop(Box::from_raw(work_ptr));
}
Err(e)
}
@ -140,23 +136,18 @@ impl WorkerPool {
impl PoolState {
fn push(&self, worker: Worker) {
//worker.set_onmessage(Some(self.callback.as_ref().unchecked_ref()));
//worker.set_onerror(Some(self.callback.as_ref().unchecked_ref()));
let mut workers = self.workers.borrow_mut();
for existing_worker in workers.iter() {
assert!(existing_worker as &JsValue != &worker as &JsValue);
assert_ne!(existing_worker as &JsValue, &worker as &JsValue);
}
workers.push(worker);
}
}
/// Entry point invoked by `worker.js`, a bit of a hack but see the "TODO" above
/// about `worker.js` in general.
/// Entry point invoked by the worker.
#[wasm_bindgen]
pub async fn child_entry_point(ptr: u32) -> Result<(), JsValue> {
pub async fn multithreaded_worker_entry(ptr: u32) -> Result<(), JsValue> {
let ptr = unsafe { Box::from_raw(ptr as *mut Work) };
//let global = js_sys::global().unchecked_into::<DedicatedWorkerGlobalScope>();
JsFuture::from((ptr.func)()).await?;
//global.post_message(&JsValue::undefined())?;
Ok(())
}

View File

@ -1,34 +1,35 @@
use std::future::Future;
use maplibre::{error::Error, io::scheduler::ScheduleMethod};
use log::warn;
use maplibre::{error::Error, io::scheduler::Scheduler};
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::Worker;
use super::pool::WorkerPool;
pub struct WebWorkerPoolScheduleMethod {
pub struct WebWorkerPoolScheduler {
pool: WorkerPool,
}
impl WebWorkerPoolScheduleMethod {
impl WebWorkerPoolScheduler {
pub fn new(new_worker: js_sys::Function) -> Self {
Self {
pool: WorkerPool::new(
4,
1,
Box::new(move || {
new_worker
.call0(&JsValue::undefined())
.unwrap()
.unwrap() // FIXME (wasm-executor): Remove unwrap
.dyn_into::<Worker>()
.unwrap()
.unwrap() // FIXME (wasm-executor): remove unwrap
}),
)
.unwrap(),
.unwrap(), // FIXME (wasm-executor): Remove unwrap
}
}
}
impl ScheduleMethod for WebWorkerPoolScheduleMethod {
impl Scheduler for WebWorkerPoolScheduler {
fn schedule<T>(
&self,
future_factory: impl (FnOnce() -> T) + Send + 'static,
@ -43,7 +44,7 @@ impl ScheduleMethod for WebWorkerPoolScheduleMethod {
Ok(JsValue::undefined())
})
})
.unwrap();
.unwrap(); // FIXME (wasm-executor): remove unwrap
Ok(())
}
}

View File

@ -0,0 +1,245 @@
use std::{
any::TypeId,
borrow::Borrow,
cell::RefCell,
collections::HashMap,
marker::PhantomData,
mem,
mem::{size_of, MaybeUninit},
ops::Deref,
pin::Pin,
rc::Rc,
sync::{
mpsc,
mpsc::{Receiver, Sender},
},
};
use js_sys::Uint8Array;
use maplibre::{
environment::Environment,
io::{
apc::{AsyncProcedure, AsyncProcedureCall, Context, Input, Message},
scheduler::Scheduler,
source_client::{HttpClient, HttpSourceClient, SourceClient},
transferables::Transferables,
},
};
use wasm_bindgen::{prelude::*, JsCast, JsValue};
use web_sys::{DedicatedWorkerGlobalScope, Worker};
use crate::{
platform::singlethreaded::transferables::{
InnerData, LinearTessellatedLayer, LinearTransferables,
},
MapType, WHATWGFetchHttpClient,
};
type UsedTransferables = LinearTransferables;
type UsedHttpClient = WHATWGFetchHttpClient;
type UsedContext = PassingContext;
enum SerializedMessageTag {
TileTessellated = 1,
UnavailableLayer = 2,
TessellatedLayer = 3,
}
impl SerializedMessageTag {
fn from_u32(tag: u32) -> Option<Self> {
match tag {
x if x == SerializedMessageTag::UnavailableLayer as u32 => {
Some(SerializedMessageTag::UnavailableLayer)
}
x if x == SerializedMessageTag::TessellatedLayer as u32 => {
Some(SerializedMessageTag::TessellatedLayer)
}
x if x == SerializedMessageTag::TileTessellated as u32 => {
Some(SerializedMessageTag::TileTessellated)
}
_ => None,
}
}
}
trait SerializableMessage {
fn serialize(&self) -> &[u8];
fn deserialize(tag: SerializedMessageTag, data: Uint8Array) -> Message<UsedTransferables>;
fn tag(&self) -> SerializedMessageTag;
}
impl SerializableMessage for Message<LinearTransferables> {
fn serialize(&self) -> &[u8] {
match self {
Message::TileTessellated(data) => bytemuck::bytes_of(data),
Message::UnavailableLayer(data) => bytemuck::bytes_of(data),
Message::TessellatedLayer(data) => bytemuck::bytes_of(data.data.as_ref()),
}
}
fn deserialize(tag: SerializedMessageTag, data: Uint8Array) -> Message<UsedTransferables> {
match tag {
SerializedMessageTag::TileTessellated => {
Message::<UsedTransferables>::TileTessellated(*bytemuck::from_bytes::<
<UsedTransferables as Transferables>::TileTessellated,
>(&data.to_vec()))
}
SerializedMessageTag::UnavailableLayer => {
Message::<UsedTransferables>::UnavailableLayer(*bytemuck::from_bytes::<
<UsedTransferables as Transferables>::UnavailableLayer,
>(&data.to_vec()))
}
SerializedMessageTag::TessellatedLayer => {
Message::<UsedTransferables>::TessellatedLayer(LinearTessellatedLayer {
data: unsafe {
let mut uninit = Box::<InnerData>::new_zeroed();
data.raw_copy_to_ptr(uninit.as_mut_ptr() as *mut u8);
let x = uninit.assume_init();
x
},
})
}
}
}
fn tag(&self) -> SerializedMessageTag {
match self {
Message::TileTessellated(_) => SerializedMessageTag::TileTessellated,
Message::UnavailableLayer(_) => SerializedMessageTag::UnavailableLayer,
Message::TessellatedLayer(_) => SerializedMessageTag::TessellatedLayer,
}
}
}
#[derive(Clone)]
pub struct PassingContext {
source_client: SourceClient<UsedHttpClient>,
}
impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
fn send(&self, data: Message<UsedTransferables>) {
let tag = data.tag();
let serialized = data.serialize();
let serialized_array_buffer = js_sys::ArrayBuffer::new(serialized.len() as u32);
let serialized_array = js_sys::Uint8Array::new(&serialized_array_buffer);
unsafe {
serialized_array.set(&Uint8Array::view(serialized), 0);
}
let global = js_sys::global().unchecked_into::<DedicatedWorkerGlobalScope>(); // FIXME (wasm-executor): Remove unchecked
let array = js_sys::Array::new();
array.push(&JsValue::from(tag as u32));
array.push(&serialized_array_buffer);
global.post_message(&array).unwrap(); // FIXME (wasm-executor) Remove unwrap
}
fn source_client(&self) -> &SourceClient<UsedHttpClient> {
&self.source_client
}
}
pub struct PassingAsyncProcedureCall {
new_worker: Box<dyn Fn() -> Worker>,
workers: Vec<Worker>,
received: Vec<Message<UsedTransferables>>,
}
impl PassingAsyncProcedureCall {
pub fn new(new_worker: js_sys::Function, initial_workers: u8) -> Self {
let create_new_worker = Box::new(move || {
new_worker
.call0(&JsValue::undefined())
.unwrap() // FIXME (wasm-executor): Remove unwrap
.dyn_into::<Worker>()
.unwrap() // FIXME (wasm-executor): Remove unwrap
});
let workers = (0..initial_workers)
.map(|_| {
let worker: Worker = create_new_worker();
let array = js_sys::Array::new();
array.push(&wasm_bindgen::module());
worker.post_message(&array).unwrap(); // FIXME (wasm-executor): Remove unwrap
worker
})
.collect::<Vec<_>>();
Self {
new_worker: create_new_worker,
workers,
received: vec![],
}
}
}
impl AsyncProcedureCall<UsedTransferables, UsedHttpClient> for PassingAsyncProcedureCall {
type Context = UsedContext;
fn receive(&mut self) -> Option<Message<UsedTransferables>> {
self.received.pop()
}
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
let procedure_ptr = procedure as *mut AsyncProcedure<Self::Context> as u32; // FIXME (wasm-executor): is u32 fine, define an overflow safe function?
let input = serde_json::to_string(&input).unwrap(); // FIXME (wasm-executor): Remove unwrap
let array = js_sys::Array::new();
array.push(&JsValue::from(procedure_ptr));
array.push(&JsValue::from(input));
self.workers[0].post_message(&array).unwrap(); // FIXME (wasm-executor): Remove unwrap
}
}
/// Entry point invoked by the worker.
#[wasm_bindgen]
pub async fn singlethreaded_worker_entry(procedure_ptr: u32, input: String) -> Result<(), JsValue> {
let procedure: AsyncProcedure<UsedContext> = unsafe { std::mem::transmute(procedure_ptr) };
let input = serde_json::from_str::<Input>(&input).unwrap(); // FIXME (wasm-executor): Remove unwrap
let context = PassingContext {
source_client: SourceClient::Http(HttpSourceClient::new(WHATWGFetchHttpClient::new())),
};
(procedure)(input, context).await;
Ok(())
}
/// Entry point invoked by the main thread.
#[wasm_bindgen]
pub unsafe fn singlethreaded_main_entry(
map_ptr: *const RefCell<MapType>,
type_id: u32,
data: Uint8Array,
) -> Result<(), JsValue> {
// FIXME (wasm-executor): Can we make this call safe? check if it was cloned before?
let mut map = Rc::from_raw(map_ptr);
let message = Message::<UsedTransferables>::deserialize(
SerializedMessageTag::from_u32(type_id).unwrap(),
data,
);
map.deref()
.borrow()
.map_schedule()
.deref()
.borrow()
.apc
.deref()
.borrow_mut()
.received
.push(message);
mem::forget(map); // FIXME (wasm-executor): Enforce this somehow
Ok(())
}

View File

@ -0,0 +1,2 @@
pub mod apc;
pub mod transferables;

View File

@ -0,0 +1,158 @@
use bytemuck::{TransparentWrapper, Zeroable};
use bytemuck_derive::{Pod, Zeroable};
use maplibre::{
benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer},
coords::WorldTileCoords,
io::{
tile_repository::StoredLayer,
transferables::{TessellatedLayer, TileTessellated, Transferables, UnavailableLayer},
},
render::ShaderVertex,
tile::Layer,
};
// FIXME (wasm-executor): properly do this!, fix this whole file
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct WrapperWorldTileCoords(WorldTileCoords);
unsafe impl TransparentWrapper<WorldTileCoords> for WrapperWorldTileCoords {}
unsafe impl bytemuck::Zeroable for WrapperWorldTileCoords {}
unsafe impl bytemuck::Pod for WrapperWorldTileCoords {}
#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct LongVertexShader([ShaderVertex; 15000]);
unsafe impl TransparentWrapper<[ShaderVertex; 15000]> for LongVertexShader {}
unsafe impl bytemuck::Zeroable for LongVertexShader {}
unsafe impl bytemuck::Pod for LongVertexShader {}
#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct LongIndices([IndexDataType; 40000]);
unsafe impl TransparentWrapper<[IndexDataType; 40000]> for LongIndices {}
unsafe impl bytemuck::Zeroable for LongIndices {}
unsafe impl bytemuck::Pod for LongIndices {}
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct LinearTileTessellated {
pub coords: WrapperWorldTileCoords,
}
impl TileTessellated for LinearTileTessellated {
fn new(coords: WorldTileCoords) -> Self {
Self {
coords: WrapperWorldTileCoords::wrap(coords),
}
}
fn coords(&self) -> &WorldTileCoords {
WrapperWorldTileCoords::peel_ref(&self.coords)
}
}
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct LinearUnavailableLayer {
pub coords: WrapperWorldTileCoords,
pub layer_name: [u8; 32],
}
impl UnavailableLayer for LinearUnavailableLayer {
fn new(coords: WorldTileCoords, layer_name: String) -> Self {
let mut new_layer_name = [0; 32];
new_layer_name[0..layer_name.len()].clone_from_slice(layer_name.as_bytes());
Self {
coords: WrapperWorldTileCoords::wrap(coords),
layer_name: new_layer_name,
}
}
fn to_stored_layer(self) -> StoredLayer {
StoredLayer::UnavailableLayer {
coords: WrapperWorldTileCoords::peel(self.coords),
layer_name: String::from_utf8(Vec::from(self.layer_name)).unwrap(), // FIXME (wasm-executor): Remove unwrap
}
}
}
#[derive(Copy, Clone, Pod, Zeroable)]
#[repr(C)]
pub struct InnerData {
pub coords: WrapperWorldTileCoords,
pub layer_name: [u8; 32],
pub layer_name_len: usize,
pub vertices: LongVertexShader,
pub vertices_len: usize,
pub indices: LongIndices,
pub indices_len: usize,
pub usable_indices: u32,
/// Holds for each feature the count of indices.
pub feature_indices: [u32; 2048],
pub feature_indices_len: usize,
}
#[derive(Clone)]
pub struct LinearTessellatedLayer {
pub data: Box<InnerData>,
}
impl TessellatedLayer for LinearTessellatedLayer {
fn new(
coords: WorldTileCoords,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
feature_indices: Vec<u32>,
layer_data: Layer,
) -> Self {
let mut data = Box::new(InnerData {
coords: WrapperWorldTileCoords::wrap(coords),
layer_name: [0; 32],
layer_name_len: layer_data.name.len(),
vertices: LongVertexShader::wrap([ShaderVertex::zeroed(); 15000]),
vertices_len: buffer.buffer.vertices.len(),
indices: LongIndices::wrap([IndexDataType::zeroed(); 40000]),
indices_len: buffer.buffer.indices.len(),
usable_indices: buffer.usable_indices,
feature_indices: [0u32; 2048],
feature_indices_len: feature_indices.len(),
});
data.vertices.0[0..buffer.buffer.vertices.len()].clone_from_slice(&buffer.buffer.vertices);
data.indices.0[0..buffer.buffer.indices.len()].clone_from_slice(&buffer.buffer.indices);
data.feature_indices[0..feature_indices.len()].clone_from_slice(&feature_indices);
data.layer_name[0..layer_data.name.len()].clone_from_slice(layer_data.name.as_bytes());
Self { data }
}
fn to_stored_layer(self) -> StoredLayer {
let layer = StoredLayer::TessellatedLayer {
coords: WrapperWorldTileCoords::peel(self.data.coords),
layer_name: String::from_utf8(Vec::from(
&self.data.layer_name[..self.data.layer_name_len],
))
.unwrap(), // FIXME (wasm-executor): Remove unwrap
buffer: OverAlignedVertexBuffer::from_slices(
&self.data.vertices.0[..self.data.vertices_len],
&self.data.indices.0[..self.data.indices_len],
self.data.usable_indices,
),
feature_indices: Vec::from(&self.data.feature_indices[..self.data.feature_indices_len]),
};
layer
}
}
pub struct LinearTransferables;
impl Transferables for LinearTransferables {
type TileTessellated = LinearTileTessellated;
type UnavailableLayer = LinearUnavailableLayer;
type TessellatedLayer = LinearTessellatedLayer;
}