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 = [ rustflags = [
# Enabled unstable APIs from web_sys # Enabled unstable APIs from web_sys
"--cfg=web_sys_unstable_apis", "--cfg=web_sys_unstable_apis",
# Enables features which are required for shared-memory "-C", "link-args=--import-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",
] ]
runner = 'wasm-bindgen-test-runner' 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 name: deploy
description: Deploy on maxammann.org description: Deploy on cloudflare
inputs: inputs:
project: project:

View File

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

View File

@ -12,14 +12,16 @@ jobs:
- uses: extractions/setup-just@v1 - uses: extractions/setup-just@v1
- name: Install toolchain - name: Install toolchain
shell: bash shell: bash
run: just default-toolchain run: |
just stable-toolchain
just stable-targets x86_64-unknown-linux-gnu
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
run: sudo apt-get install -y libwayland-dev libxkbcommon-dev # Required for winit run: sudo apt-get install -y libwayland-dev libxkbcommon-dev # Required for winit
- name: Build - name: Build
shell: bash shell: bash
run: cargo build -p maplibre-demo run: cargo build -p maplibre-demo --release --target x86_64-unknown-linux-gnu
- name: Check - name: Check
shell: bash shell: bash
run: just check maplibre-demo x86_64-unknown-linux-gnu run: just check maplibre-demo x86_64-unknown-linux-gnu
@ -29,4 +31,4 @@ jobs:
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: maplibre-rs 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 }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install toolchain - name: Install toolchain
shell: bash shell: bash
run: just default-toolchain run: |
just stable-toolchain
just stable-targets x86_64-apple-darwin
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Build - name: Build
shell: bash shell: bash
@ -29,4 +31,4 @@ jobs:
with: with:
name: maplibre-x86_64-apple-darwin-demo name: maplibre-x86_64-apple-darwin-demo
path: apple/xcode/build/Build/Products/Debug/*.app path: apple/xcode/build/Build/Products/Debug/*.app
if-no-files-found: error if-no-files-found: error

View File

@ -12,7 +12,9 @@ jobs:
- uses: extractions/setup-just@v1 - uses: extractions/setup-just@v1
- name: Install toolchain - name: Install toolchain
shell: bash shell: bash
run: just default-toolchain run: |
just stable-toolchain
just stable-targets x86_64-pc-windows-msvc
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- uses: ilammy/msvc-dev-cmd@v1 # Provide access to lib.exe - uses: ilammy/msvc-dev-cmd@v1 # Provide access to lib.exe
- name: Build - name: Build
@ -28,4 +30,4 @@ jobs:
with: with:
name: maplibre-x86_64-windows-demo name: maplibre-x86_64-windows-demo
path: target/x86_64-pc-windows-msvc/release/maplibre-demo.exe path: target/x86_64-pc-windows-msvc/release/maplibre-demo.exe
if-no-files-found: error if-no-files-found: error

View File

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

View File

@ -15,7 +15,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install toolchain - name: Install toolchain
shell: bash 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 - uses: Swatinem/rust-cache@v1
- name: Build - name: Build
shell: bash shell: bash

View File

@ -11,70 +11,58 @@ on:
deploy: deploy:
required: true required: true
type: boolean type: boolean
name:
required: true
type: string
webgl:
required: true
type: boolean
multithreaded:
required: true
type: boolean
jobs: jobs:
library-webgl: build:
name: Build WebGL name: Build
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: extractions/setup-just@v1 - uses: extractions/setup-just@v1
- name: Install nightly toolchain - name: Install nightly toolchain
shell: bash 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 - uses: Swatinem/rust-cache@v1
- name: Build lib - name: Build lib
shell: bash shell: bash
run: just web-lib build-webgl run: just web-lib build --release ${{ inputs.webgl && '--webgl' || '' }} ${{ inputs.multithreaded && '--multithreaded' || '' }}
- name: Build demo - name: Build demo
shell: bash shell: bash
run: just web-demo build run: just web-demo build
- name: Check - name: Check
shell: bash shell: bash
run: just web-check "web-webgl" run: just nightly-check web wasm32-unknown-unknown ${{ inputs.webgl && 'web-webgl' || '""' }}
- name: Test - name: Test
shell: bash shell: bash
run: | run: just web-test "web-webgl"
# 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"
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: webgl-demo name: ${{ inputs.name }}
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
path: web/demo/dist/ path: web/demo/dist/
deploy: deploy:
needs: [library-webgl, library-webgpu] needs: [build]
if: inputs.deploy if: inputs.deploy
name: Deploy name: Deploy
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
@ -82,13 +70,10 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/download-artifact@v2 - uses: actions/download-artifact@v2
with: with:
name: webgl-demo name: ${{ inputs.name }}
path: demo/webgl path: demo
- uses: actions/download-artifact@v2
with:
name: webgpu-demo
path: demo/webgpu
- name: Set HTTP Headers for Cloudflare - name: Set HTTP Headers for Cloudflare
if: inputs.multithreaded
shell: bash shell: bash
run: | run: |
echo "/* echo "/*
@ -97,7 +82,7 @@ jobs:
- name: Deploy - name: Deploy
uses: ./.github/actions/cloudflare-deploy uses: ./.github/actions/cloudflare-deploy
with: with:
project: maplibre-rs-demos project: ${{ inputs.name }}
source: demo source: demo
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }} CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}

View File

@ -24,9 +24,28 @@ jobs:
secrets: inherit secrets: inherit
library-android: library-android:
uses: ./.github/workflows/library-android.yml uses: ./.github/workflows/library-android.yml
library-web: library-web-webgl:
uses: ./.github/workflows/library-web.yml uses: ./.github/workflows/library-web.yml
with: 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 deploy: true
secrets: inherit secrets: inherit
library-apple: library-apple:

View File

@ -19,9 +19,26 @@ jobs:
deploy: false deploy: false
library-android: library-android:
uses: ./.github/workflows/library-android.yml uses: ./.github/workflows/library-android.yml
library-web: library-web-webgl:
uses: ./.github/workflows/library-web.yml uses: ./.github/workflows/library-web.yml
with: 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 deploy: false
library-apple: library-apple:
uses: ./.github/workflows/library-apple.yml uses: ./.github/workflows/library-apple.yml

View File

@ -12,7 +12,9 @@ jobs:
- uses: extractions/setup-just@v1 - uses: extractions/setup-just@v1
- name: Install toolchain - name: Install toolchain
shell: bash shell: bash
run: just default-toolchain run: |
just stable-toolchain
just stable-targets x86_64-unknown-linux-gnu
- uses: Swatinem/rust-cache@v1 - uses: Swatinem/rust-cache@v1
- name: Install GPU Drivers - name: Install GPU Drivers
uses: ./.github/actions/install-driver uses: ./.github/actions/install-driver
@ -20,4 +22,4 @@ jobs:
uses: ./.github/actions/download-test-data uses: ./.github/actions/download-test-data
- name: Benchmark - name: Benchmark
shell: bash shell: bash
run: WGPU_BACKEND=vulkan just benchmark run: WGPU_BACKEND=vulkan just benchmark

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build WASM" type="CargoCommandRunConfiguration" factoryName="Cargo Command"> <configuration default="false" name="Build WASM (single-threaded)" 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" /> <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="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" /> <option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" /> <option name="requiredFeatures" value="true" />

View File

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

View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager"> <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="command" value="run -p maplibre-demo --features trace -- headed" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" /> <option name="channel" value="DEFAULT" />

View File

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager"> <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="command" value="run -p maplibre-demo --release --features trace -- headed" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" /> <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 codegen-units = 1
opt-level = 's' opt-level = 's'
panic = "abort" panic = "abort"
strip = "debuginfo" strip = "debuginfo"
[profile.bench] [profile.bench]

View File

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

View File

@ -1,10 +1,12 @@
use maplibre::{ use maplibre::{
platform::{ io::apc::SchedulerAsyncProcedureCall,
http_client::ReqwestHttpClient, run_multithreaded, schedule_method::TokioScheduleMethod, platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
},
MapBuilder, 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")))] #[cfg(not(any(target_os = "macos", target_os = "ios")))]
compile_error!("apple works only on macOS and 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() { pub fn maplibre_apple_main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
run_multithreaded(async { run_headed_map(None);
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()
})
} }

View File

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

View File

@ -4,16 +4,15 @@ use criterion::{criterion_group, criterion_main, Criterion};
use maplibre::{ use maplibre::{
coords::{WorldTileCoords, ZoomLevel}, coords::{WorldTileCoords, ZoomLevel},
error::Error, error::Error,
headless::{utils::HeadlessPipelineProcessor, HeadlessMapWindowConfig}, headless::{utils::HeadlessPipelineProcessor, HeadlessEnvironment, HeadlessMapWindowConfig},
io::{ io::{
apc::SchedulerAsyncProcedureCall,
pipeline::{PipelineContext, Processable}, pipeline::{PipelineContext, Processable},
source_client::HttpSourceClient, source_client::HttpSourceClient,
tile_pipelines::build_vector_tile_pipeline, tile_pipelines::build_vector_tile_pipeline,
TileRequest, TileRequest,
}, },
platform::{ platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
http_client::ReqwestHttpClient, run_multithreaded, schedule_method::TokioScheduleMethod,
},
render::settings::{RendererSettings, TextureFormat}, render::settings::{RendererSettings, TextureFormat},
window::WindowSize, window::WindowSize,
MapBuilder, MapBuilder,
@ -22,19 +21,27 @@ use maplibre::{
fn headless_render(c: &mut Criterion) { fn headless_render(c: &mut Criterion) {
c.bench_function("headless_render", |b| { c.bench_function("headless_render", |b| {
let mut map = run_multithreaded(async { let mut map = run_multithreaded(async {
let mut map = MapBuilder::new() let client = ReqwestHttpClient::new(None);
.with_map_window_config(HeadlessMapWindowConfig {
size: WindowSize::new(1000, 1000).unwrap(), let mut map = MapBuilder::<
}) HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>,
.with_http_client(ReqwestHttpClient::new(None)) >::new()
.with_schedule_method(TokioScheduleMethod::new()) .with_map_window_config(HeadlessMapWindowConfig {
.with_renderer_settings(RendererSettings { size: WindowSize::new(1000, 1000).unwrap(),
texture_format: TextureFormat::Rgba8UnormSrgb, })
..RendererSettings::default() .with_http_client(client.clone())
}) .with_apc(SchedulerAsyncProcedureCall::new(
.build() client,
.initialize_headless() TokioScheduler::new(),
.await; ))
.with_scheduler(TokioScheduler::new())
.with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,
..RendererSettings::default()
})
.build()
.initialize_headless()
.await;
map.map_schedule map.map_schedule
.fetch_process(&WorldTileCoords::from((0, 0, ZoomLevel::default()))) .fetch_process(&WorldTileCoords::from((0, 0, ZoomLevel::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: The following syntax is used to resolve referenced WebWorkers:
```ts ```ts
new Worker(new URL("./pool.worker.ts", import.meta.url), { new Worker(new URL("./multithreaded-pool.worker.ts", import.meta.url), {
type: 'module' type: 'module'
}); });
``` ```
@ -107,7 +107,7 @@ See config in `web/lib/build.mjs` for an example usage.
### Babel & TypeScript ### Babel & TypeScript
Babel and TypeScript both can produce ESM modules, but they **fail with transforming references within the source code** 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. 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. The only stable solution to this is Parcel. Parcel also has good documentation around the bundling of WebWorkers.
@ -277,4 +277,4 @@ Example config in `package.json:
### Rollup ### Rollup
Not yet evaluated Not yet evaluated

View File

@ -102,18 +102,35 @@ click on run. This will start the MacOS application.
## Web (WebGL, WebGPU) ## Web (WebGL, WebGPU)
If you have a browser which already supports a recent version of the WebGPU specification then you can start a You need to first build the library for WebGL or WebGPU. Optionally, you can also enabled multi-threading support,
development server using the following commands. 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 ```bash
cd web just web-lib build # WebGPU
npm run start
``` ```
If you want to run maplibre-rs with WebGL which is supported on every major browser, then you have to use the following If not, then you must enable WebGL support:
command.
```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 ```bash
just web-lib build
just web-demo start just web-demo start
``` ```

View File

@ -5,15 +5,38 @@
set shell := ["bash", "-c"] set shell := ["bash", "-c"]
export NIGHTLY_TOOLCHAIN := "nightly-2022-07-03" export NIGHTLY_TOOLCHAIN := "nightly-2022-07-03"
export STABLE_TOOLCHAIN := "1.62"
export CARGO_TERM_COLOR := "always" export CARGO_TERM_COLOR := "always"
export RUST_BACKTRACE := "1" 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 rustup component add clippy --toolchain $NIGHTLY_TOOLCHAIN
fixup: fixup:
cargo clippy --no-deps -p maplibre --fix cargo clippy --no-deps -p maplibre --fix
cargo clippy --allow-dirty --no-deps -p maplibre-winit --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-winit --target x86_64-linux-android --fix
cargo clippy --allow-dirty --no-deps -p maplibre-android --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}} 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: test PROJECT ARCH:
cargo test -p {{PROJECT}} --target {{ARCH}} cargo test -p {{PROJECT}} --target {{ARCH}}
benchmark: benchmark:
cargo bench -p benchmarks cargo bench -p benchmarks
install-rustfmt: nightly-toolchain fmt: nightly-install-rustfmt
rustup component add rustfmt --toolchain $NIGHTLY_TOOLCHAIN
fmt: install-rustfmt
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo fmt export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo fmt
fmt-check: install-rustfmt fmt-check: nightly-install-rustfmt
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo fmt -- --check 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: web-install PROJECT:
cd web/{{PROJECT}} && npm install cd web/{{PROJECT}} && npm install
@ -65,19 +77,16 @@ web-install PROJECT:
# Example: just web-lib build-webgl # Example: just web-lib build-webgl
# Example: just web-lib watch # Example: just web-lib watch
# Example: just web-lib watch-webgl # Example: just web-lib watch-webgl
web-lib TARGET: nightly-toolchain (web-install "lib") web-lib TARGET *FLAGS: nightly-toolchain (web-install "lib")
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd web/lib && npm run {{TARGET}} export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd web/lib && npm run {{TARGET}} -- {{FLAGS}}
# Example: just web-demo start # Example: just web-demo start
# Example: just web-demo build # Example: just web-demo build
web-demo TARGET: (web-install "demo") web-demo TARGET *FLAGS: (web-install "demo")
cd web/demo && npm run {{TARGET}} cd web/demo && npm run {{TARGET}} -- {{FLAGS}}
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-test FEATURES: nightly-toolchain 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: #profile-bench:
# cargo flamegraph --bench render -- --bench # cargo flamegraph --bench render -- --bench

View File

@ -10,7 +10,7 @@ readme = "../README.md"
[features] [features]
web-webgl = ["maplibre/web-webgl"] web-webgl = ["maplibre/web-webgl"]
trace = ["maplibre/trace", "tracing-subscriber", "tracing-tracy", "tracy-client"] trace = ["maplibre/trace"]
[dependencies] [dependencies]
env_logger = "0.9.0" env_logger = "0.9.0"
@ -19,10 +19,4 @@ maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
tile-grid = "0.3" tile-grid = "0.3"
tracing = "0.1.35" clap = { version = "3.2.12", features = ["derive"] }
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,29 +1,37 @@
use maplibre::{ use maplibre::{
coords::{LatLon, WorldTileCoords}, coords::{LatLon, WorldTileCoords},
error::Error, error::Error,
headless::HeadlessMapWindowConfig, headless::{HeadlessEnvironment, HeadlessMapWindowConfig},
platform::{http_client::ReqwestHttpClient, schedule_method::TokioScheduleMethod}, io::apc::SchedulerAsyncProcedureCall,
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
render::settings::{RendererSettings, TextureFormat}, render::settings::{RendererSettings, TextureFormat},
util::grid::google_mercator, util::grid::google_mercator,
window::WindowSize, window::WindowSize,
MapBuilder, MapBuilder,
}; };
use maplibre_winit::winit::WinitEnvironment;
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator}; use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) { pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
let mut map = MapBuilder::new() let client = ReqwestHttpClient::new(None);
.with_map_window_config(HeadlessMapWindowConfig { let mut map =
size: WindowSize::new(tile_size, tile_size).unwrap(), MapBuilder::<HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>>::new()
}) .with_map_window_config(HeadlessMapWindowConfig {
.with_http_client(ReqwestHttpClient::new(None)) size: WindowSize::new(tile_size, tile_size).unwrap(),
.with_schedule_method(TokioScheduleMethod::new()) })
.with_renderer_settings(RendererSettings { .with_http_client(client.clone())
texture_format: TextureFormat::Rgba8UnormSrgb, .with_apc(SchedulerAsyncProcedureCall::new(
..RendererSettings::default() client,
}) TokioScheduler::new(),
.build() )) // FIXME (wasm-executor): avoid passing client and scheduler here
.initialize_headless() .with_scheduler(TokioScheduler::new())
.await; .with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,
..RendererSettings::default()
})
.build()
.initialize_headless()
.await;
let tile_limits = google_mercator().tile_limits( let tile_limits = google_mercator().tile_limits(
extent_wgs84_to_merc(&Extent { extent_wgs84_to_merc(&Extent {

View File

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

View File

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

View File

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

View File

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

View File

@ -3,10 +3,16 @@
//! * Platform Events like suspend/resume //! * Platform Events like suspend/resume
//! * Render a new frame //! * 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 winit::window::WindowBuilder;
use super::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow}; use super::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
use crate::winit::WinitEnvironment;
impl MapWindow for WinitMapWindow { impl MapWindow for WinitMapWindow {
fn size(&self) -> WindowSize { 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] [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 = { 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" env_logger = "0.9.0"
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "gzip"] } reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "gzip"] }
reqwest-middleware-cache = "0.1.1" # FIXME: Untrusted dependency 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] [dependencies]
async-trait = "0.1.57" 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
tracing = "0.1.36" tracing = "0.1.36"

View File

@ -6,7 +6,9 @@ use std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
}; };
use bytemuck_derive::{Pod, Zeroable};
use cgmath::{num_traits::Pow, AbsDiffEq, Matrix4, Point3, Vector3}; use cgmath::{num_traits::Pow, AbsDiffEq, Matrix4, Point3, Vector3};
use serde::{Deserialize, Serialize};
use crate::{ use crate::{
style::source::TileAddressingScheme, 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); pub struct ZoomLevel(u8);
impl ZoomLevel { impl ZoomLevel {
@ -83,7 +101,7 @@ impl std::ops::Add<u8> for ZoomLevel {
type Output = ZoomLevel; type Output = ZoomLevel;
fn add(self, rhs: u8) -> Self::Output { 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) ZoomLevel(zoom_level)
} }
} }
@ -92,7 +110,7 @@ impl std::ops::Sub<u8> for ZoomLevel {
type Output = ZoomLevel; type Output = ZoomLevel;
fn sub(self, rhs: u8) -> Self::Output { 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) ZoomLevel(zoom_level)
} }
} }
@ -289,7 +307,22 @@ impl From<(u32, u32, ZoomLevel)> for TileCoords {
/// # Coordinate System Origin /// # Coordinate System Origin
/// ///
/// The origin of the coordinate system is in the upper-left corner. /// 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 struct WorldTileCoords {
pub x: i32, pub x: i32,
pub y: 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, future::Future,
io::Write, io::Write,
iter, iter,
marker::PhantomData,
ops::{Deref, Range}, ops::{Deref, Range},
sync::Arc, sync::Arc,
}; };
@ -17,11 +18,12 @@ use crate::{
error::Error, error::Error,
headless::utils::HeadlessPipelineProcessor, headless::utils::HeadlessPipelineProcessor,
io::{ io::{
apc::{AsyncProcedureCall, SchedulerAsyncProcedureCall},
pipeline::{PipelineContext, Processable}, pipeline::{PipelineContext, Processable},
source_client::HttpSourceClient, source_client::HttpSourceClient,
tile_pipelines::build_vector_tile_pipeline, tile_pipelines::build_vector_tile_pipeline,
tile_repository::{StoredLayer, TileRepository}, tile_repository::{StoredLayer, TileRepository},
tile_request_state::TileRequestState, transferables::{DefaultTransferables, Transferables},
TileRequest, TileRequest,
}, },
render::{ render::{
@ -35,7 +37,7 @@ use crate::{
RenderState, RenderState,
}, },
schedule::{Schedule, Stage}, schedule::{Schedule, Stage},
HttpClient, MapWindow, MapWindowConfig, Renderer, ScheduleMethod, Scheduler, Style, WindowSize, Environment, HttpClient, MapWindow, MapWindowConfig, Renderer, Scheduler, Style, WindowSize,
}; };
pub struct HeadlessMapWindowConfig { pub struct HeadlessMapWindowConfig {
@ -60,56 +62,57 @@ impl MapWindow for HeadlessMapWindow {
} }
} }
pub struct HeadlessMap<MWC, SM, HC> pub struct HeadlessEnvironment<
where S: Scheduler,
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient, HC: HttpClient,
{ T: Transferables,
pub map_schedule: HeadlessMapSchedule<MWC, SM, HC>, APC: AsyncProcedureCall<T, HC>,
pub window: MWC::MapWindow, > {
phantom_s: PhantomData<S>,
phantom_hc: PhantomData<HC>,
phantom_t: PhantomData<T>,
phantom_apc: PhantomData<APC>,
} }
impl<MWC, SM, HC> HeadlessMap<MWC, SM, HC> impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
where for HeadlessEnvironment<S, HC, T, APC>
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{ {
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 &mut self.map_schedule
} }
} }
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing. /// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
pub struct HeadlessMapSchedule<MWC, SM, HC> pub struct HeadlessMapSchedule<E: Environment> {
where map_window_config: E::MapWindowConfig,
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
map_window_config: MWC,
pub map_context: MapContext, pub map_context: MapContext,
schedule: Schedule, schedule: Schedule,
scheduler: Scheduler<SM>, scheduler: E::Scheduler,
http_client: HC, http_client: E::HttpClient,
tile_request_state: TileRequestState,
} }
impl<MWC, SM, HC> HeadlessMapSchedule<MWC, SM, HC> impl<E: Environment> HeadlessMapSchedule<E> {
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
pub fn new( pub fn new(
map_window_config: MWC, map_window_config: E::MapWindowConfig,
window_size: WindowSize, window_size: WindowSize,
renderer: Renderer, renderer: Renderer,
scheduler: Scheduler<SM>, scheduler: E::Scheduler,
http_client: HC, http_client: E::HttpClient,
style: Style, style: Style,
) -> Self { ) -> Self {
let view_state = ViewState::new( let view_state = ViewState::new(
@ -122,12 +125,12 @@ where
let tile_repository = TileRepository::new(); let tile_repository = TileRepository::new();
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
let mut graph = create_default_render_graph().unwrap(); let mut graph = create_default_render_graph().unwrap(); // TODO: remove unwrap
let draw_graph = graph.get_sub_graph_mut(draw_graph::NAME).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(draw_graph::node::COPY, CopySurfaceBufferNode::default());
draw_graph draw_graph
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY) .add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
.unwrap(); .unwrap(); // TODO: remove unwrap
register_default_render_stages(graph, &mut schedule); register_default_render_stages(graph, &mut schedule);
@ -147,7 +150,6 @@ where
schedule, schedule,
scheduler, scheduler,
http_client, http_client,
tile_request_state: Default::default(),
} }
} }
@ -160,10 +162,10 @@ where
pub fn schedule(&self) -> &Schedule { pub fn schedule(&self) -> &Schedule {
&self.schedule &self.schedule
} }
pub fn scheduler(&self) -> &Scheduler<SM> { pub fn scheduler(&self) -> &E::Scheduler {
&self.scheduler &self.scheduler
} }
pub fn http_client(&self) -> &HC { pub fn http_client(&self) -> &E::HttpClient {
&self.http_client &self.http_client
} }
@ -176,13 +178,13 @@ where
.filter_map(|layer| layer.source_layer.clone()) .filter_map(|layer| layer.source_layer.clone())
.collect(); .collect();
let http_source_client: HttpSourceClient<HC> = let http_source_client: HttpSourceClient<E::HttpClient> =
HttpSourceClient::new(self.http_client.clone()); HttpSourceClient::new(self.http_client.clone());
let data = http_source_client let data = http_source_client
.fetch(&coords) .fetch(&coords)
.await .await
.unwrap() .unwrap() // TODO: remove unwrap
.into_boxed_slice(); .into_boxed_slice();
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default()); let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
@ -193,15 +195,11 @@ where
layers: source_layers, layers: source_layers,
}; };
let request_id = self pipeline.process((request, data), &mut pipeline_context);
.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);
let mut processor = pipeline_context let mut processor = pipeline_context
.take_processor::<HeadlessPipelineProcessor>() .take_processor::<HeadlessPipelineProcessor>()
.unwrap(); .unwrap(); // TODO: remove unwrap
if let Eventually::Initialized(pool) = self.map_context.renderer.state.buffer_pool_mut() { if let Eventually::Initialized(pool) = self.map_context.renderer.state.buffer_pool_mut() {
pool.clear(); pool.clear();
@ -210,6 +208,9 @@ where
self.map_context.tile_repository.clear(); self.map_context.tile_repository.clear();
while let Some(layer) = processor.layers.pop() { while let Some(layer) = processor.layers.pop() {
self.map_context
.tile_repository
.create_tile(&layer.get_coords());
self.map_context self.map_context
.tile_repository .tile_repository
.put_tessellated_layer(layer); .put_tessellated_layer(layer);
@ -261,7 +262,7 @@ impl Node for CopySurfaceBufferNode {
std::num::NonZeroU32::new( std::num::NonZeroU32::new(
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32, buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
) )
.unwrap(), .unwrap(), // TODO: remove unwrap
), ),
rows_per_image: None, rows_per_image: None,
}, },
@ -340,9 +341,9 @@ pub mod utils {
) { ) {
self.layers.push(StoredLayer::TessellatedLayer { self.layers.push(StoredLayer::TessellatedLayer {
coords: *coords, coords: *coords,
layer_name: layer_data.name,
buffer, buffer,
feature_indices, 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 std::{collections::HashSet, fmt};
use serde::{Deserialize, Serialize};
use crate::coords::WorldTileCoords; use crate::coords::WorldTileCoords;
pub mod apc;
pub mod geometry_index; pub mod geometry_index;
pub mod pipeline; pub mod pipeline;
pub mod scheduler; pub mod scheduler;
@ -12,12 +15,12 @@ pub mod source_client;
pub mod static_tile_fetcher; pub mod static_tile_fetcher;
pub mod tile_pipelines; pub mod tile_pipelines;
pub mod tile_repository; pub mod tile_repository;
pub mod tile_request_state; pub mod transferables;
pub use geozero::mvt::tile::Layer as RawLayer; pub use geozero::mvt::tile::Layer as RawLayer;
/// A request for a tile at the given coordinates and in the given layers. /// A request for a tile at the given coordinates and in the given layers.
#[derive(Clone)] #[derive(Clone, Serialize, Deserialize)]
pub struct TileRequest { pub struct TileRequest {
pub coords: WorldTileCoords, pub coords: WorldTileCoords,
pub layers: HashSet<String>, pub layers: HashSet<String>,
@ -28,6 +31,3 @@ impl fmt::Debug for TileRequest {
write!(f, "TileRequest({}, {:?})", &self.coords, &self.layers) 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::{ use crate::{
coords::WorldTileCoords, coords::WorldTileCoords,
io::{geometry_index::IndexedGeometry, TileRequestID}, io::geometry_index::IndexedGeometry,
render::ShaderVertex, render::ShaderVertex,
tessellation::{IndexDataType, OverAlignedVertexBuffer}, tessellation::{IndexDataType, OverAlignedVertexBuffer},
}; };
/// Processes events which happen during the pipeline execution /// Processes events which happen during the pipeline execution
pub trait PipelineProcessor: Downcast { 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_unavailable(&mut self, _coords: &WorldTileCoords, _layer_name: &str) {}
fn layer_tesselation_finished( fn layer_tesselation_finished(
&mut self, &mut self,
@ -30,8 +30,6 @@ pub trait PipelineProcessor: Downcast {
} }
} }
impl_downcast!(PipelineProcessor);
/// Context which is available to each step within a [`DataPipeline`] /// Context which is available to each step within a [`DataPipeline`]
pub struct PipelineContext { pub struct PipelineContext {
processor: Box<dyn PipelineProcessor>, processor: Box<dyn PipelineProcessor>,

View File

@ -5,28 +5,8 @@ use std::future::Future;
use crate::error::Error; use crate::error::Error;
/// Async/await scheduler. /// 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. /// 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"))] #[cfg(not(feature = "no-thread-safe-futures"))]
fn schedule<T>( fn schedule<T>(
&self, &self,
@ -43,3 +23,14 @@ pub trait ScheduleMethod: 'static {
where where
T: Future<Output = ()> + 'static; 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(|| { .ok_or_else(|| {
Error::Network("Failed to load tile from within the binary".to_string()) 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::{ io::{
geometry_index::IndexProcessor, geometry_index::IndexProcessor,
pipeline::{DataPipeline, PipelineContext, PipelineEnd, Processable}, pipeline::{DataPipeline, PipelineContext, PipelineEnd, Processable},
TileRequest, TileRequestID, TileRequest,
}, },
tessellation::{zero_tessellator::ZeroTessellator, IndexDataType}, tessellation::{zero_tessellator::ZeroTessellator, IndexDataType},
}; };
@ -16,17 +16,17 @@ use crate::{
pub struct ParseTile; pub struct ParseTile;
impl Processable for ParseTile { impl Processable for ParseTile {
type Input = (TileRequest, TileRequestID, Box<[u8]>); type Input = (TileRequest, Box<[u8]>);
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile); type Output = (TileRequest, geozero::mvt::Tile);
// TODO (perf): Maybe force inline // TODO (perf): Maybe force inline
fn process( fn process(
&self, &self,
(tile_request, request_id, data): Self::Input, (tile_request, data): Self::Input,
_context: &mut PipelineContext, _context: &mut PipelineContext,
) -> Self::Output { ) -> Self::Output {
let tile = geozero::mvt::Tile::decode(data.as_ref()).expect("failed to load tile"); 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; pub struct IndexLayer;
impl Processable for IndexLayer { impl Processable for IndexLayer {
type Input = (TileRequest, TileRequestID, geozero::mvt::Tile); type Input = (TileRequest, geozero::mvt::Tile);
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile); type Output = (TileRequest, geozero::mvt::Tile);
// TODO (perf): Maybe force inline // TODO (perf): Maybe force inline
fn process( fn process(
&self, &self,
(tile_request, request_id, tile): Self::Input, (tile_request, tile): Self::Input,
context: &mut PipelineContext, context: &mut PipelineContext,
) -> Self::Output { ) -> Self::Output {
let index = IndexProcessor::new(); let index = IndexProcessor::new();
@ -48,7 +48,7 @@ impl Processable for IndexLayer {
context context
.processor_mut() .processor_mut()
.layer_indexing_finished(&tile_request.coords, index.get_geometries()); .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; pub struct TessellateLayer;
impl Processable for TessellateLayer { impl Processable for TessellateLayer {
type Input = (TileRequest, TileRequestID, geozero::mvt::Tile); type Input = (TileRequest, geozero::mvt::Tile);
type Output = (TileRequest, TileRequestID, geozero::mvt::Tile); type Output = (TileRequest, geozero::mvt::Tile);
// TODO (perf): Maybe force inline // TODO (perf): Maybe force inline
fn process( fn process(
&self, &self,
(tile_request, request_id, mut tile): Self::Input, (tile_request, mut tile): Self::Input,
context: &mut PipelineContext, context: &mut PipelineContext,
) -> Self::Output { ) -> Self::Output {
let coords = &tile_request.coords; let coords = &tile_request.coords;
@ -118,11 +118,9 @@ impl Processable for TessellateLayer {
tracing::info!("tile tessellated at {} finished", &tile_request.coords); tracing::info!("tile tessellated at {} finished", &tile_request.coords);
context context.processor_mut().tile_finished(&tile_request.coords);
.processor_mut()
.tile_finished(request_id, &tile_request.coords);
(tile_request, request_id, tile) (tile_request, tile)
} }
} }
@ -159,7 +157,6 @@ mod tests {
coords: (0, 0, ZoomLevel::default()).into(), coords: (0, 0, ZoomLevel::default()).into(),
layers: Default::default(), layers: Default::default(),
}, },
0,
Box::new([0]), Box::new([0]),
), ),
&mut context, &mut context,

View File

@ -18,10 +18,10 @@ pub enum StoredLayer {
}, },
TessellatedLayer { TessellatedLayer {
coords: WorldTileCoords, coords: WorldTileCoords,
layer_name: String,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>, buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
/// Holds for each feature the count of indices. /// Holds for each feature the count of indices.
feature_indices: Vec<u32>, feature_indices: Vec<u32>,
layer_data: tile::Layer,
}, },
} }
@ -36,20 +36,29 @@ impl StoredLayer {
pub fn layer_name(&self) -> &str { pub fn layer_name(&self) -> &str {
match self { match self {
StoredLayer::UnavailableLayer { layer_name, .. } => layer_name.as_str(), 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). /// Stores multiple [StoredLayers](StoredLayer).
pub struct StoredTile { pub struct StoredTile {
layers: Vec<StoredLayer>, layers: Vec<StoredLayer>,
status: TileStatus,
} }
impl StoredTile { impl StoredTile {
pub fn new(first_layer: StoredLayer) -> Self { pub fn new() -> Self {
Self { Self {
layers: vec![first_layer], layers: vec![],
status: TileStatus::Pending,
} }
} }
} }
@ -84,7 +93,7 @@ impl TileRepository {
{ {
match entry { match entry {
btree_map::Entry::Vacant(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) => { btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().layers.push(layer); entry.get_mut().layers.push(layer);
@ -105,6 +114,46 @@ impl TileRepository {
.map(|results| results.layers.iter()) .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 /// Removes all the cached tessellate layers that are not contained within the given
/// layers hashset. /// layers hashset.
pub fn retain_missing_layer_names( 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" //! maplibre = "0.0.2"
//! ``` //! ```
use std::{
borrow::{Borrow, BorrowMut},
cell::RefCell,
rc::Rc,
};
use crate::{ use crate::{
io::{ environment::Environment,
scheduler::{ScheduleMethod, Scheduler}, io::{scheduler::Scheduler, source_client::HttpClient},
source_client::HttpClient,
},
map_schedule::InteractiveMapSchedule, map_schedule::InteractiveMapSchedule,
render::{ render::{
settings::{RendererSettings, WgpuSettings}, settings::{RendererSettings, WgpuSettings},
@ -56,29 +60,24 @@ pub mod benchmarking;
// Internal modules // Internal modules
pub(crate) mod tessellation; pub(crate) mod tessellation;
pub mod environment;
pub use geozero::mvt::tile;
/// The [`Map`] defines the public interface of the map renderer. /// The [`Map`] defines the public interface of the map renderer.
// DO NOT IMPLEMENT INTERNALS ON THIS STRUCT. // DO NOT IMPLEMENT INTERNALS ON THIS STRUCT.
pub struct Map<MWC, SM, HC> pub struct Map<E: Environment> {
where // FIXME (wasm-executor): Avoid RefCell, change ownership model!
MWC: MapWindowConfig, map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
SM: ScheduleMethod, window: RefCell<Option<<E::MapWindowConfig as MapWindowConfig>::MapWindow>>,
HC: HttpClient,
{
map_schedule: InteractiveMapSchedule<MWC, SM, HC>,
window: MWC::MapWindow,
} }
impl<MWC, SM, HC> Map<MWC, SM, HC> impl<E: Environment> Map<E>
where where
MWC: MapWindowConfig, <E::MapWindowConfig as MapWindowConfig>::MapWindow: EventLoop<E>,
SM: ScheduleMethod,
HC: HttpClient,
{ {
/// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop. /// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
pub fn run(self) pub fn run(&self) {
where
MWC::MapWindow: EventLoop<MWC, SM, HC>,
{
self.run_with_optionally_max_frames(None); self.run_with_optionally_max_frames(None);
} }
@ -87,10 +86,7 @@ where
/// # Arguments /// # Arguments
/// ///
/// * `max_frames` - Maximum number of frames per second. /// * `max_frames` - Maximum number of frames per second.
pub fn run_with_max_frames(self, max_frames: u64) pub fn run_with_max_frames(&self, max_frames: u64) {
where
MWC::MapWindow: EventLoop<MWC, SM, HC>,
{
self.run_with_optionally_max_frames(Some(max_frames)); self.run_with_optionally_max_frames(Some(max_frames));
} }
@ -99,51 +95,42 @@ where
/// # Arguments /// # Arguments
/// ///
/// * `max_frames` - Optional maximum number of frames per second. /// * `max_frames` - Optional maximum number of frames per second.
pub fn run_with_optionally_max_frames(self, max_frames: Option<u64>) pub fn run_with_optionally_max_frames(&self, max_frames: Option<u64>) {
where self.window
MWC::MapWindow: EventLoop<MWC, SM, HC>, .borrow_mut()
{ .take()
self.window.run(self.map_schedule, max_frames); .unwrap() // FIXME (wasm-executor): Remove unwrap
.run(self.map_schedule.clone(), max_frames);
} }
pub fn map_schedule(&self) -> &InteractiveMapSchedule<MWC, SM, HC> { pub fn map_schedule(&self) -> Rc<RefCell<InteractiveMapSchedule<E>>> {
&self.map_schedule 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 &mut self.map_schedule
} }*/
} }
/// Stores the map configuration before the map's state has been fully initialized. /// Stores the map configuration before the map's state has been fully initialized.
pub struct UninitializedMap<MWC, SM, HC> pub struct UninitializedMap<E: Environment> {
where scheduler: E::Scheduler,
MWC: MapWindowConfig, apc: E::AsyncProcedureCall,
SM: ScheduleMethod, http_client: E::HttpClient,
HC: HttpClient,
{
scheduler: Scheduler<SM>,
http_client: HC,
style: Style, style: Style,
wgpu_settings: WgpuSettings, wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings, 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 where
MWC: MapWindowConfig, <E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
SM: ScheduleMethod,
HC: HttpClient,
{ {
/// Initializes the whole rendering pipeline for the given configuration. /// Initializes the whole rendering pipeline for the given configuration.
/// Returns the initialized map, ready to be run. /// Returns the initialized map, ready to be run.
pub async fn initialize(self) -> Map<MWC, SM, HC> pub async fn initialize(self) -> Map<E> {
where
MWC: MapWindowConfig,
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
let window = self.map_window_config.create(); let window = self.map_window_config.create();
let window_size = window.size(); let window_size = window.size();
@ -158,22 +145,25 @@ where
.await .await
.ok(); .ok();
Map { Map {
map_schedule: InteractiveMapSchedule::new( map_schedule: Rc::new(RefCell::new(InteractiveMapSchedule::new(
self.map_window_config, self.map_window_config,
window_size, window_size,
renderer, renderer,
self.scheduler, self.scheduler,
self.apc,
self.http_client, self.http_client,
self.style, self.style,
self.wgpu_settings, self.wgpu_settings,
self.renderer_settings, self.renderer_settings,
), ))),
window, window: RefCell::new(Some(window)),
} }
} }
}
#[cfg(feature = "headless")] #[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 = self.map_window_config.create();
let window_size = window.size(); let window_size = window.size();
@ -198,30 +188,22 @@ where
} }
} }
pub struct MapBuilder<MWC, SM, HC> pub struct MapBuilder<E: Environment> {
where scheduler: Option<E::Scheduler>,
SM: ScheduleMethod, apc: Option<E::AsyncProcedureCall>,
{ http_client: Option<E::HttpClient>,
schedule_method: Option<SM>,
scheduler: Option<Scheduler<SM>>,
http_client: Option<HC>,
style: Option<Style>, style: Option<Style>,
map_window_config: Option<MWC>, map_window_config: Option<E::MapWindowConfig>,
wgpu_settings: Option<WgpuSettings>, wgpu_settings: Option<WgpuSettings>,
renderer_settings: Option<RendererSettings>, renderer_settings: Option<RendererSettings>,
} }
impl<MWC, SM, HC> MapBuilder<MWC, SM, HC> impl<E: Environment> MapBuilder<E> {
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
schedule_method: None,
scheduler: None, scheduler: None,
apc: None,
http_client: None, http_client: None,
style: None, style: None,
map_window_config: 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.map_window_config = Some(map_window_config);
self self
} }
@ -245,40 +227,36 @@ where
self self
} }
pub fn with_schedule_method(mut self, schedule_method: SM) -> Self { pub fn with_scheduler(mut self, scheduler: E::Scheduler) -> 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 {
self.scheduler = Some(scheduler); self.scheduler = Some(scheduler);
self 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 { pub fn with_style(mut self, style: Style) -> Self {
self.style = Some(style); self.style = Some(style);
self self
} }
/// Builds the UninitializedMap with the given configuration. /// Builds the UninitializedMap with the given configuration.
pub fn build(self) -> UninitializedMap<MWC, SM, HC> { pub fn build(self) -> UninitializedMap<E> {
let scheduler = self
.scheduler
.unwrap_or_else(|| Scheduler::new(self.schedule_method.unwrap()));
let style = self.style.unwrap_or_default();
UninitializedMap { UninitializedMap {
scheduler, scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
http_client: self.http_client.unwrap(), apc: self.apc.unwrap(), // TODO: Remove unwrap
style, http_client: self.http_client.unwrap(), // TODO: Remove unwrap
style: self.style.unwrap_or_default(),
wgpu_settings: self.wgpu_settings.unwrap_or_default(), wgpu_settings: self.wgpu_settings.unwrap_or_default(),
renderer_settings: self.renderer_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::{ use crate::{
context::{MapContext, ViewState}, context::{MapContext, ViewState},
@ -13,41 +13,32 @@ use crate::{
schedule::{Schedule, Stage}, schedule::{Schedule, Stage},
stages::register_stages, stages::register_stages,
style::Style, style::Style,
HeadedMapWindow, MapWindowConfig, Renderer, RendererSettings, ScheduleMethod, WgpuSettings, Environment, HeadedMapWindow, MapWindowConfig, Renderer, RendererSettings, WgpuSettings,
WindowSize, WindowSize,
}; };
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing. /// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
pub struct InteractiveMapSchedule<MWC, SM, HC> pub struct InteractiveMapSchedule<E: Environment> {
where map_window_config: E::MapWindowConfig,
MWC: MapWindowConfig,
SM: ScheduleMethod, // FIXME (wasm-executor): avoid RefCell, change ownership model
HC: HttpClient, pub apc: Rc<RefCell<E::AsyncProcedureCall>>,
{
map_window_config: MWC,
map_context: EventuallyMapContext, map_context: EventuallyMapContext,
schedule: Schedule, schedule: Schedule,
phantom_sm: PhantomData<SM>,
phantom_hc: PhantomData<HC>,
suspended: bool, suspended: bool,
} }
impl<MWC, SM, HC> InteractiveMapSchedule<MWC, SM, HC> impl<E: Environment> InteractiveMapSchedule<E> {
where
MWC: MapWindowConfig,
SM: ScheduleMethod,
HC: HttpClient,
{
pub fn new( pub fn new(
map_window_config: MWC, map_window_config: E::MapWindowConfig,
window_size: WindowSize, window_size: WindowSize,
renderer: Option<Renderer>, renderer: Option<Renderer>,
scheduler: Scheduler<SM>, scheduler: E::Scheduler, // TODO: unused
http_client: HC, apc: E::AsyncProcedureCall,
http_client: E::HttpClient,
style: Style, style: Style,
wgpu_settings: WgpuSettings, wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings, renderer_settings: RendererSettings,
@ -63,13 +54,17 @@ where
let tile_repository = TileRepository::new(); let tile_repository = TileRepository::new();
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
let http_source_client: HttpSourceClient<HC> = HttpSourceClient::new(http_client); let apc = Rc::new(RefCell::new(apc));
register_stages(&mut schedule, http_source_client, Box::new(scheduler));
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); register_default_render_stages(graph, &mut schedule);
Self { Self {
apc,
map_window_config, map_window_config,
map_context: match renderer { map_context: match renderer {
None => EventuallyMapContext::Premature(PrematureMapContext { None => EventuallyMapContext::Premature(PrematureMapContext {
@ -87,8 +82,6 @@ where
}), }),
}, },
schedule, schedule,
phantom_sm: Default::default(),
phantom_hc: Default::default(),
suspended: false, 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 { pub fn is_initialized(&self) -> bool {
match &self.map_context { match &self.map_context {
EventuallyMapContext::Full(_) => true, 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 {
where match &mut self.map_context {
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow, EventuallyMapContext::Full(MapContext { view_state, .. }) => view_state,
{ EventuallyMapContext::Premature(PrematureMapContext { view_state, .. }) => view_state,
_ => panic!("should not happen"),
}
}
pub fn apc(&self) -> &Rc<RefCell<E::AsyncProcedureCall>> {
&self.apc
}
}
impl<E: Environment> InteractiveMapSchedule<E>
where
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
pub fn suspend(&mut self) {
self.suspended = true;
}
pub fn resume(&mut self, window: &<E::MapWindowConfig as MapWindowConfig>::MapWindow) {
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
let renderer = &mut map_context.renderer;
renderer.state.recreate_surface(window, &renderer.instance);
self.suspended = false;
}
}
pub async fn late_init(&mut self) -> bool {
match &self.map_context { match &self.map_context {
EventuallyMapContext::Full(_) => false, EventuallyMapContext::Full(_) => false,
EventuallyMapContext::Premature(PrematureMapContext { EventuallyMapContext::Premature(PrematureMapContext {
@ -155,21 +157,13 @@ where
let renderer = let renderer =
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone()) Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
.await .await
.unwrap(); .unwrap(); // TODO: Remove unwrap
self.map_context.make_full(renderer); self.map_context.make_full(renderer);
true true
} }
EventuallyMapContext::_Uninitialized => false, 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 { pub struct PrematureMapContext {

View File

@ -36,9 +36,14 @@ pub mod http_client {
} }
/// Scheduler for non-web targets. /// Scheduler for non-web targets.
pub mod schedule_method { pub mod scheduler {
#[cfg(not(target_arch = "wasm32"))] #[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"))] #[cfg(not(target_arch = "wasm32"))]

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ impl Stage for UploadStage {
.position .position
.to_homogeneous() .to_homogeneous()
.cast::<f32>() .cast::<f32>()
.unwrap() .unwrap() // TODO: Remove unwrap
.into(), .into(),
))]), ))]),
); );
@ -168,7 +168,7 @@ impl UploadStage {
}) })
{ {
for style_layer in &style.layers { 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 if let Some(message) = available_layers
.iter() .iter()
@ -187,7 +187,6 @@ impl UploadStage {
StoredLayer::TessellatedLayer { StoredLayer::TessellatedLayer {
coords, coords,
feature_indices, feature_indices,
layer_data,
buffer, buffer,
.. ..
} => { } => {
@ -197,17 +196,16 @@ impl UploadStage {
); );
let guard = allocate_feature_metadata.enter(); let guard = allocate_feature_metadata.enter();
let feature_metadata = layer_data let feature_metadata =
.features (0..feature_indices.len()) // FIXME: Iterate over actual featrues
.iter() .enumerate()
.enumerate() .flat_map(|(i, _feature)| {
.flat_map(|(i, _feature)| { iter::repeat(ShaderFeatureStyle {
iter::repeat(ShaderFeatureStyle { color: color.unwrap(),
color: color.unwrap(), })
.take(feature_indices[i] as usize)
}) })
.take(feature_indices[i] as usize) .collect::<Vec<_>>();
})
.collect::<Vec<_>>();
drop(guard); drop(guard);
tracing::trace!("Allocating geometry at {}", &coords); tracing::trace!("Allocating geometry at {}", &coords);

View File

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

View File

@ -250,7 +250,7 @@ impl Schedule {
for label in &self.stage_order { for label in &self.stage_order {
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let _stage_span = tracing::info_span!("stage", name = ?label).entered(); 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); 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 //! [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 geozero::{mvt::tile, GeozeroDatasource};
use request_stage::RequestStage; use request_stage::RequestStage;
@ -9,78 +14,61 @@ use crate::{
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel}, coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
error::Error, error::Error,
io::{ io::{
apc::{AsyncProcedureCall, Context, Message},
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex}, geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
pipeline::{PipelineContext, PipelineProcessor, Processable}, pipeline::{PipelineContext, PipelineProcessor, Processable},
source_client::HttpSourceClient, source_client::HttpSourceClient,
tile_pipelines::build_vector_tile_pipeline, tile_pipelines::build_vector_tile_pipeline,
tile_request_state::TileRequestState, transferables::{
TileRequest, TileRequestID, DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
DefaultUnavailableLayer, TessellatedLayer, TileTessellated, Transferables,
UnavailableLayer,
},
TileRequest,
}, },
render::ShaderVertex, render::ShaderVertex,
schedule::Schedule, schedule::Schedule,
stages::{ stages::populate_tile_store_stage::PopulateTileStore,
message::{
LayerTessellateMessage, MessageReceiver, MessageSender, TessellateMessage,
TileTessellateMessage,
},
populate_tile_store_stage::PopulateTileStore,
},
tessellation::{IndexDataType, OverAlignedVertexBuffer}, tessellation::{IndexDataType, OverAlignedVertexBuffer},
HttpClient, ScheduleMethod, Scheduler, Environment, HttpClient, Scheduler,
}; };
mod message;
mod populate_tile_store_stage; mod populate_tile_store_stage;
mod request_stage; mod request_stage;
/// Register stages required for requesting and preparing new tiles. /// Register stages required for requesting and preparing new tiles.
pub fn register_stages<HC: HttpClient, SM: ScheduleMethod>( pub fn register_stages<E: Environment>(
schedule: &mut Schedule, schedule: &mut Schedule,
http_source_client: HttpSourceClient<HC>, http_source_client: HttpSourceClient<E::HttpClient>,
scheduler: Box<Scheduler<SM>>, 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( schedule.add_stage(
"request", "request",
RequestStage::new(shared_thread_state.clone(), http_source_client, *scheduler), RequestStage::<E>::new(http_source_client, apc.clone()),
);
schedule.add_stage(
"populate_tile_store",
PopulateTileStore::new(shared_thread_state, message_receiver),
); );
schedule.add_stage("populate_tile_store", PopulateTileStore::<E>::new(apc));
} }
pub struct HeadedPipelineProcessor { pub struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<T, HC>> {
state: SharedThreadState, context: C,
phantom_t: PhantomData<T>,
phantom_hc: PhantomData<HC>,
} }
impl PipelineProcessor for HeadedPipelineProcessor { impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
fn tile_finished(&mut self, request_id: TileRequestID, coords: &WorldTileCoords) { for HeadedPipelineProcessor<T, HC, C>
self.state {
.message_sender fn tile_finished(&mut self, coords: &WorldTileCoords) {
.send(TessellateMessage::Tile(TileTessellateMessage { self.context
request_id, .send(Message::TileTessellated(T::TileTessellated::new(*coords)))
coords: *coords,
}))
.unwrap();
} }
fn layer_unavailable(&mut self, coords: &WorldTileCoords, layer_name: &str) { fn layer_unavailable(&mut self, coords: &WorldTileCoords, layer_name: &str) {
self.state self.context
.message_sender .send(Message::UnavailableLayer(T::UnavailableLayer::new(
.send(TessellateMessage::Layer( *coords,
LayerTessellateMessage::UnavailableLayer { layer_name.to_owned(),
coords: *coords, )))
layer_name: layer_name.to_owned(),
},
))
.unwrap();
} }
fn layer_tesselation_finished( fn layer_tesselation_finished(
@ -90,17 +78,13 @@ impl PipelineProcessor for HeadedPipelineProcessor {
feature_indices: Vec<u32>, feature_indices: Vec<u32>,
layer_data: tile::Layer, layer_data: tile::Layer,
) { ) {
self.state self.context
.message_sender .send(Message::TessellatedLayer(T::TessellatedLayer::new(
.send(TessellateMessage::Layer( *coords,
LayerTessellateMessage::TessellatedLayer { buffer,
coords: *coords, feature_indices,
buffer, layer_data,
feature_indices, )))
layer_data,
},
))
.unwrap();
} }
fn layer_indexing_finished( fn layer_indexing_finished(
@ -108,80 +92,32 @@ impl PipelineProcessor for HeadedPipelineProcessor {
coords: &WorldTileCoords, coords: &WorldTileCoords,
geometries: Vec<IndexedGeometry<f64>>, 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 }) geometry_index.index_tile(coords, TileIndex::Linear { list: geometries })
} }*/
} }
} }
/// Stores and provides access to the thread safe data shared between the schedulers. // FIXME (wasm-executor): Readd
#[derive(Clone)] /*pub fn query_point(
pub struct SharedThreadState { &self,
pub tile_request_state: Arc<Mutex<TileRequestState>>, world_coords: &WorldCoords,
pub message_sender: mpsc::Sender<TessellateMessage>, z: ZoomLevel,
pub geometry_index: Arc<Mutex<GeometryIndex>>, zoom: Zoom,
} ) -> Option<Vec<IndexedGeometry<f64>>> {
if let Ok(geometry_index) = self.geometry_index.lock() {
impl SharedThreadState { geometry_index
fn get_tile_request(&self, request_id: TileRequestID) -> Option<TileRequest> { .query_point(world_coords, z, zoom)
self.tile_request_state .map(|geometries| {
.lock() geometries
.ok() .iter()
.and_then(|tile_request_state| tile_request_state.get_tile_request(request_id).cloned()) .cloned()
.cloned()
.collect::<Vec<IndexedGeometry<f64>>>()
})
} else {
unimplemented!()
} }
}*/
#[tracing::instrument(skip_all)] //}
pub fn process_tile(&self, request_id: TileRequestID, data: Box<[u8]>) -> Result<(), Error> {
if let Some(tile_request) = self.get_tile_request(request_id) {
let mut pipeline_context = PipelineContext::new(HeadedPipelineProcessor {
state: self.clone(),
});
let pipeline = build_vector_tile_pipeline();
pipeline.process((tile_request, request_id, data), &mut pipeline_context);
}
Ok(())
}
pub fn tile_unavailable(
&self,
coords: &WorldTileCoords,
request_id: TileRequestID,
) -> Result<(), Error> {
if let Some(tile_request) = self.get_tile_request(request_id) {
for to_load in &tile_request.layers {
tracing::warn!("layer {} at {} unavailable", to_load, coords);
self.message_sender.send(TessellateMessage::Layer(
LayerTessellateMessage::UnavailableLayer {
coords: tile_request.coords,
layer_name: to_load.to_string(),
},
))?;
}
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub fn query_point(
&self,
world_coords: &WorldCoords,
z: ZoomLevel,
zoom: Zoom,
) -> Option<Vec<IndexedGeometry<f64>>> {
if let Ok(geometry_index) = self.geometry_index.lock() {
geometry_index
.query_point(world_coords, z, zoom)
.map(|geometries| {
geometries
.iter()
.cloned()
.cloned()
.collect::<Vec<IndexedGeometry<f64>>>()
})
} else {
unimplemented!()
}
}
}

View File

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

View File

@ -1,56 +1,53 @@
//! Requests tiles which are currently in view //! 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::{ use crate::{
context::MapContext, context::MapContext,
coords::{ViewRegion, WorldTileCoords}, coords::{ViewRegion, WorldTileCoords, ZoomLevel},
error::Error, error::Error,
io::{ io::{
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
pipeline::{PipelineContext, Processable},
source_client::{HttpSourceClient, SourceClient}, source_client::{HttpSourceClient, SourceClient},
tile_pipelines::build_vector_tile_pipeline,
tile_repository::TileRepository, tile_repository::TileRepository,
transferables::{Transferables, UnavailableLayer},
TileRequest, TileRequest,
}, },
schedule::Stage, schedule::Stage,
stages::SharedThreadState, stages::HeadedPipelineProcessor,
HttpClient, ScheduleMethod, Scheduler, Style, Environment, HttpClient, Scheduler, Style,
}; };
pub struct RequestStage<SM, HC> pub struct RequestStage<E: Environment> {
where apc: Rc<RefCell<E::AsyncProcedureCall>>,
SM: ScheduleMethod, http_source_client: HttpSourceClient<E::HttpClient>,
HC: HttpClient,
{
shared_thread_state: SharedThreadState,
scheduler: Scheduler<SM>,
http_source_client: HttpSourceClient<HC>,
try_failed: bool,
} }
impl<SM, HC> RequestStage<SM, HC> impl<E: Environment> RequestStage<E> {
where
SM: ScheduleMethod,
HC: HttpClient,
{
pub fn new( pub fn new(
shared_thread_state: SharedThreadState, http_source_client: HttpSourceClient<E::HttpClient>,
http_source_client: HttpSourceClient<HC>, apc: Rc<RefCell<E::AsyncProcedureCall>>,
scheduler: Scheduler<SM>,
) -> Self { ) -> Self {
Self { Self {
shared_thread_state, apc,
scheduler,
http_source_client, http_source_client,
try_failed: false,
} }
} }
} }
impl<SM, HC> Stage for RequestStage<SM, HC> impl<E: Environment> Stage for RequestStage<E> {
where
SM: ScheduleMethod,
HC: HttpClient,
{
fn run( fn run(
&mut self, &mut self,
MapContext { MapContext {
@ -62,11 +59,10 @@ where
) { ) {
let view_region = view_state.create_view_region(); let view_region = view_state.create_view_region();
if view_state.camera.did_change(0.05) || view_state.zoom.did_change(0.05) || 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 { if let Some(view_region) = &view_region {
// FIXME: We also need to request tiles from layers above if we are over the maximum zoom level // FIXME: We also need to request tiles from layers above if we are over the maximum zoom level
self.try_failed = self.request_tiles_in_view(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> pub fn schedule<E: Environment, C: Context<E::Transferables, E::HttpClient>>(
where input: Input,
SM: ScheduleMethod, context: C,
HC: HttpClient, ) -> 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. /// Request tiles which are currently in view.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn request_tiles_in_view( fn request_tiles_in_view(
&self, &self,
tile_repository: &TileRepository, tile_repository: &mut TileRepository,
style: &Style, style: &Style,
view_region: &ViewRegion, view_region: &ViewRegion,
) -> bool { ) {
let mut try_failed = false;
let source_layers: HashSet<String> = style let source_layers: HashSet<String> = style
.layers .layers
.iter() .iter()
@ -98,66 +132,41 @@ where
for coords in view_region.iter() { for coords in view_region.iter() {
if coords.build_quad_key().is_some() { if coords.build_quad_key().is_some() {
// TODO: Make tesselation depend on style? // TODO: Make tesselation depend on style?
try_failed = self self.request_tile(tile_repository, &coords, &source_layers)
.try_request_tile(tile_repository, &coords, &source_layers) .unwrap(); // TODO: Remove unwrap
.unwrap();
} }
} }
try_failed
} }
fn try_request_tile( fn request_tile(
&self, &self,
tile_repository: &TileRepository, tile_repository: &mut TileRepository,
coords: &WorldTileCoords, coords: &WorldTileCoords,
layers: &HashSet<String>, layers: &HashSet<String>,
) -> Result<bool, Error> { ) -> Result<(), Error> {
if !tile_repository.is_layers_missing(coords, layers) { /* if !tile_repository.is_layers_missing(coords, layers) {
return Ok(false); return Ok(false);
}*/
if tile_repository.needs_fetching(&coords) {
tile_repository.create_tile(coords);
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,
>,
);
} }
if let Ok(mut tile_request_state) = self.shared_thread_state.tile_request_state.try_lock() { Ok(())
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;
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();
}
Ok(false)
} else {
Ok(true)
}
} }
} }

View File

@ -56,6 +56,21 @@ impl<V, I> OverAlignedVertexBuffer<V, I> {
usable_indices: 0, 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> { 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), &StrokeOptions::tolerance(DEFAULT_TOLERANCE),
&mut BuffersBuilder::new(&mut self.buffer, VertexConstructor {}), &mut BuffersBuilder::new(&mut self.buffer, VertexConstructor {}),
) )
.unwrap(); .unwrap(); // TODO: Remove unwrap
} }
fn end(&mut self, close: bool) { 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), &FillOptions::tolerance(DEFAULT_TOLERANCE).with_fill_rule(FillRule::NonZero),
&mut BuffersBuilder::new(&mut self.buffer, VertexConstructor {}), &mut BuffersBuilder::new(&mut self.buffer, VertexConstructor {}),
) )
.unwrap(); .unwrap(); // TODO: Remove unwrap
} }
} }

View File

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

View File

@ -1,7 +1,2 @@
[toolchain] [toolchain]
channel = "1.62" 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 = "0.2.81"
wasm-bindgen-futures = "0.4.31" wasm-bindgen-futures = "0.4.31"
console_log = { version = "0.2.0", features = ["color"] } 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] [dev-dependencies]
wasm-bindgen-test = "0.3.31" wasm-bindgen-test = "0.3.31"

View File

@ -12,11 +12,14 @@
"maplibre-rs": "file:../lib" "maplibre-rs": "file:../lib"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.7.20",
"@types/webpack": "^5.28.0",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^10.2.4",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"typescript": "^4.5.4", "ts-node": "^10.9.1",
"typescript": "^4.8.3",
"webpack": "^5.65.0", "webpack": "^5.65.0",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0" "webpack-dev-server": "^4.6.0"
@ -27,6 +30,7 @@
"version": "0.0.1", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"binaryen": "^110.0.0",
"spectorjs": "^0.9.27", "spectorjs": "^0.9.27",
"wasm-feature-detect": "^1.2.11" "wasm-feature-detect": "^1.2.11"
}, },
@ -36,10 +40,31 @@
"esbuild": "^0.14.38", "esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1", "esbuild-plugin-inline-worker": "^0.1.1",
"typescript": "^4.5.4", "typescript": "^4.5.4",
"wasm-pack": "^0.10.2",
"yargs": "^17.5.1" "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": { "node_modules/@discoveryjs/json-ext": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@ -148,6 +173,30 @@
"node": ">= 8" "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": { "node_modules/@types/body-parser": {
"version": "1.19.2", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -263,9 +312,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.7.15", "version": "18.7.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.20.tgz",
"integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==", "integrity": "sha512-adzY4vLLr5Uivmx8+zfSJ5fbdgKxX8UMtjtl+17n0B1q1Nz8JEmE151vefMdpD+1gyh+77weN4qEhej/O7budQ==",
"dev": true "dev": true
}, },
"node_modules/@types/qs": { "node_modules/@types/qs": {
@ -314,6 +363,17 @@
"@types/node": "*" "@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": { "node_modules/@types/ws": {
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@ -551,6 +611,15 @@
"acorn": "^8" "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": { "node_modules/ajv": {
"version": "8.11.0", "version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
@ -645,6 +714,12 @@
"node": ">= 8" "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": { "node_modules/array-flatten": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
@ -1088,6 +1163,12 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true "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": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -1185,6 +1266,15 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true "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": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -2424,6 +2514,12 @@
"node": ">=10" "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": { "node_modules/maplibre-rs": {
"resolved": "../lib", "resolved": "../lib",
"link": true "link": true
@ -3768,6 +3864,49 @@
"webpack": "^5.0.0" "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": { "node_modules/tslib": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@ -3788,9 +3927,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.8.2", "version": "4.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
"integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@ -3874,6 +4013,12 @@
"uuid": "dist/bin/uuid" "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": { "node_modules/vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "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", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true "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": { "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": { "@discoveryjs/json-ext": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@ -4323,6 +4498,30 @@
"fastq": "^1.6.0" "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": { "@types/body-parser": {
"version": "1.19.2", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz",
@ -4438,9 +4637,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "18.7.15", "version": "18.7.20",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.15.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.20.tgz",
"integrity": "sha512-XnjpaI8Bgc3eBag2Aw4t2Uj/49lLBSStHWfqKvIuXD7FIrZyMLWp8KuAFHAqxMZYTF9l08N1ctUn9YNybZJVmQ==", "integrity": "sha512-adzY4vLLr5Uivmx8+zfSJ5fbdgKxX8UMtjtl+17n0B1q1Nz8JEmE151vefMdpD+1gyh+77weN4qEhej/O7budQ==",
"dev": true "dev": true
}, },
"@types/qs": { "@types/qs": {
@ -4489,6 +4688,17 @@
"@types/node": "*" "@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": { "@types/ws": {
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@ -4702,6 +4912,12 @@
"dev": true, "dev": true,
"requires": {} "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": { "ajv": {
"version": "8.11.0", "version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
@ -4763,6 +4979,12 @@
"picomatch": "^2.0.4" "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": { "array-flatten": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
@ -5095,6 +5317,12 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true "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": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -5167,6 +5395,12 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true "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": { "dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -6097,17 +6331,23 @@
"yallist": "^4.0.0" "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": { "maplibre-rs": {
"version": "file:../lib", "version": "file:../lib",
"requires": { "requires": {
"@chialab/esbuild-plugin-meta-url": "^0.15.28", "@chialab/esbuild-plugin-meta-url": "^0.15.28",
"binaryen": "^110.0.0",
"chokidar": "^3.5.3", "chokidar": "^3.5.3",
"esbuild": "^0.14.38", "esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1", "esbuild-plugin-inline-worker": "^0.1.1",
"spectorjs": "^0.9.27", "spectorjs": "^0.9.27",
"typescript": "^4.5.4", "typescript": "^4.5.4",
"wasm-feature-detect": "^1.2.11", "wasm-feature-detect": "^1.2.11",
"wasm-pack": "^0.10.2",
"yargs": "^17.5.1" "yargs": "^17.5.1"
} }
}, },
@ -7102,6 +7342,27 @@
"semver": "^7.3.4" "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": { "tslib": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@ -7119,9 +7380,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "4.8.2", "version": "4.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
"integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==", "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
"dev": true "dev": true
}, },
"unpipe": { "unpipe": {
@ -7173,6 +7434,12 @@
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true "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": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "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", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true "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" "maplibre-rs": "file:../lib"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.7.20",
"@types/webpack": "^5.28.0",
"copy-webpack-plugin": "^10.2.4", "copy-webpack-plugin": "^10.2.4",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"typescript": "^4.5.4", "ts-node": "^10.9.1",
"typescript": "^4.8.3",
"webpack": "^5.65.0", "webpack": "^5.65.0",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.6.0" "webpack-dev-server": "^4.6.0"

View File

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

View File

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

4
web/lib/.gitignore vendored
View File

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

View File

@ -1 +1,2 @@
declare const WEBGL: boolean 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 metaUrlPlugin from '@chialab/esbuild-plugin-meta-url';
import inlineWorker from 'esbuild-plugin-inline-worker'; import inlineWorker from 'esbuild-plugin-inline-worker';
import yargs from "yargs"; import yargs from "yargs";
import process from "process";
import chokidar from "chokidar"; import chokidar from "chokidar";
import {spawnSync} from "child_process" import {spawnSync} from "child_process"
import {unlink} from "fs";
import {dirname} from "path"; import {dirname} from "path";
import {fileURLToPath} from "url"; import {fileURLToPath} from "url";
let argv = yargs(process.argv.slice(2)) let argv = yargs(process.argv.slice(2))
.option('watch', { .option('watch', {
alias: 'w',
type: 'boolean', type: 'boolean',
description: 'Enable watching' description: 'Enable watching'
}) })
.option('release', {
type: 'boolean',
description: 'Release mode'
})
.option('webgl', { .option('webgl', {
alias: 'g',
type: 'boolean', type: 'boolean',
description: 'Enable webgl' description: 'Enable webgl'
}) })
.option('multithreaded', {
type: 'boolean',
description: 'Enable multithreaded support'
})
.option('esm', { .option('esm', {
alias: 'e',
type: 'boolean', type: 'boolean',
description: 'Enable esm' description: 'Enable esm'
}) })
.option('cjs', { .option('cjs', {
alias: 'c',
type: 'boolean', type: 'boolean',
description: 'Enable cjs' description: 'Enable cjs'
}) })
.option('iife', { .option('iife', {
alias: 'i',
type: 'boolean', type: 'boolean',
description: 'Enable iife' description: 'Enable iife'
}) })
@ -39,6 +42,8 @@ let argv = yargs(process.argv.slice(2))
let esm = argv.esm; let esm = argv.esm;
let iife = argv.iife; let iife = argv.iife;
let cjs = argv.cjs; let cjs = argv.cjs;
let release = argv.release;
let multithreaded = argv.multithreaded;
if (!esm && !iife && !cjs) { if (!esm && !iife && !cjs) {
console.warn("Enabling ESM bundling as no other bundle is enabled.") console.warn("Enabling ESM bundling as no other bundle is enabled.")
@ -51,19 +56,29 @@ if (webgl) {
console.log("WebGL support enabled.") console.log("WebGL support enabled.")
} }
let baseSettings = { if (multithreaded) {
entryPoints: ['src/index.ts'], console.log("multithreaded support enabled.")
bundle: true, }
let baseConfig = {
platform: "browser", platform: "browser",
bundle: true,
assetNames: "assets/[name]", assetNames: "assets/[name]",
define: {"WEBGL": `${webgl}`}, define: {
WEBGL: `${webgl}`,
MULTITHREADED: `${multithreaded}`
},
}
let config = {
...baseConfig,
entryPoints:['src/index.ts'],
incremental: argv.watch, incremental: argv.watch,
plugins: [ plugins: [
inlineWorker({ inlineWorker({
format: "cjs", platform: "browser", ...baseConfig,
format: "cjs",
target: 'es2022', target: 'es2022',
bundle: true,
assetNames: "assets/[name]",
}), }),
metaUrlPlugin() metaUrlPlugin()
], ],
@ -98,8 +113,8 @@ const emitTypeScript = () => {
"--", "--",
"-m", "es2022", "-m", "es2022",
"-outDir", outDirectory, "-outDir", outDirectory,
"--declaration",
"--emitDeclarationOnly" "--emitDeclarationOnly"
], { ], {
cwd: '.', cwd: '.',
stdio: 'inherit', stdio: 'inherit',
@ -107,36 +122,74 @@ const emitTypeScript = () => {
if (child.status !== 0) { if (child.status !== 0) {
console.error("Failed to execute tsc") console.error("Failed to execute tsc")
process.exit(1)
} }
} }
// TODO: Do not continue if one step fails
const wasmPack = () => { 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", // language=toml
"wasm-pack","--", 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", "build",
"--out-name", "index", "-p", "web", "--lib",
"--out-dir", outDirectory, "--target", "wasm32-unknown-unknown",
getWebDirectory(), "--profile", profile,
"--target", "web", "--features", `${webgl ? "web-webgl," : ""}`,
"--", ...(multithreaded ? ["-Z", "build-std=std,panic_abort"] : []),
"--features", `${webgl ? "web-webgl" : ""}`,
"-Z", "build-std=std,panic_abort"
], { ], {
cwd: '.', cwd: '.',
stdio: 'inherit', stdio: 'inherit',
}); });
if (child.status !== 0) { if (cargo.status !== 0) {
console.error("Failed to execute wasm-pack") 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) => { const watchResult = async (result) => {
@ -149,17 +202,22 @@ const watchResult = async (result) => {
}); });
const update = async (path) => { const update = async (path) => {
console.log(`Updating: ${path}`) try {
if (path.endsWith(".rs")) { console.log(`Updating: ${path}`)
console.log("Rebuilding Rust...") if (path.endsWith(".rs")) {
wasmPack(); console.log("Rebuilding Rust...")
wasmPack();
}
console.log("Rebuilding...")
await result.rebuild();
console.log("Emitting TypeScript types...")
emitTypeScript();
} catch (e) {
console.error("Error while updating:")
console.error(e)
} }
console.log("Rebuilding...")
await result.rebuild();
console.log("Emitting TypeScript types...")
emitTypeScript();
} }
console.log("Watching...") console.log("Watching...")
@ -171,7 +229,7 @@ const watchResult = async (result) => {
} }
const esbuild = async (name, globalName = undefined) => { 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) { if (argv.watch) {
console.log("Watching is enabled.") console.log("Watching is enabled.")
@ -180,7 +238,7 @@ const esbuild = async (name, globalName = undefined) => {
} }
const start = async () => { const start = async () => {
console.log("Running wasm-pack...") console.log("Creating WASM...")
wasmPack(); wasmPack();
if (esm) { if (esm) {

View File

@ -9,6 +9,7 @@
"version": "0.0.1", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"binaryen": "^110.0.0",
"spectorjs": "^0.9.27", "spectorjs": "^0.9.27",
"wasm-feature-detect": "^1.2.11" "wasm-feature-detect": "^1.2.11"
}, },
@ -18,7 +19,6 @@
"esbuild": "^0.14.38", "esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1", "esbuild-plugin-inline-worker": "^0.1.1",
"typescript": "^4.5.4", "typescript": "^4.5.4",
"wasm-pack": "^0.10.2",
"yargs": "^17.5.1" "yargs": "^17.5.1"
} }
}, },
@ -135,21 +135,6 @@
"node": ">= 8" "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": { "node_modules/binary-extensions": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@ -159,28 +144,13 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/binary-install": { "node_modules/binaryen": {
"version": "0.1.1", "version": "110.0.0",
"resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.1.1.tgz", "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-110.0.0.tgz",
"integrity": "sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ==", "integrity": "sha512-b1rQFvnjNIfuW0UYSBgwLIrKD9PaG0iEzWXyoWLBFp/HRdvgD+7LGUnxG+/yKz1+tyiTdRm/lFRxsmYXaULIUg==",
"dev": true, "bin": {
"dependencies": { "wasm-opt": "bin/wasm-opt",
"axios": "^0.21.1", "wasm2js": "bin/wasm2js"
"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/braces": { "node_modules/braces": {
@ -222,15 +192,6 @@
"fsevents": "~2.3.2" "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": { "node_modules/cliui": {
"version": "7.0.4", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@ -266,12 +227,6 @@
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true "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": { "node_modules/detect-libc": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@ -707,44 +662,6 @@
"node": ">=8" "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": { "node_modules/fsevents": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@ -768,26 +685,6 @@
"node": "6.* || 8.* || >= 10.*" "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": { "node_modules/glob-parent": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@ -800,22 +697,6 @@
"node": ">= 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": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "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" "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": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -952,15 +784,6 @@
"node": ">=0.10.0" "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": { "node_modules/p-limit": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@ -1006,15 +829,6 @@
"node": ">=8" "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": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@ -1060,21 +874,6 @@
"node": ">=0.10.0" "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": { "node_modules/semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -1115,23 +914,6 @@
"node": ">=8" "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": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w==" "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": { "node_modules/wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "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" "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": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@ -1207,12 +970,6 @@
"node": ">=10" "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": { "node_modules/yargs": {
"version": "17.5.1", "version": "17.5.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
@ -1319,47 +1076,16 @@
"picomatch": "^2.0.4" "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": { "binary-extensions": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true "dev": true
}, },
"binary-install": { "binaryen": {
"version": "0.1.1", "version": "110.0.0",
"resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.1.1.tgz", "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-110.0.0.tgz",
"integrity": "sha512-DqED0D/6LrS+BHDkKn34vhRqOGjy5gTMgvYZsGK2TpNbdPuz4h+MRlNgGv5QBRd7pWq/jylM4eKNCizgAq3kNQ==", "integrity": "sha512-b1rQFvnjNIfuW0UYSBgwLIrKD9PaG0iEzWXyoWLBFp/HRdvgD+7LGUnxG+/yKz1+tyiTdRm/lFRxsmYXaULIUg=="
"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"
}
}, },
"braces": { "braces": {
"version": "3.0.2", "version": "3.0.2",
@ -1386,12 +1112,6 @@
"readdirp": "~3.6.0" "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": { "cliui": {
"version": "7.0.4", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@ -1424,12 +1144,6 @@
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true "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": { "detect-libc": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
@ -1657,27 +1371,6 @@
"path-exists": "^4.0.0" "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": { "fsevents": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@ -1691,20 +1384,6 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true "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": { "glob-parent": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@ -1714,22 +1393,6 @@
"is-glob": "^4.0.1" "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": { "is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -1784,55 +1447,12 @@
"semver": "^6.0.0" "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": { "normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true "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": { "p-limit": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
@ -1863,12 +1483,6 @@
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true "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": { "picomatch": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@ -1899,15 +1513,6 @@
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true "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": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -1939,20 +1544,6 @@
"ansi-regex": "^5.0.1" "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": { "to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w==" "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": { "wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@ -1993,24 +1575,12 @@
"strip-ansi": "^6.0.0" "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": { "y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true "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": { "yargs": {
"version": "17.5.1", "version": "17.5.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",

View File

@ -16,16 +16,16 @@
"main": "dist/esbuild-cjs/main.js", "main": "dist/esbuild-cjs/main.js",
"types": "dist/ts/index.d.ts", "types": "dist/ts/index.d.ts",
"dependencies": { "dependencies": {
"binaryen": "^110.0.0",
"spectorjs": "^0.9.27", "spectorjs": "^0.9.27",
"wasm-feature-detect": "^1.2.11" "wasm-feature-detect": "^1.2.11"
}, },
"devDependencies": { "devDependencies": {
"chokidar": "^3.5.3",
"@chialab/esbuild-plugin-meta-url": "^0.15.28", "@chialab/esbuild-plugin-meta-url": "^0.15.28",
"chokidar": "^3.5.3",
"esbuild": "^0.14.38", "esbuild": "^0.14.38",
"esbuild-plugin-inline-worker": "^0.1.1", "esbuild-plugin-inline-worker": "^0.1.1",
"typescript": "^4.5.4", "typescript": "^4.5.4",
"wasm-pack": "^0.10.2",
"yargs": "^17.5.1" "yargs": "^17.5.1"
}, },
"repository": { "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 {Spector} from "spectorjs"
import {WebWorkerMessageType} from "./types" import {checkRequirements, checkWasmFeatures} from "./browser";
import { import {preventDefaultTouchActions} from "./canvas";
bigInt, // @ts-ignore esbuild plugin is handling this
bulkMemory, import MultithreadedPoolWorker from './multithreaded/multithreaded-pool.worker.js';
exceptions, // @ts-ignore esbuild plugin is handling this
multiValue, import PoolWorker from './singlethreaded/pool.worker.js';
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)
}
}*/
export const startMapLibre = async (wasmPath: string | undefined, workerPath: string | undefined) => { export const startMapLibre = async (wasmPath: string | undefined, workerPath: string | undefined) => {
await checkWasmFeatures() await checkWasmFeatures()
if (!checkRequirements()) { let message = checkRequirements();
if (message) {
console.error(message)
alert(message)
return return
} }
if (WEBGL) {
let spector = new Spector()
spector.displayUI()
}
preventDefaultTouchActions(); 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}) const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY / PAGES, shared: true})
await init(wasmPath, memory) await maplibre.default(wasmPath, memory)
const schedulerPtr = create_pool_scheduler(() => { maplibre.run(await maplibre.create_map(() => {
return workerPath ? new PoolWorker(workerPath, { return workerPath ? new Worker(workerPath, {
type: 'module' type: 'module'
}) : PoolWorker(); }) : MultithreadedPoolWorker();
}) }))
} else {
const memory = new WebAssembly.Memory({initial: 1024, shared: false})
await maplibre.default(wasmPath, memory);
// setupLegacyWebWorker(schedulerPtr, memory) let callbacks: {worker_callback?: (message: MessageEvent) => void} = {}
await run(schedulerPtr) 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;
})
let clonedMap = maplibre.clone_map(map)
callbacks.worker_callback = (message) => {
let tag = message.data[0];
let data = new Uint8Array(message.data[1]);
// @ts-ignore TODO unsync_main_entry may not be defined
maplibre.singlethreaded_main_entry(clonedMap, tag, data)
}
maplibre.run(map)
}
} }

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 => { 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`: // Propagate to main `onerror`:
setTimeout(() => { setTimeout(() => {
throw err; throw err;
@ -13,6 +13,7 @@ onmessage = async message => {
self.onmessage = async message => { self.onmessage = async message => {
// This will queue further commands up until the module is fully initialised: // This will queue further commands up until the module is fully initialised:
await 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

@ -14,4 +14,4 @@
"typeRoots": ["@types"] "typeRoots": ["@types"]
}, },
"include": ["./src/**/*"] "include": ["./src/**/*"]
} }

View File

@ -9,7 +9,7 @@ pub struct WebError(pub String);
impl From<JsValue> for WebError { impl From<JsValue> for WebError {
fn from(maybe_error: JsValue) -> Self { fn from(maybe_error: JsValue) -> Self {
assert!(maybe_error.is_instance_of::<JSError>()); assert!(maybe_error.is_instance_of::<JSError>());
let error: JSError = maybe_error.dyn_into().unwrap(); let error: JSError = maybe_error.dyn_into().unwrap(); // TODO: Remove unwrap
WebError(error.message().as_string().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 std::{borrow::BorrowMut, cell::RefCell, mem, ops::Deref, panic, rc::Rc};
use maplibre_winit::winit::WinitMapWindowConfig;
use maplibre::{io::scheduler::NopScheduler, Map, MapBuilder};
use maplibre_winit::winit::{WinitEnvironment, WinitMapWindowConfig};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use crate::platform::{ use crate::platform::http_client::WHATWGFetchHttpClient;
http_client::WHATWGFetchHttpClient, schedule_method::WebWorkerPoolScheduleMethod,
};
mod error; mod error;
mod platform; mod platform;
@ -38,31 +38,76 @@ pub fn wasm_bindgen_start() {
enable_tracing(); enable_tracing();
} }
#[wasm_bindgen] #[cfg(not(target_feature = "atomics"))]
pub fn create_pool_scheduler( pub type MapType = Map<
new_worker: js_sys::Function, WinitEnvironment<
) -> *mut Scheduler<WebWorkerPoolScheduleMethod> { NopScheduler,
let scheduler = Box::new(Scheduler::new(WebWorkerPoolScheduleMethod::new(new_worker))); 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] #[wasm_bindgen]
pub async fn run(scheduler_ptr: *mut Scheduler<WebWorkerPoolScheduleMethod>) { pub unsafe fn clone_map(map_ptr: *const RefCell<MapType>) -> *const RefCell<MapType> {
let scheduler: Box<Scheduler<WebWorkerPoolScheduleMethod>> = let mut map = Rc::from_raw(map_ptr);
unsafe { Box::from_raw(scheduler_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 #[wasm_bindgen]
MapBuilder::new() pub unsafe fn run(map_ptr: *const RefCell<MapType>) {
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string())) let mut map = Rc::from_raw(map_ptr);
.with_http_client(WHATWGFetchHttpClient::new())
.with_existing_scheduler(*scheduler)
.build()
.initialize()
.await
.run();
// std::mem::forget(scheduler); map.deref().borrow().run();
} }
#[cfg(test)] #[cfg(test)]

View File

@ -23,12 +23,12 @@ impl WHATWGFetchHttpClient {
// Get the global scope // Get the global scope
let global = js_sys::global(); let global = js_sys::global();
assert!(global.is_instance_of::<WorkerGlobalScope>()); 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 // Call fetch on global scope
let maybe_response = JsFuture::from(scope.fetch_with_request(&request)).await?; let maybe_response = JsFuture::from(scope.fetch_with_request(&request)).await?;
assert!(maybe_response.is_instance_of::<Response>()); 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 // Get ArrayBuffer
let maybe_array_buffer = JsFuture::from(response.array_buffer()?).await?; 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?; let maybe_array_buffer = Self::fetch_array_buffer(url).await?;
assert!(maybe_array_buffer.is_instance_of::<ArrayBuffer>()); 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> // Copy data to Vec<u8>
let buffer: Uint8Array = Uint8Array::new(&array_buffer); 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 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. //! 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) //! 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 js_sys::Promise;
use rand::prelude::*; use rand::prelude::*;
@ -23,7 +23,6 @@ pub struct WorkerPool {
struct PoolState { struct PoolState {
workers: RefCell<Vec<Worker>>, workers: RefCell<Vec<Worker>>,
callback: Closure<dyn FnMut(Event)>,
} }
struct Work { struct Work {
@ -46,9 +45,6 @@ impl WorkerPool {
new_worker, new_worker,
state: Rc::new(PoolState { state: Rc::new(PoolState {
workers: RefCell::new(Vec::with_capacity(initial)), 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 { for _ in 0..initial {
@ -95,13 +91,13 @@ impl WorkerPool {
/// message is sent to it. /// message is sent to it.
fn worker(&self) -> Result<Worker, JsValue> { fn worker(&self) -> Result<Worker, JsValue> {
let workers = self.state.workers.borrow(); 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), Some(worker) => Some(worker),
None => None, None => None,
}; };
if result.is_none() { if result.is_none() {
self.spawn(); self.spawn()?;
} }
match result { match result {
@ -124,13 +120,13 @@ impl WorkerPool {
/// message is sent to it. /// message is sent to it.
pub fn execute(&self, f: impl (FnOnce() -> Promise) + Send + 'static) -> Result<(), JsValue> { pub fn execute(&self, f: impl (FnOnce() -> Promise) + Send + 'static) -> Result<(), JsValue> {
let worker = self.worker()?; let worker = self.worker()?;
let work = Box::new(Work { func: Box::new(f) }); let work = Work { func: Box::new(f) };
let ptr = Box::into_raw(work); let work_ptr = Box::into_raw(Box::new(work));
match worker.post_message(&JsValue::from(ptr as u32)) { match worker.post_message(&JsValue::from(work_ptr as u32)) {
Ok(()) => Ok(()), Ok(()) => Ok(()),
Err(e) => { Err(e) => {
unsafe { unsafe {
drop(Box::from_raw(ptr)); drop(Box::from_raw(work_ptr));
} }
Err(e) Err(e)
} }
@ -140,23 +136,18 @@ impl WorkerPool {
impl PoolState { impl PoolState {
fn push(&self, worker: Worker) { 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(); let mut workers = self.workers.borrow_mut();
for existing_worker in workers.iter() { 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); workers.push(worker);
} }
} }
/// Entry point invoked by `worker.js`, a bit of a hack but see the "TODO" above /// Entry point invoked by the worker.
/// about `worker.js` in general.
#[wasm_bindgen] #[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 ptr = unsafe { Box::from_raw(ptr as *mut Work) };
//let global = js_sys::global().unchecked_into::<DedicatedWorkerGlobalScope>();
JsFuture::from((ptr.func)()).await?; JsFuture::from((ptr.func)()).await?;
//global.post_message(&JsValue::undefined())?;
Ok(()) Ok(())
} }

View File

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