Merge pull request #179 from maxammann/refactor-structure

Refactor structure
This commit is contained in:
Max Ammann 2022-11-02 22:07:15 +01:00 committed by GitHub
commit 1be1476fac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
82 changed files with 1957 additions and 1621 deletions

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

@ -20,7 +20,8 @@
<excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/docs/book" />
<excludeFolder url="file://$MODULE_DIR$/web/lib/.parcel-cache" />
<excludeFolder url="file://$MODULE_DIR$/web/lib/src/wasm-pack" />
<excludeFolder url="file://$MODULE_DIR$/web/lib/src/wasm" />
<excludeFolder url="file://$MODULE_DIR$/web/lib/dist" />
<excludeFolder url="file://$MODULE_DIR$/maplibre-cache" />
</content>
<orderEntry type="inheritedJdk" />

View File

@ -1,20 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build Android" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="+nightly-2022-02-26 apk build -p maplibre-android -Zbuild-std" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="backtrace" value="SHORT" />
<envs>
<env name="ANDROID_NDK_ROOT" value="$NDK_PATH$" />
</envs>
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,21 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build WASM (single-threaded)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="command" value="build -p web --features web-webgl --lib --profile wasm-dev --target wasm32-unknown-unknown" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<envs>
<env name="RUSTUP_TOOLCHAIN" value="nightly-2022-07-03-x86_64-unknown-linux-gnu" />
</envs>
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
</component>

View File

@ -11,7 +11,7 @@ maplibre = { path = "../maplibre" }
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
env_logger = "0.9.0"
log = "0.4.17"
ndk-glue = "0.5.0" # version is required by winit
ndk-glue = "0.7.0" # version is required by winit
jni = "0.19.0"
[lib]

6
android/gradle/.idea/kotlinc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.7.20" />
</component>
</project>

View File

@ -1,6 +1,6 @@
plugins {
id 'com.android.application' version '7.0.4' apply true
id 'org.jetbrains.kotlin.android' version '1.6.21' apply true
id 'com.android.application' version '7.2.0' apply true
id 'org.jetbrains.kotlin.android' version '1.7.20' apply true
}
android {
@ -34,6 +34,7 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}
namespace 'com.example.demo'
}
dependencies {

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.demo">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

View File

@ -10,6 +10,6 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MapLibreRs.android_main()
MapLibreRs.start()
}
}

View File

@ -1,7 +1,7 @@
plugins {
id 'org.mozilla.rust-android-gradle.rust-android' version '0.9.3' apply true
id 'com.android.library' version '7.0.4' apply true
id 'org.jetbrains.kotlin.android' version '1.6.21' apply true
id 'com.android.library' version '7.2.0' apply true
id 'org.jetbrains.kotlin.android' version '1.7.20' apply true
}
apply plugin: 'kotlin-android'
@ -14,9 +14,7 @@ android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
@ -27,6 +25,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
namespace 'org.maplibre_rs'
}
cargo {
@ -35,6 +34,8 @@ cargo {
libname = "maplibre_android"
targetDirectory = "${module}/../target"
profile = "debug"
rustupChannel = "nightly-2022-10-23"
features {
defaultAnd "foo", "bar"

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="org.maplibre_rs" />
<manifest/>

View File

@ -1,6 +1,12 @@
package org.maplibre_rs;
import android.os.Environment;
public class MapLibreRs {
public static void start() {
android_main();
}
public static native void android_main();
static {

View File

@ -5,9 +5,8 @@ use log::Level;
use maplibre::{
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
render::settings::{Backends, WgpuSettings},
MapBuilder,
};
use maplibre_winit::winit::{run_headed_map, WinitMapWindowConfig};
use maplibre_winit::{run_headed_map, WinitMapWindowConfig};
#[cfg(not(target_os = "android"))]
compile_error!("android works only on android.");

View File

@ -1,11 +1,9 @@
use maplibre::{
io::apc::SchedulerAsyncProcedureCall,
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
MapBuilder,
};
use maplibre_winit::winit::{
use maplibre_winit::{
run_headed_map, WinitEnvironment, WinitEventLoop, WinitMapWindow, WinitMapWindowConfig,
WinitWindow,
};
#[cfg(not(any(target_os = "macos", target_os = "ios")))]

View File

@ -4,7 +4,10 @@ use criterion::{criterion_group, criterion_main, Criterion};
use maplibre::{
coords::{WorldTileCoords, ZoomLevel},
error::Error,
headless::{utils::HeadlessPipelineProcessor, HeadlessEnvironment, HeadlessMapWindowConfig},
headless::{
create_headless_renderer, environment::HeadlessEnvironment, map::HeadlessMap,
window::HeadlessMapWindowConfig,
},
io::{
apc::SchedulerAsyncProcedureCall,
pipeline::{PipelineContext, Processable},
@ -12,43 +15,32 @@ use maplibre::{
tile_pipelines::build_vector_tile_pipeline,
TileRequest,
},
kernel::{Kernel, KernelBuilder},
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
render::settings::{RendererSettings, TextureFormat},
render::{
builder::{InitializedRenderer, RendererBuilder},
settings::{RendererSettings, TextureFormat},
},
style::Style,
window::WindowSize,
MapBuilder,
};
fn headless_render(c: &mut Criterion) {
c.bench_function("headless_render", |b| {
let mut map = run_multithreaded(async {
let client = ReqwestHttpClient::new(None);
let (mut map, tile) = run_multithreaded(async {
let (kernel, renderer) = create_headless_renderer(1000, None).await;
let style = Style::default();
let mut map = HeadlessMap::new(style, renderer, kernel).unwrap();
let mut map = MapBuilder::<
HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>,
>::new()
.with_map_window_config(HeadlessMapWindowConfig {
size: WindowSize::new(1000, 1000).unwrap(),
})
.with_http_client(client.clone())
.with_apc(SchedulerAsyncProcedureCall::new(
client,
TokioScheduler::new(),
))
.with_scheduler(TokioScheduler::new())
.with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,
..RendererSettings::default()
})
.build()
.initialize_headless()
.await;
map.map_schedule
.fetch_process(&WorldTileCoords::from((0, 0, ZoomLevel::default())))
let tile = map
.fetch_tile(
WorldTileCoords::from((0, 0, ZoomLevel::default())),
&["water"],
)
.await
.expect("Failed to fetch and process!");
map
(map, tile)
});
b.to_async(
@ -58,7 +50,7 @@ fn headless_render(c: &mut Criterion) {
.unwrap(),
)
.iter(|| {
match map.map_schedule_mut().update_and_redraw() {
match map.render_tile(tile.clone()) {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);

View File

@ -97,8 +97,16 @@ web-test FEATURES: nightly-toolchain
#profile-bench:
# cargo flamegraph --bench render -- --bench
build-android: nightly-toolchain print-android-env
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew assembleDebug
build-android: build-android-lib build-android-demo
build-android-lib: nightly-toolchain print-android-env
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew :lib:assembleDebug
build-android-demo: nightly-toolchain print-android-env
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew :demo:assembleDebug
install-android-demo: nightly-toolchain print-android-env
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cd android/gradle && ./gradlew :demo:installDebug
test-android TARGET: nightly-toolchain print-android-env
export RUSTUP_TOOLCHAIN=$NIGHTLY_TOOLCHAIN && cargo test -p maplibre-android --target {{TARGET}} -Z build-std=std,panic_abort

View File

@ -1,37 +1,33 @@
use maplibre::{
coords::{LatLon, WorldTileCoords},
error::Error,
headless::{HeadlessEnvironment, HeadlessMapWindowConfig},
headless::{create_headless_renderer, map::HeadlessMap, window::HeadlessMapWindowConfig},
io::apc::SchedulerAsyncProcedureCall,
kernel::KernelBuilder,
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
render::settings::{RendererSettings, TextureFormat},
render::{
builder::RendererBuilder,
settings::{RendererSettings, TextureFormat},
},
style::Style,
util::grid::google_mercator,
window::WindowSize,
MapBuilder,
};
use maplibre_winit::winit::WinitEnvironment;
use maplibre_winit::WinitEnvironment;
use tile_grid::{extent_wgs84_to_merc, Extent, GridIterator};
pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
let client = ReqwestHttpClient::new(None);
let mut map =
MapBuilder::<HeadlessEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>>::new()
.with_map_window_config(HeadlessMapWindowConfig {
size: WindowSize::new(tile_size, tile_size).unwrap(),
})
.with_http_client(client.clone())
.with_apc(SchedulerAsyncProcedureCall::new(
client,
TokioScheduler::new(),
)) // FIXME (wasm-executor): avoid passing client and scheduler here
.with_scheduler(TokioScheduler::new())
.with_renderer_settings(RendererSettings {
texture_format: TextureFormat::Rgba8UnormSrgb,
..RendererSettings::default()
})
.build()
.initialize_headless()
.await;
let (kernel, renderer) = create_headless_renderer(tile_size, None).await;
let style = Style::default();
let requested_layers = style
.layers
.iter()
.map(|layer| layer.source_layer.as_ref().unwrap().clone())
.collect::<Vec<_>>();
let mut map = HeadlessMap::new(style, renderer, kernel).unwrap();
let tile_limits = google_mercator().tile_limits(
extent_wgs84_to_merc(&Extent {
@ -46,18 +42,17 @@ pub async fn run_headless(tile_size: u32, min: LatLon, max: LatLon) {
for (z, x, y) in GridIterator::new(10, 10, tile_limits) {
let coords = WorldTileCoords::from((x as i32, y as i32, z.into()));
println!("Rendering {}", &coords);
map.map_schedule
.fetch_process(&coords)
let tile = map
.fetch_tile(
coords,
&requested_layers
.iter()
.map(|layer| layer.as_str())
.collect::<Vec<_>>(),
)
.await
.expect("Failed to fetch and process!");
.expect("Failed to fetch and process");
match map.map_schedule_mut().update_and_redraw() {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);
if e.should_exit() {}
}
e => eprintln!("{:?}", e),
};
map.render_tile(tile).expect("Rendering failed");
}
}

View File

@ -2,7 +2,7 @@ use std::io::ErrorKind;
use clap::{builder::ValueParser, Parser, Subcommand};
use maplibre::{coords::LatLon, platform::run_multithreaded};
use maplibre_winit::winit::run_headed_map;
use maplibre_winit::run_headed_map;
use crate::headless::run_headless;

View File

@ -18,7 +18,7 @@ wasm-bindgen = "0.2.81"
wasm-bindgen-futures = "0.4.31"
[dependencies]
maplibre = { path = "../maplibre", version = "0.0.2" }
maplibre = { path = "../maplibre", version = "0.0.2", default-features = false }
winit = { version = "0.27.2", default-features = false }
cgmath = "0.18.0"
instant = { version = "0.1.12", features = ["wasm-bindgen"] } # TODO: Untrusted dependency

View File

@ -3,7 +3,7 @@
use std::time::Duration;
use cgmath::Vector2;
use maplibre::context::ViewState;
use maplibre::world::ViewState;
use winit::event::{DeviceEvent, KeyboardInput, TouchPhase, WindowEvent};
use crate::input::{

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use cgmath::{EuclideanSpace, Point3, Vector2, Vector3, Zero};
use maplibre::{context::ViewState, render::camera::Camera};
use maplibre::{render::camera::Camera, world::ViewState};
use winit::event::{ElementState, MouseButton};
use super::UpdateState;
@ -45,17 +45,17 @@ impl UpdateState for PanHandler {
};
if self.start_camera_position.is_none() {
self.start_camera_position = Some(state.camera.position.to_vec());
self.start_camera_position = Some(state.camera().position().to_vec());
}
if let Some(start_camera_position) = self.start_camera_position {
state.camera.position = Point3::from_vec(
state.camera_mut().move_to(Point3::from_vec(
start_camera_position + Vector3::new(delta.x, delta.y, 0.0),
);
));
}
}
} else {
self.reference_camera = Some(state.camera.clone());
self.reference_camera = Some(state.camera().clone());
}
}
}

View File

@ -1,6 +1,6 @@
use std::time::Duration;
use maplibre::context::ViewState;
use maplibre::world::ViewState;
use super::UpdateState;

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use cgmath::Vector2;
use maplibre::context::ViewState;
use maplibre::world::ViewState;
use winit::event::{ElementState, MouseButton};
use crate::input::UpdateState;
@ -66,7 +66,7 @@ impl UpdateState for QueryHandler {
let _z = state.visible_level(); // FIXME: can be wrong, if tiles of different z are visible
let _zoom = state.zoom();
if let Some(_coordinates) = state.camera.window_to_world_at_ground(
if let Some(_coordinates) = state.camera().window_to_world_at_ground(
&window_position,
&inverted_view_proj,
false,

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use cgmath::{Vector3, Zero};
use maplibre::context::ViewState;
use maplibre::world::ViewState;
use super::UpdateState;
@ -17,7 +17,7 @@ impl UpdateState for ShiftHandler {
let dt = dt.as_secs_f64() * (1.0 / self.speed);
let delta = self.camera_translate * dt;
state.camera.position += delta;
state.camera_mut().move_relative(delta);
self.camera_translate -= delta;
}
}

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use cgmath::{Deg, Rad, Zero};
use maplibre::context::ViewState;
use maplibre::world::ViewState;
use super::UpdateState;
@ -17,7 +17,7 @@ impl UpdateState for TiltHandler {
let dt = dt.as_secs_f64() * (1.0 / self.speed);
let delta = self.delta_pitch * dt;
state.camera.pitch += Rad::from(delta);
state.camera_mut().pitch_self(delta);
self.delta_pitch -= delta;
}
}

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use cgmath::{Vector2, Vector3};
use maplibre::{context::ViewState, coords::Zoom};
use maplibre::{coords::Zoom, world::ViewState};
use super::UpdateState;
@ -24,7 +24,7 @@ impl UpdateState for ZoomHandler {
let view_proj = state.view_projection();
let inverted_view_proj = view_proj.invert();
if let Some(cursor_position) = state.camera.window_to_world_at_ground(
if let Some(cursor_position) = state.camera().window_to_world_at_ground(
&window_position,
&inverted_view_proj,
false,
@ -37,7 +37,7 @@ impl UpdateState for ZoomHandler {
cursor_position.z,
) - cursor_position;
state.camera.position += delta;
state.camera_mut().move_relative(delta);
}
}
}

View File

@ -1,2 +1,221 @@
pub mod input;
pub mod winit;
use std::{cell::RefCell, fmt::Debug, marker::PhantomData, ops::Deref, rc::Rc};
use instant::Instant;
use log::info;
use maplibre::{
environment::Environment,
error::Error,
event_loop::{EventLoop, EventLoopProxy},
io::{
apc::{AsyncProcedureCall, Message},
scheduler::Scheduler,
source_client::HttpClient,
transferables::{DefaultTransferables, Transferables},
},
map::Map,
render::{
builder::RendererBuilder,
settings::{Backends, WgpuSettings},
},
window::{HeadedMapWindow, MapWindowConfig},
};
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::ControlFlow,
};
use crate::input::{InputController, UpdateState};
pub type RawWinitWindow = winit::window::Window;
pub type RawWinitEventLoop<ET> = winit::event_loop::EventLoop<ET>;
pub type RawEventLoopProxy<ET> = winit::event_loop::EventLoopProxy<ET>;
#[cfg(target_arch = "wasm32")]
mod web;
#[cfg(not(target_arch = "wasm32"))]
mod noweb;
#[cfg(not(target_arch = "wasm32"))]
pub use noweb::*;
#[cfg(target_arch = "wasm32")]
pub use web::*;
pub struct WinitMapWindow<ET: 'static> {
window: RawWinitWindow,
event_loop: Option<WinitEventLoop<ET>>,
}
impl<ET> WinitMapWindow<ET> {
pub fn take_event_loop(&mut self) -> Option<WinitEventLoop<ET>> {
self.event_loop.take()
}
}
impl<ET> HeadedMapWindow for WinitMapWindow<ET> {
type RawWindow = RawWinitWindow;
fn raw(&self) -> &Self::RawWindow {
&self.window
}
fn request_redraw(&self) {
self.window.request_redraw()
}
fn id(&self) -> u64 {
self.window.id().into()
}
}
pub struct WinitEventLoop<ET: 'static> {
event_loop: RawWinitEventLoop<ET>,
}
impl<ET: 'static + PartialEq + Debug> EventLoop<ET> for WinitEventLoop<ET> {
type EventLoopProxy = WinitEventLoopProxy<ET>;
fn run<E>(mut self, mut map: Map<E>, max_frames: Option<u64>)
where
E: Environment,
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
let mut last_render_time = Instant::now();
let mut current_frame: u64 = 0;
let mut input_controller = InputController::new(0.2, 100.0, 0.1);
self.event_loop
.run(move |event, window_target, control_flow| {
#[cfg(target_os = "android")]
if !map.has_renderer() && event == Event::Resumed {
use tokio::{runtime::Handle, task};
task::block_in_place(|| {
Handle::current().block_on(async {
map.initialize_renderer(RendererBuilder::new()
.with_wgpu_settings(WgpuSettings {
backends: Some(Backends::VULKAN),
..WgpuSettings::default()
})).await.unwrap();
})
});
return;
}
match event {
Event::DeviceEvent {
ref event,
.. // We're not using device_id currently
} => {
input_controller.device_input(event);
}
Event::WindowEvent {
ref event,
window_id,
} if window_id == map.window().id().into() => {
if !input_controller.window_input(event) {
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
} => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => {
if let Ok(map_context) = map.context_mut() {
map_context.resize(physical_size.width, physical_size.height);
}
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
if let Ok(map_context) = map.context_mut() {
map_context.resize(new_inner_size.width, new_inner_size.height);
}
}
_ => {}
}
}
}
Event::RedrawRequested(_) => {
let now = Instant::now();
let dt = now - last_render_time;
last_render_time = now;
if let Ok(map_context) = map.context_mut() {
input_controller.update_state(map_context.world.view_state_mut(), dt);
}
match map.run_schedule() {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);
if e.should_exit() {
*control_flow = ControlFlow::Exit;
}
}
e => eprintln!("{:?}", e)
};
if let Some(max_frames) = max_frames {
if current_frame >= max_frames {
log::info!("Exiting because maximum frames reached.");
*control_flow = ControlFlow::Exit;
}
current_frame += 1;
}
}
Event::Suspended => {
// FIXME unimplemented!()
}
Event::Resumed => {
// FIXME unimplemented!()
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
map.window().request_redraw();
}
_ => {}
}
});
}
fn create_proxy(&self) -> Self::EventLoopProxy {
WinitEventLoopProxy {
proxy: self.event_loop.create_proxy(),
}
}
}
pub struct WinitEventLoopProxy<ET: 'static> {
proxy: RawEventLoopProxy<ET>,
}
impl<ET: 'static> EventLoopProxy<ET> for WinitEventLoopProxy<ET> {
fn send_event(&self, event: ET) {
self.proxy.send_event(event); // FIXME: Handle unwrap
}
}
pub struct WinitEnvironment<S: Scheduler, HC: HttpClient, APC: AsyncProcedureCall<HC>, ET> {
phantom_s: PhantomData<S>,
phantom_hc: PhantomData<HC>,
phantom_apc: PhantomData<APC>,
phantom_et: PhantomData<ET>,
}
impl<S: Scheduler, HC: HttpClient, APC: AsyncProcedureCall<HC>, ET: 'static> Environment
for WinitEnvironment<S, HC, APC, ET>
{
type MapWindowConfig = WinitMapWindowConfig<ET>;
type AsyncProcedureCall = APC;
type Scheduler = S;
type HttpClient = HC;
}

View File

@ -3,18 +3,42 @@
//! * Platform Events like suspend/resume
//! * Render a new frame
use std::marker::PhantomData;
use maplibre::{
event_loop::EventLoop,
io::apc::SchedulerAsyncProcedureCall,
kernel::{Kernel, KernelBuilder},
map::Map,
platform::{http_client::ReqwestHttpClient, run_multithreaded, scheduler::TokioScheduler},
render::{
builder::{InitializationResult, InitializedRenderer, RendererBuilder},
settings::{Backends, RendererSettings, WgpuSettings},
},
style::Style,
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
MapBuilder,
};
use winit::window::WindowBuilder;
use super::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
use crate::winit::WinitEnvironment;
use super::{RawWinitEventLoop, RawWinitWindow, WinitMapWindow};
use crate::{WinitEnvironment, WinitEventLoop};
impl MapWindow for WinitMapWindow {
pub struct WinitMapWindowConfig<ET> {
title: String,
phantom_et: PhantomData<ET>,
}
impl<ET> WinitMapWindowConfig<ET> {
pub fn new(title: String) -> Self {
Self {
title,
phantom_et: Default::default(),
}
}
}
impl<ET> MapWindow for WinitMapWindow<ET> {
fn size(&self) -> WindowSize {
let size = self.window.inner_size();
#[cfg(target_os = "android")]
@ -29,27 +53,22 @@ impl MapWindow for WinitMapWindow {
window_size
}
}
impl HeadedMapWindow for WinitMapWindow {
type RawWindow = WinitWindow;
fn inner(&self) -> &Self::RawWindow {
&self.window
}
}
impl MapWindowConfig for WinitMapWindowConfig {
type MapWindow = WinitMapWindow;
impl<ET: 'static> MapWindowConfig for WinitMapWindowConfig<ET> {
type MapWindow = WinitMapWindow<ET>;
fn create(&self) -> Self::MapWindow {
let event_loop = WinitEventLoop::new();
let raw_event_loop = winit::event_loop::EventLoopBuilder::<ET>::with_user_event().build();
let window = WindowBuilder::new()
.with_title(&self.title)
.build(&event_loop)
.build(&raw_event_loop)
.unwrap();
Self::MapWindow {
window,
event_loop: Some(event_loop),
event_loop: Some(WinitEventLoop {
event_loop: raw_event_loop,
}),
}
}
}
@ -57,7 +76,7 @@ impl MapWindowConfig for WinitMapWindowConfig {
pub fn run_headed_map(cache_path: Option<String>) {
run_multithreaded(async {
let client = ReqwestHttpClient::new(cache_path);
MapBuilder::<WinitEnvironment<_, _, _, SchedulerAsyncProcedureCall<_, _>>>::new()
let kernel: Kernel<WinitEnvironment<_, _, _, ()>> = KernelBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(client.clone())
.with_apc(SchedulerAsyncProcedureCall::new(
@ -65,9 +84,23 @@ pub fn run_headed_map(cache_path: Option<String>) {
TokioScheduler::new(),
))
.with_scheduler(TokioScheduler::new())
.build()
.initialize()
.build();
let mut map = Map::new(Style::default(), kernel).unwrap();
#[cfg(not(target_os = "android"))]
{
map.initialize_renderer(RendererBuilder::new().with_wgpu_settings(WgpuSettings {
backends: Some(Backends::VULKAN),
..WgpuSettings::default()
}))
.await
.run()
.unwrap();
}
map.window_mut()
.take_event_loop()
.expect("Event loop is not available")
.run(map, None)
})
}

View File

@ -1,42 +1,54 @@
use std::marker::PhantomData;
use maplibre::window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize};
use winit::{platform::web::WindowBuilderExtWebSys, window::WindowBuilder};
use super::{WinitEventLoop, WinitMapWindow, WinitMapWindowConfig, WinitWindow};
use super::{RawWinitEventLoop, RawWinitWindow, WinitMapWindow};
use crate::WinitEventLoop;
impl MapWindowConfig for WinitMapWindowConfig {
type MapWindow = WinitMapWindow;
pub struct WinitMapWindowConfig<ET> {
canvas_id: String,
phantom_et: PhantomData<ET>,
}
impl<ET: 'static> WinitMapWindowConfig<ET> {
pub fn new(canvas_id: String) -> Self {
Self {
canvas_id,
phantom_et: Default::default(),
}
}
}
impl<ET: 'static> MapWindowConfig for WinitMapWindowConfig<ET> {
type MapWindow = WinitMapWindow<ET>;
fn create(&self) -> Self::MapWindow {
let event_loop = WinitEventLoop::new();
let raw_event_loop = winit::event_loop::EventLoopBuilder::<ET>::with_user_event().build();
let window: winit::window::Window = WindowBuilder::new()
.with_canvas(Some(get_canvas(&self.canvas_id)))
.build(&event_loop)
.build(&raw_event_loop)
.unwrap();
let size = get_body_size().unwrap();
window.set_inner_size(size);
Self::MapWindow {
window,
event_loop: Some(event_loop),
event_loop: Some(WinitEventLoop {
event_loop: raw_event_loop,
}),
}
}
}
impl MapWindow for WinitMapWindow {
impl<ET: 'static> MapWindow for WinitMapWindow<ET> {
fn size(&self) -> WindowSize {
let size = self.window.inner_size();
WindowSize::new(size.width, size.height).expect("failed to get window dimensions.")
}
}
impl HeadedMapWindow for WinitMapWindow {
type RawWindow = WinitWindow;
fn inner(&self) -> &Self::RawWindow {
&self.window
}
}
pub fn get_body_size() -> Option<winit::dpi::LogicalSize<i32>> {
let web_window: web_sys::Window = web_sys::window().unwrap();

View File

@ -1,205 +0,0 @@
use std::{cell::RefCell, marker::PhantomData, ops::Deref, rc::Rc};
use instant::Instant;
use maplibre::{
environment::Environment,
error::Error,
io::{
apc::{AsyncProcedureCall, Message},
scheduler::Scheduler,
source_client::HttpClient,
transferables::{DefaultTransferables, Transferables},
},
map_schedule::InteractiveMapSchedule,
window::{EventLoop, HeadedMapWindow, MapWindowConfig},
};
use winit::{
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::ControlFlow,
};
use crate::input::{InputController, UpdateState};
#[cfg(target_arch = "wasm32")]
mod web;
#[cfg(not(target_arch = "wasm32"))]
mod noweb;
#[cfg(not(target_arch = "wasm32"))]
pub use noweb::*;
#[cfg(target_arch = "wasm32")]
pub use web::*;
#[cfg(not(target_arch = "wasm32"))]
pub struct WinitMapWindowConfig {
title: String,
}
#[cfg(not(target_arch = "wasm32"))]
impl WinitMapWindowConfig {
pub fn new(title: String) -> Self {
Self { title }
}
}
#[cfg(target_arch = "wasm32")]
pub struct WinitMapWindowConfig {
canvas_id: String,
}
#[cfg(target_arch = "wasm32")]
impl WinitMapWindowConfig {
pub fn new(canvas_id: String) -> Self {
Self { canvas_id }
}
}
pub struct WinitMapWindow {
window: WinitWindow,
event_loop: Option<WinitEventLoop>,
}
impl WinitMapWindow {
pub fn take_event_loop(&mut self) -> Option<WinitEventLoop> {
self.event_loop.take()
}
}
pub type WinitWindow = winit::window::Window;
pub type WinitEventLoop = winit::event_loop::EventLoop<()>;
pub struct WinitEnvironment<
S: Scheduler,
HC: HttpClient,
T: Transferables,
APC: AsyncProcedureCall<T, HC>,
> {
phantom_s: PhantomData<S>,
phantom_hc: PhantomData<HC>,
phantom_t: PhantomData<T>,
phantom_apc: PhantomData<APC>,
}
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
for WinitEnvironment<S, HC, T, APC>
{
type MapWindowConfig = WinitMapWindowConfig;
type AsyncProcedureCall = APC;
type Scheduler = S;
type HttpClient = HC;
type Transferables = T;
}
///Main (platform-specific) main loop which handles:
///* Input (Mouse/Keyboard)
///* Platform Events like suspend/resume
///* Render a new frame
impl<E: Environment> EventLoop<E> for WinitMapWindow
where
E::MapWindowConfig: MapWindowConfig<MapWindow = WinitMapWindow>,
{
fn run(
mut self,
map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
max_frames: Option<u64>,
) {
let mut last_render_time = Instant::now();
let mut current_frame: u64 = 0;
let mut input_controller = InputController::new(0.2, 100.0, 0.1);
self.take_event_loop()
.unwrap()
.run(move |event, _, control_flow| {
let mut map_schedule = map_schedule.deref().borrow_mut();
#[cfg(target_os = "android")]
if !map_schedule.is_initialized() && event == Event::Resumed {
use tokio::{runtime::Handle, task};
task::block_in_place(|| {
Handle::current().block_on(async {
map_schedule.late_init().await;
})
});
return;
}
match event {
Event::DeviceEvent {
ref event,
.. // We're not using device_id currently
} => {
input_controller.device_input(event);
}
Event::WindowEvent {
ref event,
window_id,
} if window_id == self.inner().id() => {
if !input_controller.window_input(event) {
match event {
WindowEvent::CloseRequested
| WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(VirtualKeyCode::Escape),
..
},
..
} => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => {
map_schedule.resize(physical_size.width, physical_size.height);
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
map_schedule.resize(new_inner_size.width, new_inner_size.height);
}
_ => {}
}
}
}
Event::RedrawRequested(_) => {
let now = Instant::now();
let dt = now - last_render_time;
last_render_time = now;
input_controller.update_state(map_schedule.view_state_mut(), dt);
match map_schedule.update_and_redraw() {
Ok(_) => {}
Err(Error::Render(e)) => {
eprintln!("{}", e);
if e.should_exit() {
*control_flow = ControlFlow::Exit;
}
}
e => eprintln!("{:?}", e)
};
if let Some(max_frames) = max_frames {
if current_frame >= max_frames {
log::info!("Exiting because maximum frames reached.");
*control_flow = ControlFlow::Exit;
}
current_frame += 1;
}
}
Event::Suspended => {
map_schedule.suspend();
}
Event::Resumed => {
map_schedule.resume(&self);
}
Event::MainEventsCleared => {
// RedrawRequested will only trigger once, unless we manually
// request it.
self.inner().request_redraw();
}
_ => {}
}
});
}
}

View File

@ -9,10 +9,11 @@ description = "Native Maps for Web, Mobile and Desktop"
readme = "../README.md"
[features]
default = ["thread-safe-futures"]
web-webgl = ["wgpu/webgl"]
# Enable tracing using tracy on desktop/mobile and the chrome profiler on web
trace = ["tracing-subscriber", "tracing-tracy", "tracy-client"]
no-thread-safe-futures = []
thread-safe-futures = []
embed-static-tiles = ["maplibre-build-tools/sqlite"]
headless = ["png"]

View File

@ -1,93 +1,19 @@
use std::ops::Div;
use cgmath::Angle;
use crate::{
coords::{LatLon, ViewRegion, WorldCoords, Zoom, ZoomLevel, TILE_SIZE},
io::tile_repository::TileRepository,
render::camera::{Camera, Perspective, ViewProjection},
util::ChangeObserver,
Renderer, Style, WindowSize,
render::Renderer,
style::Style,
world::{ViewState, World},
};
/// Stores the camera configuration.
pub struct ViewState {
pub zoom: ChangeObserver<Zoom>,
pub camera: ChangeObserver<Camera>,
pub perspective: Perspective,
}
impl ViewState {
pub fn new<P: Into<cgmath::Rad<f64>>>(
window_size: &WindowSize,
position: WorldCoords,
zoom: Zoom,
pitch: f64,
fovy: P,
) -> Self {
let tile_center = TILE_SIZE / 2.0;
let fovy = fovy.into();
let height = tile_center / (fovy / 2.0).tan();
let camera = Camera::new(
(position.x, position.y, height),
cgmath::Deg(-90.0),
cgmath::Deg(pitch),
window_size.width(),
window_size.height(),
);
let perspective = Perspective::new(
window_size.width(),
window_size.height(),
cgmath::Deg(110.0),
// in tile.vertex.wgsl we are setting each layer's final `z` in ndc space to `z_index`.
// This means that regardless of the `znear` value all layers will be rendered as part
// of the near plane.
// These values have been selected experimentally:
// https://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
1024.0,
2048.0,
);
Self {
zoom: ChangeObserver::new(zoom),
camera: ChangeObserver::new(camera),
perspective,
}
}
pub fn create_view_region(&self) -> Option<ViewRegion> {
self.camera
.view_region_bounding_box(&self.view_projection().invert())
.map(|bounding_box| {
ViewRegion::new(bounding_box, 0, 32, *self.zoom, self.visible_level())
})
}
pub fn view_projection(&self) -> ViewProjection {
self.camera.calc_view_proj(&self.perspective)
}
pub fn visible_level(&self) -> ZoomLevel {
self.zoom.level()
}
pub fn zoom(&self) -> Zoom {
*self.zoom
}
pub fn update_zoom(&mut self, new_zoom: Zoom) {
*self.zoom = new_zoom;
log::info!("zoom: {}", new_zoom);
}
}
/// Stores the context of the map.
pub struct MapContext {
pub view_state: ViewState,
pub style: Style,
pub tile_repository: TileRepository,
pub world: World,
pub renderer: Renderer,
}
impl MapContext {
pub fn resize(&mut self, width: u32, height: u32) {
self.world.view_state.resize(width, height);
self.renderer.resize(width, height)
}
}

View File

@ -1,19 +1,31 @@
use crate::{
event_loop::EventLoopConfig,
io::{
apc::AsyncProcedureCall,
scheduler::Scheduler,
source_client::{HttpClient, HttpSourceClient, SourceClient},
transferables::{
DefaultTessellatedLayer, DefaultTileTessellated, DefaultUnavailableLayer, Transferables,
},
},
HttpClient, MapWindowConfig, Scheduler,
kernel::Kernel,
window::MapWindowConfig,
};
/// The environment defines which types must be injected into maplibre at compile time.
/// Essentially, this trait implements the
/// [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) design pattern.
/// By instantiating this trait at compile time with concrete types, it is possible to create
/// different compile-time instances of maplibre.
///
/// For example it is possible to change the way tasks are scheduled. It is also possible to change
/// the HTTP implementation for fetching tiles over the network.
pub trait Environment: 'static {
type MapWindowConfig: MapWindowConfig;
type AsyncProcedureCall: AsyncProcedureCall<Self::Transferables, Self::HttpClient>;
type Scheduler: Scheduler;
type HttpClient: HttpClient;
type AsyncProcedureCall: AsyncProcedureCall<Self::HttpClient>;
type Transferables: Transferables;
type Scheduler: Scheduler;
type HttpClient: HttpClient;
}

View File

@ -1,45 +1,28 @@
//! Errors which can happen in various parts of the library.
use std::{fmt, fmt::Formatter, sync::mpsc::SendError};
use std::{borrow::Cow, fmt, fmt::Formatter, sync::mpsc::SendError};
use lyon::tessellation::TessellationError;
#[derive(Debug)]
pub enum RenderError {
Surface(wgpu::SurfaceError),
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
RenderError::Surface(e) => write!(f, "{}", e),
}
}
}
impl RenderError {
pub fn should_exit(&self) -> bool {
match self {
RenderError::Surface(e) => match e {
wgpu::SurfaceError::OutOfMemory => true,
_ => false,
},
}
}
}
use crate::render::{error::RenderError, graph::RenderGraphError};
/// Enumeration of errors which can happen during the operation of the library.
#[derive(Debug)]
pub enum Error {
Schedule,
APC,
Scheduler,
Network(String),
Tesselation(TessellationError),
Render(RenderError),
Generic(Cow<'static, str>),
}
impl From<wgpu::SurfaceError> for Error {
fn from(e: wgpu::SurfaceError) -> Self {
Error::Render(RenderError::Surface(e))
impl<E> From<E> for Error
where
E: Into<RenderError>,
{
fn from(e: E) -> Self {
Error::Render(e.into())
}
}
@ -51,6 +34,6 @@ impl From<TessellationError> for Error {
impl<T> From<SendError<T>> for Error {
fn from(_e: SendError<T>) -> Self {
Error::Schedule
Error::Scheduler
}
}

View File

@ -0,0 +1,27 @@
use crate::{
environment::Environment,
map::Map,
window::{HeadedMapWindow, MapWindowConfig},
};
pub trait EventLoopConfig {
type EventType: 'static;
type EventLoopProxy: EventLoopProxy<Self::EventType>;
fn create_proxy() -> Self::EventLoopProxy;
}
pub trait EventLoopProxy<T: 'static> {
fn send_event(&self, event: T);
}
pub trait EventLoop<ET: 'static + PartialEq> {
type EventLoopProxy: EventLoopProxy<ET>;
fn run<E>(self, map: Map<E>, max_frames: Option<u64>)
where
E: Environment,
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow;
fn create_proxy(&self) -> Self::EventLoopProxy;
}

View File

@ -1,350 +0,0 @@
use std::{
collections::HashSet,
fs::File,
future::Future,
io::Write,
iter,
marker::PhantomData,
ops::{Deref, Range},
sync::Arc,
};
use tokio::{runtime::Handle, task};
use wgpu::{BufferAsyncError, BufferSlice};
use crate::{
context::{MapContext, ViewState},
coords::{LatLon, ViewRegion, WorldCoords, WorldTileCoords, Zoom, TILE_SIZE},
error::Error,
headless::utils::HeadlessPipelineProcessor,
io::{
apc::{AsyncProcedureCall, SchedulerAsyncProcedureCall},
pipeline::{PipelineContext, Processable},
source_client::HttpSourceClient,
tile_pipelines::build_vector_tile_pipeline,
tile_repository::{StoredLayer, TileRepository},
transferables::{DefaultTransferables, Transferables},
TileRequest,
},
render::{
camera::ViewProjection,
create_default_render_graph, draw_graph,
eventually::Eventually,
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
register_default_render_stages,
resource::{BufferDimensions, BufferedTextureHead, Head, IndexEntry, TrackedRenderPass},
stages::RenderStageLabel,
RenderState,
},
schedule::{Schedule, Stage},
Environment, HttpClient, MapWindow, MapWindowConfig, Renderer, Scheduler, Style, WindowSize,
};
pub struct HeadlessMapWindowConfig {
pub size: WindowSize,
}
impl MapWindowConfig for HeadlessMapWindowConfig {
type MapWindow = HeadlessMapWindow;
fn create(&self) -> Self::MapWindow {
Self::MapWindow { size: self.size }
}
}
pub struct HeadlessMapWindow {
size: WindowSize,
}
impl MapWindow for HeadlessMapWindow {
fn size(&self) -> WindowSize {
self.size
}
}
pub struct HeadlessEnvironment<
S: Scheduler,
HC: HttpClient,
T: Transferables,
APC: AsyncProcedureCall<T, HC>,
> {
phantom_s: PhantomData<S>,
phantom_hc: PhantomData<HC>,
phantom_t: PhantomData<T>,
phantom_apc: PhantomData<APC>,
}
impl<S: Scheduler, HC: HttpClient, T: Transferables, APC: AsyncProcedureCall<T, HC>> Environment
for HeadlessEnvironment<S, HC, T, APC>
{
type MapWindowConfig = HeadlessMapWindowConfig;
type AsyncProcedureCall = APC;
type Scheduler = S;
type HttpClient = HC;
type Transferables = T;
}
pub struct HeadlessMap<E: Environment> {
pub map_schedule: HeadlessMapSchedule<E>,
pub window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
}
impl<E: Environment> HeadlessMap<E> {
pub fn map_schedule_mut(&mut self) -> &mut HeadlessMapSchedule<E> {
&mut self.map_schedule
}
}
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
pub struct HeadlessMapSchedule<E: Environment> {
map_window_config: E::MapWindowConfig,
pub map_context: MapContext,
schedule: Schedule,
scheduler: E::Scheduler,
http_client: E::HttpClient,
}
impl<E: Environment> HeadlessMapSchedule<E> {
pub fn new(
map_window_config: E::MapWindowConfig,
window_size: WindowSize,
renderer: Renderer,
scheduler: E::Scheduler,
http_client: E::HttpClient,
style: Style,
) -> Self {
let view_state = ViewState::new(
&window_size,
WorldCoords::from((TILE_SIZE / 2., TILE_SIZE / 2.)),
Zoom::default(),
0.0,
cgmath::Deg(110.0),
);
let tile_repository = TileRepository::new();
let mut schedule = Schedule::default();
let mut graph = create_default_render_graph().unwrap(); // TODO: remove unwrap
let draw_graph = graph.get_sub_graph_mut(draw_graph::NAME).unwrap(); // TODO: remove unwrap
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
draw_graph
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
.unwrap(); // TODO: remove unwrap
register_default_render_stages(graph, &mut schedule);
schedule.add_stage(
RenderStageLabel::Cleanup,
WriteSurfaceBufferStage::default(),
);
Self {
map_window_config,
map_context: MapContext {
view_state,
style,
tile_repository,
renderer,
},
schedule,
scheduler,
http_client,
}
}
#[tracing::instrument(name = "update_and_redraw", skip_all)]
pub fn update_and_redraw(&mut self) -> Result<(), Error> {
self.schedule.run(&mut self.map_context);
Ok(())
}
pub fn schedule(&self) -> &Schedule {
&self.schedule
}
pub fn scheduler(&self) -> &E::Scheduler {
&self.scheduler
}
pub fn http_client(&self) -> &E::HttpClient {
&self.http_client
}
pub async fn fetch_process(&mut self, coords: &WorldTileCoords) -> Option<()> {
let source_layers: HashSet<String> = self
.map_context
.style
.layers
.iter()
.filter_map(|layer| layer.source_layer.clone())
.collect();
let http_source_client: HttpSourceClient<E::HttpClient> =
HttpSourceClient::new(self.http_client.clone());
let data = http_source_client
.fetch(&coords)
.await
.unwrap() // TODO: remove unwrap
.into_boxed_slice();
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
let pipeline = build_vector_tile_pipeline();
let request = TileRequest {
coords: WorldTileCoords::default(),
layers: source_layers,
};
pipeline.process((request, data), &mut pipeline_context);
let mut processor = pipeline_context
.take_processor::<HeadlessPipelineProcessor>()
.unwrap(); // TODO: remove unwrap
if let Eventually::Initialized(pool) = self.map_context.renderer.state.buffer_pool_mut() {
pool.clear();
}
self.map_context.tile_repository.clear();
while let Some(layer) = processor.layers.pop() {
self.map_context
.tile_repository
.create_tile(&layer.get_coords());
self.map_context
.tile_repository
.put_tessellated_layer(layer);
}
Some(())
}
}
/// Node which copies the contents of the GPU-side texture in [`BufferedTextureHead`] to an
/// unmapped GPU-side buffer. This buffer will be mapped in
/// [`crate::render::stages::write_surface_buffer_stage::WriteSurfaceBufferStage`].
#[derive(Default)]
pub struct CopySurfaceBufferNode {}
impl CopySurfaceBufferNode {
pub fn new() -> Self {
Self {}
}
}
impl Node for CopySurfaceBufferNode {
fn input(&self) -> Vec<SlotInfo> {
vec![]
}
fn update(&mut self, _state: &mut RenderState) {}
fn run(
&self,
_graph: &mut RenderGraphContext,
RenderContext {
command_encoder, ..
}: &mut RenderContext,
state: &RenderState,
) -> Result<(), NodeRunError> {
let surface = state.surface();
match surface.head() {
Head::Headed(_) => {}
Head::Headless(buffered_texture) => {
let size = surface.size();
command_encoder.copy_texture_to_buffer(
buffered_texture.texture.as_image_copy(),
wgpu::ImageCopyBuffer {
buffer: &buffered_texture.output_buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(
std::num::NonZeroU32::new(
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
)
.unwrap(), // TODO: remove unwrap
),
rows_per_image: None,
},
},
wgpu::Extent3d {
width: size.width() as u32,
height: size.height() as u32,
depth_or_array_layers: 1,
},
);
}
}
Ok(())
}
}
/// Stage which writes the current contents of the GPU/CPU buffer in [`BufferedTextureHead`]
/// to disk as PNG.
#[derive(Default)]
pub struct WriteSurfaceBufferStage {
frame: u64,
}
impl Stage for WriteSurfaceBufferStage {
fn run(
&mut self,
MapContext {
renderer: Renderer { state, device, .. },
..
}: &mut MapContext,
) {
let surface = state.surface();
match surface.head() {
Head::Headed(_) => {}
Head::Headless(buffered_texture) => {
let buffered_texture: Arc<BufferedTextureHead> = buffered_texture.clone();
let device = device.clone();
let current_frame = self.frame;
task::block_in_place(|| {
Handle::current().block_on(async {
buffered_texture
.create_png(&device, format!("frame_{}.png", current_frame).as_str())
.await;
})
});
self.frame += 1;
}
}
}
}
pub mod utils {
use crate::{
coords::WorldTileCoords,
io::{pipeline::PipelineProcessor, tile_repository::StoredLayer, RawLayer},
render::ShaderVertex,
tessellation::{IndexDataType, OverAlignedVertexBuffer},
};
#[derive(Default)]
pub struct HeadlessPipelineProcessor {
pub layers: Vec<StoredLayer>,
}
impl PipelineProcessor for HeadlessPipelineProcessor {
fn layer_tesselation_finished(
&mut self,
coords: &WorldTileCoords,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
feature_indices: Vec<u32>,
layer_data: RawLayer,
) {
self.layers.push(StoredLayer::TessellatedLayer {
coords: *coords,
layer_name: layer_data.name,
buffer,
feature_indices,
})
}
}
}

View File

@ -0,0 +1,15 @@
use crate::{
environment::Environment,
headless::window::HeadlessMapWindowConfig,
io::apc::SchedulerAsyncProcedureCall,
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
};
pub struct HeadlessEnvironment;
impl Environment for HeadlessEnvironment {
type MapWindowConfig = HeadlessMapWindowConfig;
type AsyncProcedureCall = SchedulerAsyncProcedureCall<Self::HttpClient, Self::Scheduler>;
type Scheduler = TokioScheduler;
type HttpClient = ReqwestHttpClient;
}

View File

@ -0,0 +1,65 @@
use crate::render::{
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
resource::Head,
RenderState,
};
/// Node which copies the contents of the GPU-side texture in [`BufferedTextureHead`] to an
/// unmapped GPU-side buffer. This buffer will be mapped in
/// [`crate::render::stages::write_surface_buffer_stage::WriteSurfaceBufferStage`].
#[derive(Default)]
pub struct CopySurfaceBufferNode {}
impl CopySurfaceBufferNode {
pub fn new() -> Self {
Self {}
}
}
impl Node for CopySurfaceBufferNode {
fn input(&self) -> Vec<SlotInfo> {
vec![]
}
fn update(&mut self, _state: &mut RenderState) {}
fn run(
&self,
_graph: &mut RenderGraphContext,
RenderContext {
command_encoder, ..
}: &mut RenderContext,
state: &RenderState,
) -> Result<(), NodeRunError> {
let surface = state.surface();
match surface.head() {
Head::Headed(_) => {}
Head::Headless(buffered_texture) => {
let size = surface.size();
command_encoder.copy_texture_to_buffer(
buffered_texture.texture.as_image_copy(),
wgpu::ImageCopyBuffer {
buffer: &buffered_texture.output_buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(
std::num::NonZeroU32::new(
buffered_texture.buffer_dimensions.padded_bytes_per_row as u32,
)
.unwrap(), // TODO: remove unwrap
),
rows_per_image: None,
},
},
wgpu::Extent3d {
width: size.width() as u32,
height: size.height() as u32,
depth_or_array_layers: 1,
},
);
}
}
Ok(())
}
}

View File

@ -0,0 +1,151 @@
use std::collections::HashSet;
use crate::{
context::MapContext,
coords::{WorldCoords, WorldTileCoords, Zoom, TILE_SIZE},
error::Error,
headless::{
environment::HeadlessEnvironment, graph_node::CopySurfaceBufferNode,
stage::WriteSurfaceBufferStage,
},
io::{
pipeline::{PipelineContext, PipelineProcessor, Processable},
tile_pipelines::build_vector_tile_pipeline,
tile_repository::{StoredLayer, StoredTile, TileStatus},
RawLayer, TileRequest,
},
kernel::Kernel,
render::{
builder::UninitializedRenderer, create_default_render_graph, draw_graph,
error::RenderError, eventually::Eventually, register_default_render_stages, resource::Head,
stages::RenderStageLabel, Renderer, ShaderVertex,
},
schedule::{Schedule, Stage},
style::Style,
tessellation::{IndexDataType, OverAlignedVertexBuffer},
window::WindowSize,
world::World,
};
pub struct HeadlessMap {
kernel: Kernel<HeadlessEnvironment>,
schedule: Schedule,
map_context: MapContext,
}
impl HeadlessMap {
pub fn new(
style: Style,
renderer: Renderer,
kernel: Kernel<HeadlessEnvironment>,
) -> Result<Self, Error> {
let window_size = renderer.state().surface().size();
let world = World::new(
window_size,
WorldCoords::from((TILE_SIZE / 2., TILE_SIZE / 2.)),
Zoom::default(),
cgmath::Deg(0.0),
);
let mut graph = create_default_render_graph()?;
let draw_graph = graph
.get_sub_graph_mut(draw_graph::NAME)
.expect("Subgraph does not exist");
draw_graph.add_node(draw_graph::node::COPY, CopySurfaceBufferNode::default());
draw_graph
.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::COPY)
.unwrap(); // TODO: remove unwrap
let mut schedule = Schedule::default();
register_default_render_stages(graph, &mut schedule);
schedule.add_stage(
RenderStageLabel::Cleanup,
WriteSurfaceBufferStage::default(),
);
Ok(Self {
kernel,
map_context: MapContext {
style,
world,
renderer,
},
schedule,
})
}
pub fn render_tile(&mut self, tile: StoredTile) -> Result<(), Error> {
let context = &mut self.map_context;
if let Eventually::Initialized(pool) = context.renderer.state.buffer_pool_mut() {
pool.clear();
} else {
// TODO return error
}
context.world.tile_repository.clear();
context.world.tile_repository.put_tile(tile);
self.schedule.run(&mut self.map_context);
Ok(())
}
pub async fn fetch_tile(
&self,
coords: WorldTileCoords,
source_layers: &[&str],
) -> Result<StoredTile, Error> {
let source_client = self.kernel.source_client();
let data = source_client.fetch(&coords).await?.into_boxed_slice();
let mut pipeline_context = PipelineContext::new(HeadlessPipelineProcessor::default());
let pipeline = build_vector_tile_pipeline();
let target_coords = WorldTileCoords::default(); // load to 0,0,0
pipeline.process(
(
TileRequest {
coords: target_coords,
layers: source_layers
.iter()
.map(|layer| layer.to_string())
.collect::<HashSet<String>>(),
},
data,
),
&mut pipeline_context,
);
let mut processor = pipeline_context
.take_processor::<HeadlessPipelineProcessor>()
.expect("Unable to get processor");
Ok(StoredTile::success(target_coords, processor.layers))
}
}
#[derive(Default)]
pub struct HeadlessPipelineProcessor {
pub layers: Vec<StoredLayer>,
}
impl PipelineProcessor for HeadlessPipelineProcessor {
fn layer_tesselation_finished(
&mut self,
coords: &WorldTileCoords,
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
feature_indices: Vec<u32>,
layer_data: RawLayer,
) -> Result<(), Error> {
self.layers.push(StoredLayer::TessellatedLayer {
coords: *coords,
layer_name: layer_data.name,
buffer,
feature_indices,
});
Ok(())
}
}

View File

@ -0,0 +1,50 @@
use crate::{
headless::{
environment::HeadlessEnvironment,
window::{HeadlessMapWindow, HeadlessMapWindowConfig},
},
io::apc::SchedulerAsyncProcedureCall,
kernel::{Kernel, KernelBuilder},
platform::{http_client::ReqwestHttpClient, scheduler::TokioScheduler},
render::{
builder::{InitializedRenderer, RendererBuilder},
Renderer,
},
window::{MapWindowConfig, WindowSize},
};
mod graph_node;
mod stage;
pub mod environment;
pub mod map;
pub mod window;
pub async fn create_headless_renderer(
tile_size: u32,
cache_path: Option<String>,
) -> (Kernel<HeadlessEnvironment>, Renderer) {
let client = ReqwestHttpClient::new(cache_path);
let kernel = KernelBuilder::new()
.with_map_window_config(HeadlessMapWindowConfig::new(
WindowSize::new(tile_size, tile_size).unwrap(),
))
.with_http_client(client.clone())
.with_apc(SchedulerAsyncProcedureCall::new(
client,
TokioScheduler::new(),
))
.with_scheduler(TokioScheduler::new())
.build();
let mwc: &HeadlessMapWindowConfig = kernel.map_window_config();
let window: HeadlessMapWindow = mwc.create();
let renderer = RendererBuilder::new()
.build()
.initialize_headless::<HeadlessMapWindowConfig>(&window)
.await
.expect("Failed to initialize renderer");
(kernel, renderer)
}

View File

@ -0,0 +1,50 @@
use std::sync::Arc;
use tokio::{runtime::Handle, task};
use crate::{
context::MapContext,
render::{
resource::{BufferedTextureHead, Head},
Renderer,
},
schedule::Stage,
};
/// Stage which writes the current contents of the GPU/CPU buffer in [`BufferedTextureHead`]
/// to disk as PNG.
#[derive(Default)]
pub struct WriteSurfaceBufferStage {
frame: u64,
}
impl Stage for WriteSurfaceBufferStage {
fn run(
&mut self,
MapContext {
renderer: Renderer { state, device, .. },
..
}: &mut MapContext,
) {
let surface = state.surface();
match surface.head() {
Head::Headed(_) => {}
Head::Headless(buffered_texture) => {
let buffered_texture: Arc<BufferedTextureHead> = buffered_texture.clone();
let device = device.clone();
let current_frame = self.frame;
task::block_in_place(|| {
Handle::current().block_on(async {
buffered_texture
.create_png(&device, format!("frame_{}.png", current_frame).as_str())
.await;
})
});
self.frame += 1;
}
}
}
}

View File

@ -0,0 +1,29 @@
use crate::window::{MapWindow, MapWindowConfig, WindowSize};
pub struct HeadlessMapWindowConfig {
size: WindowSize,
}
impl HeadlessMapWindowConfig {
pub fn new(size: WindowSize) -> Self {
Self { size }
}
}
impl MapWindowConfig for HeadlessMapWindowConfig {
type MapWindow = HeadlessMapWindow;
fn create(&self) -> Self::MapWindow {
Self::MapWindow { size: self.size }
}
}
pub struct HeadlessMapWindow {
size: WindowSize,
}
impl MapWindow for HeadlessMapWindow {
fn size(&self) -> WindowSize {
self.size
}
}

View File

@ -12,17 +12,21 @@ use serde::{Deserialize, Serialize};
use crate::{
coords::WorldTileCoords,
error::Error,
io::{
source_client::{HttpSourceClient, SourceClient},
scheduler::Scheduler,
source_client::{HttpClient, 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.
/// The result of the tessellation of a tile. This is sent as a message from a worker to the caller
/// of an [`AsyncProcedure`].
///
/// * `TessellatedLayer` contains the result of the tessellation for a specific layer.
/// * `UnavailableLayer` is sent if a requested layer is not found.
/// * `TileTessellated` is sent if processing of a tile finished.
#[derive(Clone)]
pub enum Message<T: Transferables> {
TileTessellated(T::TileTessellated),
@ -30,30 +34,82 @@ pub enum Message<T: Transferables> {
TessellatedLayer(T::TessellatedLayer),
}
/// Inputs for an [`AsyncProcedure`]
#[derive(Clone, Serialize, Deserialize)]
pub enum Input {
TileRequest(TileRequest),
}
/// Allows sending messages from workers to back to the caller.
pub trait Context<T: Transferables, HC: HttpClient>: Send + 'static {
fn send(&self, data: Message<T>);
/// Send a message back to the caller.
// FIXME (wasm-executor): handle results send() calls
fn send(&self, data: Message<T>) -> Result<(), Error>;
fn source_client(&self) -> &SourceClient<HC>;
}
#[cfg(not(feature = "no-thread-safe-futures"))]
#[cfg(feature = "thread-safe-futures")]
pub type AsyncProcedureFuture = Pin<Box<(dyn Future<Output = ()> + Send + 'static)>>;
#[cfg(feature = "no-thread-safe-futures")]
#[cfg(not(feature = "thread-safe-futures"))]
pub type AsyncProcedureFuture = Pin<Box<(dyn Future<Output = ()> + 'static)>>;
/// Type definitions for asynchronous procedure calls. These functions can be called in an
/// [`AsyncProcedureCall`]. Functions of this type are required to be statically available at
/// compile time. It is explicitly not possible to use closures, as they would require special
/// serialization which is currently not supported.
pub type AsyncProcedure<C> = fn(input: Input, context: C) -> AsyncProcedureFuture;
pub trait AsyncProcedureCall<T: Transferables, HC: HttpClient>: 'static {
type Context: Context<T, HC> + Send;
/// APCs define an interface for performing work asynchronously.
/// This work can be implemented through procedures which can be called asynchronously, hence the
/// name AsyncProcedureCall or APC for short.
///
/// APCs serve as an abstraction for doing work on a separate thread, and then getting responses
/// back. An asynchronous procedure call can for example be performed by using message passing. In
/// fact this could theoretically work over a network socket.
///
/// It is possible to schedule work on a remote host by calling [`AsyncProcedureCall::call()`]
/// and getting the results back by calling the non-blocking function
/// [`AsyncProcedureCall::receive()`]. The [`AsyncProcedureCall::receive()`] function returns a
/// struct which implements [`Transferables`].
///
/// ## Transferables
///
/// Based on whether the current platform supports shared-memory or not, the implementation of APCs
/// might want to send the whole data from the worker to the caller back or just pointers to that
/// data. The [`Transferables`] trait allows developers to define that and use different data
/// layouts for different platforms.
///
/// ## Message Passing vs APC
///
/// One might wonder why this is called [`AsyncProcedureCall`] instead of `MessagePassingInterface`.
/// The reason for this is quite simple. We are actually referencing and calling procedures which
/// are defined in different threads, processes or hosts. That means, that an [`AsyncProcedureCall`]
/// is actually distinct from a `MessagePassingInterface`.
///
///
/// ## Current Implementations
///
/// We currently have two implementation for APCs. One uses the Tokio async runtime on native
/// targets in [`SchedulerAsyncProcedureCall`].
/// For the web we implemented an alternative way to call APCs which is called
/// [`PassingAsyncProcedureCall`]. This implementation does not depend on shared-memory compared to
/// [`SchedulerAsyncProcedureCall`]. In fact, on the web we are currently not depending on
/// shared-memory because that feature is hidden behind feature flags in browsers
/// (see [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).
///
///
// TODO: Rename to AsyncProcedureCaller?
pub trait AsyncProcedureCall<HC: HttpClient>: 'static {
type Context: Context<Self::Transferables, HC> + Send;
type Transferables: Transferables;
fn receive(&mut self) -> Option<Message<T>>;
/// Try to receive a message non-blocking.
fn receive(&self) -> Option<Message<Self::Transferables>>;
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>);
/// Call an [`AsyncProcedure`] using some [`Input`]. This function is non-blocking and
/// returns immediately.
fn call(&self, input: Input, procedure: AsyncProcedure<Self::Context>);
}
#[derive(Clone)]
@ -63,8 +119,8 @@ pub struct SchedulerContext<T: Transferables, HC: HttpClient> {
}
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 send(&self, data: Message<T>) -> Result<(), Error> {
self.sender.send(data).map_err(|e| Error::APC)
}
fn source_client(&self) -> &SourceClient<HC> {
@ -91,19 +147,18 @@ impl<HC: HttpClient, S: Scheduler> SchedulerAsyncProcedureCall<HC, S> {
}
}
impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<DefaultTransferables, HC>
for SchedulerAsyncProcedureCall<HC, S>
{
type Context = SchedulerContext<DefaultTransferables, HC>;
impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<HC> for SchedulerAsyncProcedureCall<HC, S> {
type Context = SchedulerContext<Self::Transferables, HC>;
type Transferables = DefaultTransferables;
fn receive(&mut self) -> Option<Message<DefaultTransferables>> {
fn receive(&self) -> Option<Message<DefaultTransferables>> {
let transferred = self.channel.1.try_recv().ok()?;
Some(transferred)
}
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
fn call(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
let sender = self.channel.0.clone();
let client = self.http_client.clone(); // FIXME (wasm-executor): do not clone each time
let client = self.http_client.clone(); // TODO (perf): do not clone each time
self.scheduler
.schedule(move || async move {
@ -111,7 +166,7 @@ impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<DefaultTransferables, HC>
input,
SchedulerContext {
sender,
source_client: SourceClient::Http(HttpSourceClient::new(client)),
source_client: SourceClient::new(HttpSourceClient::new(client)),
},
)
.await;

View File

@ -5,28 +5,40 @@ use geozero::mvt::tile;
use crate::{
coords::WorldTileCoords,
error::Error,
io::geometry_index::IndexedGeometry,
render::ShaderVertex,
tessellation::{IndexDataType, OverAlignedVertexBuffer},
};
/// Processes events which happen during the pipeline execution
// FIXME (wasm-executor): handle results for messages below
pub trait PipelineProcessor: Downcast {
fn tile_finished(&mut self, _coords: &WorldTileCoords) {}
fn layer_unavailable(&mut self, _coords: &WorldTileCoords, _layer_name: &str) {}
fn tile_finished(&mut self, _coords: &WorldTileCoords) -> Result<(), Error> {
Ok(())
}
fn layer_unavailable(
&mut self,
_coords: &WorldTileCoords,
_layer_name: &str,
) -> Result<(), Error> {
Ok(())
}
fn layer_tesselation_finished(
&mut self,
_coords: &WorldTileCoords,
_buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
_feature_indices: Vec<u32>,
_layer_data: tile::Layer,
) {
) -> Result<(), Error> {
Ok(())
}
fn layer_indexing_finished(
&mut self,
_coords: &WorldTileCoords,
_geometries: Vec<IndexedGeometry<f64>>,
) {
) -> Result<(), Error> {
Ok(())
}
}

View File

@ -7,7 +7,7 @@ use crate::error::Error;
/// Async/await scheduler.
/// Can schedule a task from a future factory and a shared state.
pub trait Scheduler: 'static {
#[cfg(not(feature = "no-thread-safe-futures"))]
#[cfg(feature = "thread-safe-futures")]
fn schedule<T>(
&self,
future_factory: impl (FnOnce() -> T) + Send + 'static,
@ -15,7 +15,7 @@ pub trait Scheduler: 'static {
where
T: Future<Output = ()> + Send + 'static;
#[cfg(feature = "no-thread-safe-futures")]
#[cfg(not(feature = "thread-safe-futures"))]
fn schedule<T>(
&self,
future_factory: impl (FnOnce() -> T) + Send + 'static,
@ -31,6 +31,6 @@ impl Scheduler for NopScheduler {
where
T: Future<Output = ()> + 'static,
{
Err(Error::Schedule)
Err(Error::Scheduler)
}
}

View File

@ -12,9 +12,9 @@ pub type HTTPClientFactory<HC> = dyn Fn() -> HC;
/// [https://github.com/dtolnay/async-trait/blob/b70720c4c1cc0d810b7446efda44f81310ee7bf2/README.md#non-threadsafe-futures](https://github.com/dtolnay/async-trait/blob/b70720c4c1cc0d810b7446efda44f81310ee7bf2/README.md#non-threadsafe-futures)
///
/// Users of this library can decide whether futures from the HTTPClient are thread-safe or not via
/// the future "no-thread-safe-futures". Tokio futures are thread-safe.
#[cfg_attr(feature = "no-thread-safe-futures", async_trait(?Send))]
#[cfg_attr(not(feature = "no-thread-safe-futures"), async_trait)]
/// the future "thread-safe-futures". Tokio futures are thread-safe.
#[cfg_attr(not(feature = "thread-safe-futures"), async_trait(?Send))]
#[cfg_attr(feature = "thread-safe-futures", async_trait)]
pub trait HttpClient: Clone + Sync + Send + 'static {
async fn fetch(&self, url: &str) -> Result<Vec<u8>, Error>;
}
@ -32,25 +32,23 @@ where
/// Defines the different types of HTTP clients such as basic HTTP and Mbtiles.
/// More types might be coming such as S3 and other cloud http clients.
#[derive(Clone)]
pub enum SourceClient<HC>
pub struct SourceClient<HC>
where
HC: HttpClient,
{
Http(HttpSourceClient<HC>),
Mbtiles {
// TODO
},
http: HttpSourceClient<HC>, // TODO: mbtiles: Mbtiles
}
impl<HC> SourceClient<HC>
where
HC: HttpClient,
{
pub fn new(http: HttpSourceClient<HC>) -> Self {
Self { http }
}
pub async fn fetch(&self, coords: &WorldTileCoords) -> Result<Vec<u8>, Error> {
match self {
SourceClient::Http(client) => client.fetch(coords).await,
SourceClient::Mbtiles { .. } => unimplemented!(),
}
self.http.fetch(coords).await
}
}

View File

@ -45,6 +45,7 @@ impl Processable for IndexLayer {
) -> Self::Output {
let index = IndexProcessor::new();
// FIXME: Handle result
context
.processor_mut()
.layer_indexing_finished(&tile_request.coords, index.get_geometries());
@ -78,6 +79,7 @@ impl Processable for TessellateLayer {
let mut tessellator = ZeroTessellator::<IndexDataType>::default();
if let Err(e) = layer.process(&mut tessellator) {
// FIXME: Handle result
context
.processor_mut()
.layer_unavailable(coords, layer_name);
@ -89,12 +91,13 @@ impl Processable for TessellateLayer {
e
);
} else {
// FIXME: Handle result
context.processor_mut().layer_tesselation_finished(
coords,
tessellator.buffer.into(),
tessellator.feature_indices,
cloned_layer,
)
);
}
}
@ -105,6 +108,7 @@ impl Processable for TessellateLayer {
.collect::<HashSet<_>>();
for missing_layer in tile_request.layers.difference(&available_layers) {
// FIXME: Handle result
context
.processor_mut()
.layer_unavailable(coords, missing_layer);
@ -118,6 +122,7 @@ impl Processable for TessellateLayer {
tracing::info!("tile tessellated at {} finished", &tile_request.coords);
// FIXME: Handle result
context.processor_mut().tile_finished(&tile_request.coords);
(tile_request, tile)

View File

@ -11,6 +11,7 @@ use crate::{
};
/// A layer which is stored for future use.
#[derive(Clone)]
pub enum StoredLayer {
UnavailableLayer {
coords: WorldTileCoords,
@ -41,7 +42,7 @@ impl StoredLayer {
}
}
#[derive(Eq, PartialEq)]
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum TileStatus {
Pending,
Failed,
@ -49,18 +50,37 @@ pub enum TileStatus {
}
/// Stores multiple [StoredLayers](StoredLayer).
#[derive(Clone)]
pub struct StoredTile {
coords: WorldTileCoords,
layers: Vec<StoredLayer>,
status: TileStatus,
}
impl StoredTile {
pub fn new() -> Self {
pub fn pending(coords: WorldTileCoords) -> Self {
Self {
coords,
layers: vec![],
status: TileStatus::Pending,
}
}
pub fn success(coords: WorldTileCoords, layers: Vec<StoredLayer>) -> Self {
Self {
coords,
layers,
status: TileStatus::Success,
}
}
pub fn failed(coords: WorldTileCoords) -> Self {
Self {
coords,
layers: vec![],
status: TileStatus::Failed,
}
}
}
/// Stores and provides access to a quad tree of cached tiles with world tile coords.
@ -82,10 +102,10 @@ impl TileRepository {
/// Inserts a tessellated layer into the quad tree at its world tile coords.
/// If the space is vacant, the tessellated layer is inserted into a new
/// [crate::io::tile_repository::CachedTile].
/// [crate::io::tile_repository::StoredLayer].
/// If the space is occupied, the tessellated layer is added to the current
/// [crate::io::tile_repository::CachedTile].
pub fn put_tessellated_layer(&mut self, layer: StoredLayer) {
/// [crate::io::tile_repository::StoredLayer].
pub fn put_layer(&mut self, layer: StoredLayer) {
if let Some(entry) = layer
.get_coords()
.build_quad_key()
@ -102,9 +122,15 @@ impl TileRepository {
}
}
pub fn put_tile(&mut self, tile: StoredTile) {
if let Some(key) = tile.coords.build_quad_key() {
self.tree.insert(key, tile);
}
}
/// Returns the list of tessellated layers at the given world tile coords. None if tile is
/// missing from the cache.
pub fn iter_tessellated_layers_at(
pub fn iter_layers_at(
&self,
coords: &WorldTileCoords,
) -> Option<impl Iterator<Item = &StoredLayer> + '_> {
@ -115,11 +141,11 @@ impl TileRepository {
}
/// Create a new tile.
pub fn create_tile(&mut self, coords: &WorldTileCoords) -> bool {
pub fn create_tile(&mut self, coords: WorldTileCoords) -> bool {
if let Some(entry) = coords.build_quad_key().map(|key| self.tree.entry(key)) {
match entry {
btree_map::Entry::Vacant(entry) => {
entry.insert(StoredTile::new());
entry.insert(StoredTile::pending(coords));
}
_ => {}
}
@ -128,14 +154,14 @@ impl TileRepository {
}
/// Checks if a layer has been fetched.
pub fn needs_fetching(&self, coords: &WorldTileCoords) -> bool {
pub fn has_tile(&self, coords: &WorldTileCoords) -> bool {
if let Some(_) = coords.build_quad_key().and_then(|key| self.tree.get(&key)) {
return false;
}
true
}
pub fn success(&mut self, coords: &WorldTileCoords) {
pub fn mark_tile_succeeded(&mut self, coords: &WorldTileCoords) {
if let Some(cached_tile) = coords
.build_quad_key()
.and_then(|key| self.tree.get_mut(&key))
@ -145,7 +171,7 @@ impl TileRepository {
}
/// Checks if a layer has been fetched.
pub fn fail(&mut self, coords: &WorldTileCoords) {
pub fn mark_tile_failed(&mut self, coords: &WorldTileCoords) {
if let Some(cached_tile) = coords
.build_quad_key()
.and_then(|key| self.tree.get_mut(&key))
@ -153,42 +179,4 @@ impl TileRepository {
cached_tile.status = TileStatus::Failed;
}
}
/// Removes all the cached tessellate layers that are not contained within the given
/// layers hashset.
pub fn retain_missing_layer_names(
&self,
coords: &WorldTileCoords,
layers: &mut HashSet<String>,
) {
if let Some(cached_tile) = coords.build_quad_key().and_then(|key| self.tree.get(&key)) {
let tessellated_set: HashSet<String> = cached_tile
.layers
.iter()
.map(|tessellated_layer| tessellated_layer.layer_name().to_string())
.collect();
layers.retain(|layer| !tessellated_set.contains(layer));
}
}
/// Checks if a layer is missing from the given layers set at the given coords.
pub fn is_layers_missing(&self, coords: &WorldTileCoords, layers: &HashSet<String>) -> bool {
if let Some(cached_tile) = coords.build_quad_key().and_then(|key| self.tree.get(&key)) {
let tessellated_set: HashSet<&str> = cached_tile
.layers
.iter()
.map(|tessellated_layer| tessellated_layer.layer_name())
.collect();
for layer in layers {
if !tessellated_set.contains(layer.as_str()) {
return true;
}
}
return false;
}
true
}
}

View File

@ -66,7 +66,7 @@ pub struct DefaultTessellatedLayer {
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
pub layer_data: Layer, // FIXME (perf): Introduce a better structure for this
}
impl TessellatedLayer for DefaultTessellatedLayer {

85
maplibre/src/kernel.rs Normal file
View File

@ -0,0 +1,85 @@
use crate::{
environment::Environment,
io::source_client::{HttpSourceClient, SourceClient},
};
/// Holds references to core constructs of maplibre. Based on the compile-time initialization
/// different implementations for handling windows, asynchronous work, or data sources are provided
/// through a [`Kernel`].
///
/// An [`Environment`] defines the types which are used.
///
/// A Kernel lives as long as a [Map](crate::map::Map) usually. It is shared through out various
/// components of the maplibre library.
pub struct Kernel<E: Environment> {
map_window_config: E::MapWindowConfig,
apc: E::AsyncProcedureCall,
scheduler: E::Scheduler,
source_client: SourceClient<E::HttpClient>,
}
impl<E: Environment> Kernel<E> {
pub fn map_window_config(&self) -> &E::MapWindowConfig {
&self.map_window_config
}
pub fn apc(&self) -> &E::AsyncProcedureCall {
&self.apc
}
pub fn scheduler(&self) -> &E::Scheduler {
&self.scheduler
}
pub fn source_client(&self) -> &SourceClient<E::HttpClient> {
&self.source_client
}
}
/// A convenient builder for [Kernels](Kernel).
pub struct KernelBuilder<E: Environment> {
map_window_config: Option<E::MapWindowConfig>,
apc: Option<E::AsyncProcedureCall>,
scheduler: Option<E::Scheduler>,
http_client: Option<E::HttpClient>,
}
impl<E: Environment> KernelBuilder<E> {
pub fn new() -> Self {
Self {
scheduler: None,
apc: None,
http_client: None,
map_window_config: None,
}
}
pub fn with_map_window_config(mut self, map_window_config: E::MapWindowConfig) -> Self {
self.map_window_config = Some(map_window_config);
self
}
pub fn with_scheduler(mut self, scheduler: E::Scheduler) -> Self {
self.scheduler = Some(scheduler);
self
}
pub fn with_apc(mut self, apc: E::AsyncProcedureCall) -> Self {
self.apc = Some(apc);
self
}
pub fn with_http_client(mut self, http_client: E::HttpClient) -> Self {
self.http_client = Some(http_client);
self
}
pub fn build(self) -> Kernel<E> {
Kernel {
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
apc: self.apc.unwrap(), // TODO: Remove unwrap
source_client: SourceClient::new(HttpSourceClient::new(self.http_client.unwrap())), // TODO: Remove unwrap
map_window_config: self.map_window_config.unwrap(), // TODO: Remove unwrap
}
}
}

View File

@ -16,23 +16,8 @@
//! maplibre = "0.0.2"
//! ```
use std::{
borrow::{Borrow, BorrowMut},
cell::RefCell,
rc::Rc,
};
use crate::{
environment::Environment,
io::{scheduler::Scheduler, source_client::HttpClient},
map_schedule::InteractiveMapSchedule,
render::{
settings::{RendererSettings, WgpuSettings},
RenderState, Renderer,
},
style::Style,
window::{EventLoop, HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
};
// Internal modules
pub(crate) mod tessellation;
pub mod context;
pub mod coords;
@ -40,10 +25,8 @@ pub mod error;
#[cfg(feature = "headless")]
pub mod headless;
pub mod io;
// Exposed because of input handlers in maplibre-winit
pub mod map_schedule;
pub mod platform;
// Exposed because of camera
// TODO: Exposed because of camera
pub mod render;
pub mod style;
pub mod util;
@ -54,209 +37,15 @@ pub mod schedule;
// Exposed because of SharedThreadState
pub mod stages;
pub mod environment;
// Used for benchmarking
pub mod benchmarking;
// Internal modules
pub(crate) mod tessellation;
pub mod environment;
pub mod event_loop;
pub mod kernel;
pub mod map;
pub mod world;
// Export tile format
pub use geozero::mvt::tile;
/// The [`Map`] defines the public interface of the map renderer.
// DO NOT IMPLEMENT INTERNALS ON THIS STRUCT.
pub struct Map<E: Environment> {
// FIXME (wasm-executor): Avoid RefCell, change ownership model!
map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>,
window: RefCell<Option<<E::MapWindowConfig as MapWindowConfig>::MapWindow>>,
}
impl<E: Environment> Map<E>
where
<E::MapWindowConfig as MapWindowConfig>::MapWindow: EventLoop<E>,
{
/// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
pub fn run(&self) {
self.run_with_optionally_max_frames(None);
}
/// Starts the [`crate::map_schedule::MapState`] Runnable with the configured event loop.
///
/// # Arguments
///
/// * `max_frames` - Maximum number of frames per second.
pub fn run_with_max_frames(&self, max_frames: u64) {
self.run_with_optionally_max_frames(Some(max_frames));
}
/// Starts the MapState Runnable with the configured event loop.
///
/// # Arguments
///
/// * `max_frames` - Optional maximum number of frames per second.
pub fn run_with_optionally_max_frames(&self, max_frames: Option<u64>) {
self.window
.borrow_mut()
.take()
.unwrap() // FIXME (wasm-executor): Remove unwrap
.run(self.map_schedule.clone(), max_frames);
}
pub fn map_schedule(&self) -> Rc<RefCell<InteractiveMapSchedule<E>>> {
self.map_schedule.clone()
}
/* pub fn map_schedule_mut(&mut self) -> &mut InteractiveMapSchedule<E> {
&mut self.map_schedule
}*/
}
/// Stores the map configuration before the map's state has been fully initialized.
pub struct UninitializedMap<E: Environment> {
scheduler: E::Scheduler,
apc: E::AsyncProcedureCall,
http_client: E::HttpClient,
style: Style,
wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings,
map_window_config: E::MapWindowConfig,
}
impl<E: Environment> UninitializedMap<E>
where
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
/// Initializes the whole rendering pipeline for the given configuration.
/// Returns the initialized map, ready to be run.
pub async fn initialize(self) -> Map<E> {
let window = self.map_window_config.create();
let window_size = window.size();
#[cfg(target_os = "android")]
let renderer = None;
#[cfg(not(target_os = "android"))]
let renderer = Renderer::initialize(
&window,
self.wgpu_settings.clone(),
self.renderer_settings.clone(),
)
.await
.ok();
Map {
map_schedule: Rc::new(RefCell::new(InteractiveMapSchedule::new(
self.map_window_config,
window_size,
renderer,
self.scheduler,
self.apc,
self.http_client,
self.style,
self.wgpu_settings,
self.renderer_settings,
))),
window: RefCell::new(Some(window)),
}
}
}
#[cfg(feature = "headless")]
impl<E: Environment> UninitializedMap<E> {
pub async fn initialize_headless(self) -> headless::HeadlessMap<E> {
let window = self.map_window_config.create();
let window_size = window.size();
let renderer = Renderer::initialize_headless(
&window,
self.wgpu_settings.clone(),
self.renderer_settings.clone(),
)
.await
.expect("Failed to initialize renderer");
headless::HeadlessMap {
map_schedule: headless::HeadlessMapSchedule::new(
self.map_window_config,
window_size,
renderer,
self.scheduler,
self.http_client,
self.style,
),
window,
}
}
}
pub struct MapBuilder<E: Environment> {
scheduler: Option<E::Scheduler>,
apc: Option<E::AsyncProcedureCall>,
http_client: Option<E::HttpClient>,
style: Option<Style>,
map_window_config: Option<E::MapWindowConfig>,
wgpu_settings: Option<WgpuSettings>,
renderer_settings: Option<RendererSettings>,
}
impl<E: Environment> MapBuilder<E> {
pub fn new() -> Self {
Self {
scheduler: None,
apc: None,
http_client: None,
style: None,
map_window_config: None,
wgpu_settings: None,
renderer_settings: None,
}
}
pub fn with_map_window_config(mut self, map_window_config: E::MapWindowConfig) -> Self {
self.map_window_config = Some(map_window_config);
self
}
pub fn with_renderer_settings(mut self, renderer_settings: RendererSettings) -> Self {
self.renderer_settings = Some(renderer_settings);
self
}
pub fn with_wgpu_settings(mut self, wgpu_settings: WgpuSettings) -> Self {
self.wgpu_settings = Some(wgpu_settings);
self
}
pub fn with_scheduler(mut self, scheduler: E::Scheduler) -> Self {
self.scheduler = Some(scheduler);
self
}
pub fn with_apc(mut self, apc: E::AsyncProcedureCall) -> Self {
self.apc = Some(apc);
self
}
pub fn with_http_client(mut self, http_client: E::HttpClient) -> Self {
self.http_client = Some(http_client);
self
}
pub fn with_style(mut self, style: Style) -> Self {
self.style = Some(style);
self
}
/// Builds the UninitializedMap with the given configuration.
pub fn build(self) -> UninitializedMap<E> {
UninitializedMap {
scheduler: self.scheduler.unwrap(), // TODO: Remove unwrap
apc: self.apc.unwrap(), // TODO: Remove unwrap
http_client: self.http_client.unwrap(), // TODO: Remove unwrap
style: self.style.unwrap_or_default(),
wgpu_settings: self.wgpu_settings.unwrap_or_default(),
renderer_settings: self.renderer_settings.unwrap_or_default(),
map_window_config: self.map_window_config.unwrap(), // TODO: Remove unwrap
}
}
}

149
maplibre/src/map.rs Normal file
View File

@ -0,0 +1,149 @@
use std::rc::Rc;
use crate::{
context::MapContext,
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
environment::Environment,
error::Error,
kernel::Kernel,
render::{
builder::{
InitializationResult, InitializedRenderer, RendererBuilder, UninitializedRenderer,
},
create_default_render_graph, register_default_render_stages,
settings::{RendererSettings, WgpuSettings},
Renderer,
},
schedule::{Schedule, Stage},
stages::register_stages,
style::Style,
window::{HeadedMapWindow, MapWindow, MapWindowConfig, WindowSize},
world::World,
};
pub enum MapContextState {
Ready(MapContext),
Pending { style: Style },
}
pub struct Map<E: Environment> {
kernel: Rc<Kernel<E>>,
schedule: Schedule,
map_context: MapContextState,
window: <E::MapWindowConfig as MapWindowConfig>::MapWindow,
}
impl<E: Environment> Map<E>
where
<<E as Environment>::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
pub fn new(style: Style, kernel: Kernel<E>) -> Result<Self, Error> {
let mut schedule = Schedule::default();
let graph = create_default_render_graph().unwrap(); // TODO: Remove unwrap
register_default_render_stages(graph, &mut schedule);
let kernel = Rc::new(kernel);
register_stages::<E>(&mut schedule, kernel.clone());
let mut window = kernel.map_window_config().create();
let map = Self {
kernel,
map_context: MapContextState::Pending { style },
schedule,
window,
};
Ok(map)
}
pub async fn initialize_renderer(
&mut self,
render_builder: RendererBuilder,
) -> Result<(), Error> {
let result = render_builder
.build()
.initialize_renderer::<E::MapWindowConfig>(&self.window)
.await
.expect("Failed to initialize renderer");
match &mut self.map_context {
MapContextState::Ready(_) => Err(Error::Generic("Renderer is already set".into())),
MapContextState::Pending { style } => {
let window_size = self.window.size();
let center = style.center.unwrap_or_default();
let world = World::new_at(
window_size,
LatLon::new(center[0], center[1]),
style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default(),
cgmath::Deg::<f64>(style.pitch.unwrap_or_default()),
);
match result {
InitializationResult::Initialized(InitializedRenderer { renderer, .. }) => {
*&mut self.map_context = MapContextState::Ready(MapContext {
world,
style: std::mem::take(style),
renderer,
});
}
InitializationResult::Uninizalized(UninitializedRenderer { .. }) => {}
_ => panic!("Rendering context gone"),
};
Ok(())
}
}
}
pub fn window_mut(&mut self) -> &mut <E::MapWindowConfig as MapWindowConfig>::MapWindow {
&mut self.window
}
pub fn window(&self) -> &<E::MapWindowConfig as MapWindowConfig>::MapWindow {
&self.window
}
pub fn has_renderer(&self) -> bool {
match &self.map_context {
MapContextState::Ready(_) => true,
MapContextState::Pending { .. } => false,
}
}
#[tracing::instrument(name = "update_and_redraw", skip_all)]
pub fn run_schedule(&mut self) -> Result<(), Error> {
match &mut self.map_context {
MapContextState::Ready(map_context) => {
self.schedule.run(map_context);
Ok(())
}
MapContextState::Pending { .. } => {
Err(Error::Generic("Renderer is already set".into()))
}
}
}
pub fn context(&self) -> Result<&MapContext, Error> {
match &self.map_context {
MapContextState::Ready(map_context) => Ok(map_context),
MapContextState::Pending { .. } => {
Err(Error::Generic("Renderer is already set".into()))
}
}
}
pub fn context_mut(&mut self) -> Result<&mut MapContext, Error> {
match &mut self.map_context {
MapContextState::Ready(map_context) => Ok(map_context),
MapContextState::Pending { .. } => {
Err(Error::Generic("Renderer is already set".into()))
}
}
}
pub fn kernel(&self) -> &Rc<Kernel<E>> {
&self.kernel
}
}

View File

@ -1,207 +1,26 @@
use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc};
/* pub fn resume(&mut self, window: &<E::MapWindowConfig as MapWindowConfig>::MapWindow) {
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
let renderer = &mut map_context.renderer;
renderer.state.recreate_surface(window, &renderer.instance);
}
}*/
use crate::{
context::{MapContext, ViewState},
coords::{LatLon, WorldCoords, Zoom, TILE_SIZE},
error::Error,
io::{
scheduler::Scheduler,
source_client::{HttpClient, HttpSourceClient},
tile_repository::TileRepository,
},
render::{create_default_render_graph, register_default_render_stages},
schedule::{Schedule, Stage},
stages::register_stages,
style::Style,
Environment, HeadedMapWindow, MapWindowConfig, Renderer, RendererSettings, WgpuSettings,
WindowSize,
};
/// Stores the state of the map, dispatches tile fetching and caching, tessellation and drawing.
pub struct InteractiveMapSchedule<E: Environment> {
map_window_config: E::MapWindowConfig,
// FIXME (wasm-executor): avoid RefCell, change ownership model
pub apc: Rc<RefCell<E::AsyncProcedureCall>>,
map_context: EventuallyMapContext,
schedule: Schedule,
suspended: bool,
}
impl<E: Environment> InteractiveMapSchedule<E> {
pub fn new(
map_window_config: E::MapWindowConfig,
window_size: WindowSize,
renderer: Option<Renderer>,
scheduler: E::Scheduler, // TODO: unused
apc: E::AsyncProcedureCall,
http_client: E::HttpClient,
style: Style,
wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings,
) -> Self {
let zoom = style.zoom.map(|zoom| Zoom::new(zoom)).unwrap_or_default();
let position = style
.center
.map(|center| WorldCoords::from_lat_lon(LatLon::new(center[0], center[1]), zoom))
.unwrap_or_default();
let pitch = style.pitch.unwrap_or_default();
let view_state = ViewState::new(&window_size, position, zoom, pitch, cgmath::Deg(110.0));
let tile_repository = TileRepository::new();
let mut schedule = Schedule::default();
let apc = Rc::new(RefCell::new(apc));
let http_source_client: HttpSourceClient<E::HttpClient> =
HttpSourceClient::new(http_client);
register_stages::<E>(&mut schedule, http_source_client, apc.clone());
let graph = create_default_render_graph().unwrap(); // TODO: Remove unwrap
register_default_render_stages(graph, &mut schedule);
Self {
apc,
map_window_config,
map_context: match renderer {
None => EventuallyMapContext::Premature(PrematureMapContext {
view_state,
style,
tile_repository,
wgpu_settings,
renderer_settings,
}),
Some(renderer) => EventuallyMapContext::Full(MapContext {
view_state,
style,
tile_repository,
renderer,
}),
},
schedule,
suspended: false,
/* pub async fn late_init(&mut self) -> bool {
match &self.map_context {
EventuallyMapContext::Full(_) => false,
EventuallyMapContext::Uninizalized(PrematureMapContext {
wgpu_settings,
renderer_settings,
..
}) => {
let window = self.map_window_config.create();
let renderer =
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
.await
.unwrap(); // TODO: Remove unwrap
self.map_context.make_full(renderer);
true
}
EventuallyMapContext::_Uninitialized => false,
}
#[tracing::instrument(name = "update_and_redraw", skip_all)]
pub fn update_and_redraw(&mut self) -> Result<(), Error> {
if self.suspended {
return Ok(());
}
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
self.schedule.run(map_context)
}
Ok(())
}
pub fn resize(&mut self, width: u32, height: u32) {
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
let view_state = &mut map_context.view_state;
view_state.perspective.resize(width, height);
view_state.camera.resize(width, height);
map_context.renderer.resize(width, height)
}
}
pub fn is_initialized(&self) -> bool {
match &self.map_context {
EventuallyMapContext::Full(_) => true,
_ => false,
}
}
pub fn view_state_mut(&mut self) -> &mut ViewState {
match &mut self.map_context {
EventuallyMapContext::Full(MapContext { view_state, .. }) => view_state,
EventuallyMapContext::Premature(PrematureMapContext { view_state, .. }) => view_state,
_ => panic!("should not happen"),
}
}
pub fn apc(&self) -> &Rc<RefCell<E::AsyncProcedureCall>> {
&self.apc
}
}
impl<E: Environment> InteractiveMapSchedule<E>
where
<E::MapWindowConfig as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
pub fn suspend(&mut self) {
self.suspended = true;
}
pub fn resume(&mut self, window: &<E::MapWindowConfig as MapWindowConfig>::MapWindow) {
if let EventuallyMapContext::Full(map_context) = &mut self.map_context {
let renderer = &mut map_context.renderer;
renderer.state.recreate_surface(window, &renderer.instance);
self.suspended = false;
}
}
pub async fn late_init(&mut self) -> bool {
match &self.map_context {
EventuallyMapContext::Full(_) => false,
EventuallyMapContext::Premature(PrematureMapContext {
wgpu_settings,
renderer_settings,
..
}) => {
let window = self.map_window_config.create();
let renderer =
Renderer::initialize(&window, wgpu_settings.clone(), renderer_settings.clone())
.await
.unwrap(); // TODO: Remove unwrap
self.map_context.make_full(renderer);
true
}
EventuallyMapContext::_Uninitialized => false,
}
}
}
pub struct PrematureMapContext {
view_state: ViewState,
style: Style,
tile_repository: TileRepository,
wgpu_settings: WgpuSettings,
renderer_settings: RendererSettings,
}
pub enum EventuallyMapContext {
Full(MapContext),
Premature(PrematureMapContext),
_Uninitialized,
}
impl EventuallyMapContext {
pub fn make_full(&mut self, renderer: Renderer) {
let context = mem::replace(self, EventuallyMapContext::_Uninitialized);
match context {
EventuallyMapContext::Full(_) => {}
EventuallyMapContext::Premature(PrematureMapContext {
view_state,
style,
tile_repository,
..
}) => {
*self = EventuallyMapContext::Full(MapContext {
view_state,
style,
tile_repository,
renderer,
});
}
EventuallyMapContext::_Uninitialized => {}
}
}
}
}*/

View File

@ -3,7 +3,7 @@ use reqwest::{Client, StatusCode};
use reqwest_middleware::ClientWithMiddleware;
use reqwest_middleware_cache::{managers::CACacheManager, Cache, CacheMode};
use crate::{error::Error, HttpClient};
use crate::{error::Error, io::source_client::HttpClient};
#[derive(Clone)]
pub struct ReqwestHttpClient {
@ -23,6 +23,7 @@ impl From<reqwest_middleware::Error> for Error {
impl ReqwestHttpClient {
/// cache_path: Under which path should we cache requests.
// TODO: Use Into<Path> instead of String
pub fn new(cache_path: Option<String>) -> Self {
let mut builder = reqwest_middleware::ClientBuilder::new(Client::new());

View File

@ -1,6 +1,6 @@
use std::future::Future;
use crate::{error::Error, Scheduler};
use crate::{error::Error, io::scheduler::Scheduler};
/// Multi-threading with Tokio.
pub struct TokioScheduler;

View File

@ -0,0 +1,126 @@
use std::marker::PhantomData;
use crate::{
environment::Environment,
error::Error,
kernel::Kernel,
render::{
settings::{RendererSettings, WgpuSettings},
Renderer,
},
style::Style,
window::{HeadedMapWindow, MapWindow, MapWindowConfig},
};
pub struct RendererBuilder {
wgpu_settings: Option<WgpuSettings>,
renderer_settings: Option<RendererSettings>,
}
impl RendererBuilder {
pub fn new() -> Self {
Self {
wgpu_settings: None,
renderer_settings: None,
}
}
pub fn with_renderer_settings(mut self, renderer_settings: RendererSettings) -> Self {
self.renderer_settings = Some(renderer_settings);
self
}
pub fn with_wgpu_settings(mut self, wgpu_settings: WgpuSettings) -> Self {
self.wgpu_settings = Some(wgpu_settings);
self
}
pub fn build(self) -> UninitializedRenderer {
UninitializedRenderer {
wgpu_settings: self.wgpu_settings.unwrap_or_default(),
renderer_settings: self.renderer_settings.unwrap_or_default(),
}
}
}
pub enum InitializationResult {
Initialized(InitializedRenderer),
Uninizalized(UninitializedRenderer),
Gone,
}
impl Default for InitializationResult {
fn default() -> Self {
Self::Gone
}
}
impl InitializationResult {
pub fn unwarp_renderer(self) -> InitializedRenderer {
match self {
InitializationResult::Initialized(renderer) => renderer,
InitializationResult::Uninizalized(_) => panic!("Renderer is not initialized"),
InitializationResult::Gone => panic!("Initialization context is gone"),
}
}
pub fn into_option(self) -> Option<Renderer> {
match self {
InitializationResult::Initialized(InitializedRenderer { renderer, .. }) => {
Some(renderer)
}
InitializationResult::Uninizalized(_) => None,
InitializationResult::Gone => panic!("Initialization context is gone"),
}
}
}
pub struct UninitializedRenderer {
pub wgpu_settings: WgpuSettings,
pub renderer_settings: RendererSettings,
}
impl UninitializedRenderer {
/// Initializes the whole rendering pipeline for the given configuration.
/// Returns the initialized map, ready to be run.
pub async fn initialize_renderer<MWC>(
self,
existing_window: &MWC::MapWindow,
) -> Result<InitializationResult, Error>
where
MWC: MapWindowConfig,
<MWC as MapWindowConfig>::MapWindow: HeadedMapWindow,
{
let renderer = Renderer::initialize(
existing_window,
self.wgpu_settings.clone(),
self.renderer_settings.clone(),
)
.await?;
Ok(InitializationResult::Initialized(InitializedRenderer {
renderer,
}))
}
}
#[cfg(feature = "headless")]
impl UninitializedRenderer {
pub(crate) async fn initialize_headless<MWC>(
self,
existing_window: &MWC::MapWindow,
) -> Result<Renderer, Error>
where
MWC: MapWindowConfig,
{
Ok(Renderer::initialize_headless(
existing_window,
self.wgpu_settings.clone(),
self.renderer_settings.clone(),
)
.await?)
}
}
pub struct InitializedRenderer {
pub renderer: Renderer,
}

View File

@ -1,6 +1,6 @@
//! Main camera
use cgmath::{prelude::*, AbsDiffEq, Matrix4, Point2, Point3, Vector2, Vector3, Vector4};
use cgmath::{prelude::*, AbsDiffEq, Matrix4, Point2, Point3, Rad, Vector2, Vector3, Vector4};
use crate::util::{
math::{bounds_from_points, Aabb2, Aabb3, Plane},
@ -68,12 +68,12 @@ impl ModelViewProjection {
#[derive(Debug, Clone)]
pub struct Camera {
pub position: Point3<f64>, // The z axis never changes, the zoom is used instead
pub yaw: cgmath::Rad<f64>,
pub pitch: cgmath::Rad<f64>,
position: Point3<f64>, // The z axis never changes, the zoom is used instead
yaw: cgmath::Rad<f64>,
pitch: cgmath::Rad<f64>,
pub width: f64,
pub height: f64,
width: f64,
height: f64,
}
impl SignificantlyDifferent for Camera {
@ -103,6 +103,14 @@ impl Camera {
}
}
pub fn move_to(&mut self, point: Point3<f64>) {
self.position = point;
}
pub fn move_relative(&mut self, delta: Vector3<f64>) {
self.position += delta;
}
pub fn resize(&mut self, width: u32, height: u32) {
self.width = width as f64;
self.height = height as f64;
@ -347,6 +355,26 @@ impl Camera {
Point2::new(max_x, max_y),
))
}
pub fn position(&self) -> Point3<f64> {
self.position
}
pub fn yaw(&self) -> cgmath::Rad<f64> {
self.yaw
}
pub fn yaw_self<P: Into<Rad<f64>>>(&mut self, delta: P) {
self.yaw += delta.into();
}
pub fn pitch(&self) -> cgmath::Rad<f64> {
self.pitch
}
pub fn pitch_self<P: Into<Rad<f64>>>(&mut self, delta: P) {
self.pitch += delta.into();
}
}
pub struct Perspective {

View File

@ -0,0 +1,50 @@
use std::fmt;
use crate::{error::Error, render::graph::RenderGraphError};
#[derive(Debug)]
pub enum RenderError {
Surface(wgpu::SurfaceError),
Graph(RenderGraphError),
Device(wgpu::RequestDeviceError),
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RenderError::Surface(e) => write!(f, "{}", e),
RenderError::Graph(e) => write!(f, "{:?}", e),
RenderError::Device(e) => write!(f, "{}", e),
}
}
}
impl RenderError {
pub fn should_exit(&self) -> bool {
match self {
RenderError::Surface(e) => match e {
wgpu::SurfaceError::OutOfMemory => true,
_ => false,
},
_ => true,
}
}
}
impl From<RenderGraphError> for RenderError {
fn from(e: RenderGraphError) -> Self {
RenderError::Graph(e)
}
}
impl From<wgpu::SurfaceError> for Error {
fn from(e: wgpu::SurfaceError) -> Self {
Error::Render(RenderError::Surface(e))
}
}
impl From<wgpu::RequestDeviceError> for Error {
fn from(e: wgpu::RequestDeviceError) -> Self {
Error::Render(RenderError::Device(e))
}
}

View File

@ -574,8 +574,8 @@ mod tests {
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
SlotInfo,
};
use crate::{
render::graph::{RenderContext, SlotType},
use crate::render::{
graph::{RenderContext, SlotType},
RenderState,
};

View File

@ -32,7 +32,6 @@ use crate::{
tile_view_pattern::{TileInView, TileShape, TileViewPattern},
},
tessellation::IndexDataType,
HeadedMapWindow, MapWindow,
};
pub mod graph;
@ -49,19 +48,24 @@ mod tile_pipeline;
mod tile_view_pattern;
// Public API
pub mod builder;
pub mod camera;
pub mod error;
pub mod eventually;
pub mod settings;
pub use shaders::ShaderVertex;
pub use stages::register_default_render_stages;
use crate::render::{
graph::{EmptyNode, RenderGraph, RenderGraphError},
main_pass::{MainPassDriverNode, MainPassNode},
use crate::{
render::{
graph::{EmptyNode, RenderGraph, RenderGraphError},
main_pass::{MainPassDriverNode, MainPassNode},
},
window::{HeadedMapWindow, MapWindow},
};
pub const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
const INDEX_FORMAT: wgpu::IndexFormat = wgpu::IndexFormat::Uint32; // Must match IndexDataType
pub struct RenderState {
render_target: Eventually<TextureView>,
@ -411,7 +415,10 @@ impl Renderer {
#[cfg(test)]
mod tests {
use crate::{MapWindow, MapWindowConfig, WindowSize};
use crate::{
render::{settings::RendererSettings, RenderState},
window::{MapWindow, MapWindowConfig, WindowSize},
};
pub struct HeadlessMapWindowConfig {
size: WindowSize,
@ -440,9 +447,8 @@ mod tests {
async fn test_render() {
use log::LevelFilter;
use crate::{
render::{graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface},
RenderState, RendererSettings,
use crate::render::{
graph::RenderGraph, graph_runner::RenderGraphRunner, resource::Surface,
};
let _ = env_logger::builder()

View File

@ -1,15 +1,12 @@
//! Specifies the instructions which are going to be sent to the GPU. Render commands can be concatenated
//! into a new render command which executes multiple instruction sets.
use crate::{
render::{
eventually::Eventually::Initialized,
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
resource::{Globals, IndexEntry, TrackedRenderPass},
tile_view_pattern::{TileInView, TileShape},
INDEX_FORMAT,
},
RenderState,
use crate::render::{
eventually::Eventually::Initialized,
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
resource::{Globals, IndexEntry, TrackedRenderPass},
tile_view_pattern::{TileInView, TileShape},
RenderState, INDEX_FORMAT,
};
impl PhaseItem for TileInView {

View File

@ -1,4 +1,4 @@
use crate::{render::resource::TrackedRenderPass, RenderState};
use crate::render::{resource::TrackedRenderPass, RenderState};
/// A draw function which is used to draw a specific [`PhaseItem`].
///

View File

@ -7,8 +7,7 @@ use wgpu::CompositeAlphaMode;
use crate::{
render::{eventually::HasChanged, resource::texture::TextureView, settings::RendererSettings},
window::HeadedMapWindow,
MapWindow, WindowSize,
window::{HeadedMapWindow, MapWindow, WindowSize},
};
pub struct BufferDimensions {
@ -53,7 +52,7 @@ impl WindowHead {
where
MW: MapWindow + HeadedMapWindow,
{
self.surface = unsafe { instance.create_surface(window.inner()) };
self.surface = unsafe { instance.create_surface(window.raw()) };
}
pub fn surface(&self) -> &wgpu::Surface {
&self.surface
@ -144,7 +143,7 @@ impl Surface {
present_mode: wgpu::PresentMode::Fifo, // VSync
};
let surface = unsafe { instance.create_surface(window.inner()) };
let surface = unsafe { instance.create_surface(window.raw()) };
Self {
size,

View File

@ -1,8 +1,11 @@
//! Extracts data from the current state.
use crate::{
context::MapContext, coords::ViewRegion, render::eventually::Eventually::Initialized,
schedule::Stage, RenderState, Renderer,
context::MapContext,
coords::ViewRegion,
render::{eventually::Eventually::Initialized, RenderState, Renderer},
schedule::Stage,
world::World,
};
#[derive(Default)]
@ -12,7 +15,7 @@ impl Stage for ExtractStage {
fn run(
&mut self,
MapContext {
view_state,
world: World { view_state, .. },
renderer:
Renderer {
state:

View File

@ -6,9 +6,9 @@ use crate::{
context::MapContext,
render::{
eventually::Eventually::Initialized, graph::RenderGraph, graph_runner::RenderGraphRunner,
Renderer,
},
schedule::Stage,
Renderer,
};
/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.

View File

@ -1,6 +1,10 @@
//! Sorts items of the [RenderPhases](RenderPhase).
use crate::{context::MapContext, render::render_phase::RenderPhase, schedule::Stage, Renderer};
use crate::{
context::MapContext,
render::{render_phase::RenderPhase, Renderer},
schedule::Stage,
};
#[derive(Default)]
pub struct PhaseSortStage;

View File

@ -4,9 +4,9 @@ use crate::{
context::MapContext,
render::{
eventually::Eventually::Initialized, resource::IndexEntry, tile_view_pattern::TileInView,
RenderState, Renderer,
},
schedule::Stage,
RenderState, Renderer,
};
#[derive(Default)]
@ -17,7 +17,6 @@ impl Stage for QueueStage {
fn run(
&mut self,
MapContext {
view_state: _,
renderer:
Renderer {
state:

View File

@ -10,9 +10,9 @@ use crate::{
shaders::{Shader, ShaderTileMetadata},
tile_pipeline::TilePipeline,
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_SIZE},
Renderer,
},
schedule::Stage,
Renderer,
};
#[derive(Default)]

View File

@ -10,9 +10,11 @@ use crate::{
camera::ViewProjection,
eventually::Eventually::Initialized,
shaders::{ShaderCamera, ShaderFeatureStyle, ShaderGlobals, ShaderLayerMetadata, Vec4f32},
RenderState, Renderer,
},
schedule::Stage,
RenderState, Renderer, Style,
style::Style,
world::World,
};
#[derive(Default)]
@ -23,9 +25,12 @@ impl Stage for UploadStage {
fn run(
&mut self,
MapContext {
view_state,
world:
World {
tile_repository,
view_state,
},
style,
tile_repository,
renderer: Renderer { queue, state, .. },
..
}: &mut MapContext,
@ -40,8 +45,8 @@ impl Stage for UploadStage {
bytemuck::cast_slice(&[ShaderGlobals::new(ShaderCamera::new(
view_proj.downcast().into(),
view_state
.camera
.position
.camera()
.position()
.to_homogeneous()
.cast::<f32>()
.unwrap() // TODO: Remove unwrap
@ -159,9 +164,8 @@ impl UploadStage {
let loaded_layers = buffer_pool
.get_loaded_layers_at(&world_coords)
.unwrap_or_default();
if let Some(available_layers) = tile_repository
.iter_tessellated_layers_at(&world_coords)
.map(|layers| {
if let Some(available_layers) =
tile_repository.iter_layers_at(&world_coords).map(|layers| {
layers
.filter(|result| !loaded_layers.contains(&result.layer_name()))
.collect::<Vec<_>>()

View File

@ -6,10 +6,9 @@ use crate::{
platform::MIN_WEBGL_BUFFER_SIZE,
render::{
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
settings::Msaa,
settings::{Msaa, RendererSettings},
shaders::ShaderGlobals,
},
RendererSettings,
};
pub struct TilePipeline {

View File

@ -12,12 +12,13 @@ use request_stage::RequestStage;
use crate::{
coords::{WorldCoords, WorldTileCoords, Zoom, ZoomLevel},
environment::Environment,
error::Error,
io::{
apc::{AsyncProcedureCall, Context, Message},
geometry_index::{GeometryIndex, IndexedGeometry, TileIndex},
pipeline::{PipelineContext, PipelineProcessor, Processable},
source_client::HttpSourceClient,
source_client::{HttpClient, HttpSourceClient},
tile_pipelines::build_vector_tile_pipeline,
transferables::{
DefaultTessellatedLayer, DefaultTileTessellated, DefaultTransferables,
@ -26,27 +27,20 @@ use crate::{
},
TileRequest,
},
kernel::Kernel,
render::ShaderVertex,
schedule::Schedule,
stages::populate_tile_store_stage::PopulateTileStore,
tessellation::{IndexDataType, OverAlignedVertexBuffer},
Environment, HttpClient, Scheduler,
};
mod populate_tile_store_stage;
mod request_stage;
/// Register stages required for requesting and preparing new tiles.
pub fn register_stages<E: Environment>(
schedule: &mut Schedule,
http_source_client: HttpSourceClient<E::HttpClient>,
apc: Rc<RefCell<E::AsyncProcedureCall>>,
) {
schedule.add_stage(
"request",
RequestStage::<E>::new(http_source_client, apc.clone()),
);
schedule.add_stage("populate_tile_store", PopulateTileStore::<E>::new(apc));
pub fn register_stages<E: Environment>(schedule: &mut Schedule, kernel: Rc<Kernel<E>>) {
schedule.add_stage("request", RequestStage::<E>::new(kernel.clone()));
schedule.add_stage("populate_tile_store", PopulateTileStore::<E>::new(kernel));
}
pub struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<T, HC>> {
@ -58,12 +52,16 @@ pub struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<
impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
for HeadedPipelineProcessor<T, HC, C>
{
fn tile_finished(&mut self, coords: &WorldTileCoords) {
fn tile_finished(&mut self, coords: &WorldTileCoords) -> Result<(), Error> {
self.context
.send(Message::TileTessellated(T::TileTessellated::new(*coords)))
}
fn layer_unavailable(&mut self, coords: &WorldTileCoords, layer_name: &str) {
fn layer_unavailable(
&mut self,
coords: &WorldTileCoords,
layer_name: &str,
) -> Result<(), Error> {
self.context
.send(Message::UnavailableLayer(T::UnavailableLayer::new(
*coords,
@ -77,7 +75,7 @@ impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
feature_indices: Vec<u32>,
layer_data: tile::Layer,
) {
) -> Result<(), Error> {
self.context
.send(Message::TessellatedLayer(T::TessellatedLayer::new(
*coords,
@ -91,11 +89,12 @@ impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
&mut self,
coords: &WorldTileCoords,
geometries: Vec<IndexedGeometry<f64>>,
) {
) -> Result<(), Error> {
// FIXME (wasm-executor): Readd
/* if let Ok(mut geometry_index) = self.state.geometry_index.lock() {
geometry_index.index_tile(coords, TileIndex::Linear { list: geometries })
}*/
Ok(())
}
}

View File

@ -4,22 +4,24 @@ use std::{borrow::BorrowMut, cell::RefCell, ops::Deref, rc::Rc};
use crate::{
context::MapContext,
environment::Environment,
io::{
apc::{AsyncProcedureCall, Message},
tile_repository::StoredLayer,
transferables::{TessellatedLayer, TileTessellated, UnavailableLayer},
},
kernel::Kernel,
schedule::Stage,
Environment,
world::World,
};
pub struct PopulateTileStore<E: Environment> {
apc: Rc<RefCell<E::AsyncProcedureCall>>,
kernel: Rc<Kernel<E>>,
}
impl<E: Environment> PopulateTileStore<E> {
pub fn new(apc: Rc<RefCell<E::AsyncProcedureCall>>) -> Self {
Self { apc }
pub fn new(kernel: Rc<Kernel<E>>) -> Self {
Self { kernel }
}
}
@ -27,42 +29,43 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
fn run(
&mut self,
MapContext {
tile_repository, ..
world: World {
tile_repository, ..
},
..
}: &mut MapContext,
) {
if let Ok(mut apc) = self.apc.deref().try_borrow_mut() {
if let Some(result) = apc.receive() {
match result {
Message::TileTessellated(tranferred) => {
let coords = tranferred.coords();
tile_repository.success(coords);
tracing::trace!("Tile at {} finished loading", coords);
log::warn!("Tile at {} finished loading", coords);
}
// FIXME: deduplicate
Message::UnavailableLayer(tranferred) => {
let layer: StoredLayer = tranferred.to_stored_layer();
tracing::debug!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
tile_repository.put_tessellated_layer(layer);
}
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);
}
if let Some(result) = self.kernel.apc().receive() {
match result {
Message::TileTessellated(tranferred) => {
let coords = tranferred.coords();
tile_repository.mark_tile_succeeded(coords);
tracing::trace!("Tile at {} finished loading", coords);
log::warn!("Tile at {} finished loading", coords);
}
// FIXME: deduplicate
Message::UnavailableLayer(tranferred) => {
let layer: StoredLayer = tranferred.to_stored_layer();
tracing::debug!(
"Layer {} at {} reached main thread",
layer.layer_name(),
layer.get_coords()
);
tile_repository.put_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_layer(layer);
}
}
}

View File

@ -15,6 +15,7 @@ use std::{
use crate::{
context::MapContext,
coords::{ViewRegion, WorldTileCoords, ZoomLevel},
environment::Environment,
error::Error,
io::{
apc::{AsyncProcedureCall, AsyncProcedureFuture, Context, Input, Message},
@ -25,25 +26,20 @@ use crate::{
transferables::{Transferables, UnavailableLayer},
TileRequest,
},
kernel::Kernel,
schedule::Stage,
stages::HeadedPipelineProcessor,
Environment, HttpClient, Scheduler, Style,
style::Style,
world::World,
};
pub struct RequestStage<E: Environment> {
apc: Rc<RefCell<E::AsyncProcedureCall>>,
http_source_client: HttpSourceClient<E::HttpClient>,
kernel: Rc<Kernel<E>>,
}
impl<E: Environment> RequestStage<E> {
pub fn new(
http_source_client: HttpSourceClient<E::HttpClient>,
apc: Rc<RefCell<E::AsyncProcedureCall>>,
) -> Self {
Self {
apc,
http_source_client,
}
pub fn new(kernel: Rc<Kernel<E>>) -> Self {
Self { kernel }
}
}
@ -51,27 +47,35 @@ impl<E: Environment> Stage for RequestStage<E> {
fn run(
&mut self,
MapContext {
view_state,
world:
World {
tile_repository,
view_state,
},
style,
tile_repository,
..
}: &mut MapContext,
) {
let view_region = view_state.create_view_region();
if view_state.camera.did_change(0.05) || view_state.zoom.did_change(0.05) {
if view_state.did_camera_change() || view_state.did_zoom_change() {
if let Some(view_region) = &view_region {
// FIXME: We also need to request tiles from layers above if we are over the maximum zoom level
self.request_tiles_in_view(tile_repository, style, view_region);
}
}
view_state.camera.update_reference();
view_state.zoom.update_reference();
view_state.update_references();
}
}
pub fn schedule<E: Environment, C: Context<E::Transferables, E::HttpClient>>(
pub fn schedule<
E: Environment,
C: Context<
<E::AsyncProcedureCall as AsyncProcedureCall<E::HttpClient>>::Transferables,
E::HttpClient,
>,
>(
input: Input,
context: C,
) -> AsyncProcedureFuture {
@ -102,12 +106,15 @@ pub fn schedule<E: Environment, C: Context<E::Transferables, E::HttpClient>>(
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(
// FIXME: Handle result
context.send(
Message::UnavailableLayer(<<E::AsyncProcedureCall as AsyncProcedureCall<
E::HttpClient,
>>::Transferables as Transferables>::UnavailableLayer::new(
input.coords,
to_load.to_string(),
),
));
)),
);
}
}
}
@ -132,7 +139,7 @@ impl<E: Environment> RequestStage<E> {
for coords in view_region.iter() {
if coords.build_quad_key().is_some() {
// TODO: Make tesselation depend on style?
self.request_tile(tile_repository, &coords, &source_layers)
self.request_tile(tile_repository, coords, &source_layers)
.unwrap(); // TODO: Remove unwrap
}
}
@ -141,26 +148,26 @@ impl<E: Environment> RequestStage<E> {
fn request_tile(
&self,
tile_repository: &mut TileRepository,
coords: &WorldTileCoords,
coords: WorldTileCoords,
layers: &HashSet<String>,
) -> Result<(), Error> {
/* if !tile_repository.is_layers_missing(coords, layers) {
/* TODO: is this still required?
if !tile_repository.is_layers_missing(coords, layers) {
return Ok(false);
}*/
if tile_repository.needs_fetching(&coords) {
if tile_repository.has_tile(&coords) {
tile_repository.create_tile(coords);
tracing::info!("new tile request: {}", &coords);
self.apc.deref().borrow().schedule(
self.kernel.apc().call(
Input::TileRequest(TileRequest {
coords: *coords,
coords,
layers: layers.clone(),
}),
schedule::<
E,
<E::AsyncProcedureCall as AsyncProcedureCall<
E::Transferables,
E::HttpClient,
>>::Context,
>,

View File

@ -62,7 +62,6 @@ impl<V, I> OverAlignedVertexBuffer<V, I> {
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);

View File

@ -4,7 +4,7 @@ use std::{cell::RefCell, rc::Rc};
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use crate::{Environment, HttpClient, InteractiveMapSchedule};
use crate::environment::Environment;
/// Window of a certain [`WindowSize`]. This can either be a proper window or a headless one.
pub trait MapWindow {
@ -16,7 +16,12 @@ pub trait MapWindow {
pub trait HeadedMapWindow: MapWindow {
type RawWindow: HasRawWindowHandle + HasRawDisplayHandle;
fn inner(&self) -> &Self::RawWindow;
fn raw(&self) -> &Self::RawWindow;
// TODO: Can we avoid this?
fn request_redraw(&self);
fn id(&self) -> u64;
}
/// A configuration for a window which determines the corresponding implementation of a
@ -27,13 +32,6 @@ pub trait MapWindowConfig: 'static {
fn create(&self) -> Self::MapWindow;
}
/// The event loop is responsible for processing events and propagating them to the map renderer.
/// Only non-headless windows use an [`EventLoop`].
pub trait EventLoop<E: Environment> {
// FIXME (wasm-executor): Avoid Rc, change ownership model
fn run(self, map_schedule: Rc<RefCell<InteractiveMapSchedule<E>>>, max_frames: Option<u64>);
}
/// Window size with a width and an height in pixels.
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct WindowSize {

163
maplibre/src/world.rs Normal file
View File

@ -0,0 +1,163 @@
use std::ops::{Deref, DerefMut};
use cgmath::{Angle, Point3};
use crate::{
context::MapContext,
coords::{LatLon, ViewRegion, WorldCoords, Zoom, ZoomLevel, TILE_SIZE},
io::tile_repository::TileRepository,
render::camera::{Camera, Perspective, ViewProjection},
util::ChangeObserver,
window::WindowSize,
};
pub struct World {
pub view_state: ViewState,
pub tile_repository: TileRepository,
}
impl World {
pub fn new_at<P: Into<cgmath::Deg<f64>>>(
window_size: WindowSize,
initial_center: LatLon,
initial_zoom: Zoom,
pitch: P,
) -> Self {
Self::new(
window_size,
WorldCoords::from_lat_lon(initial_center, initial_zoom),
initial_zoom,
pitch,
)
}
pub fn new<P: Into<cgmath::Deg<f64>>>(
window_size: WindowSize,
initial_center: WorldCoords,
initial_zoom: Zoom,
pitch: P,
) -> Self {
let position = initial_center;
let view_state = ViewState::new(
window_size,
position,
initial_zoom,
pitch,
cgmath::Deg(110.0),
);
let tile_repository = TileRepository::new();
World {
view_state,
tile_repository,
}
}
pub fn view_state(&self) -> &ViewState {
&self.view_state
}
pub fn view_state_mut(&mut self) -> &mut ViewState {
&mut self.view_state
}
}
/// Stores the camera configuration.
pub struct ViewState {
zoom: ChangeObserver<Zoom>,
camera: ChangeObserver<Camera>,
perspective: Perspective,
}
impl ViewState {
pub fn new<F: Into<cgmath::Rad<f64>>, P: Into<cgmath::Deg<f64>>>(
window_size: WindowSize,
position: WorldCoords,
zoom: Zoom,
pitch: P,
fovy: F,
) -> Self {
let tile_center = TILE_SIZE / 2.0;
let fovy = fovy.into();
let height = tile_center / (fovy / 2.0).tan();
let camera = Camera::new(
(position.x, position.y, height),
cgmath::Deg(-90.0),
pitch.into(),
window_size.width(),
window_size.height(),
);
let perspective = Perspective::new(
window_size.width(),
window_size.height(),
cgmath::Deg(110.0),
// in tile.vertex.wgsl we are setting each layer's final `z` in ndc space to `z_index`.
// This means that regardless of the `znear` value all layers will be rendered as part
// of the near plane.
// These values have been selected experimentally:
// https://www.sjbaker.org/steve/omniv/love_your_z_buffer.html
1024.0,
2048.0,
);
Self {
zoom: ChangeObserver::new(zoom),
camera: ChangeObserver::new(camera),
perspective,
}
}
pub fn resize(&mut self, width: u32, height: u32) {
self.perspective.resize(width, height);
self.camera.resize(width, height);
}
pub fn create_view_region(&self) -> Option<ViewRegion> {
self.camera
.view_region_bounding_box(&self.view_projection().invert())
.map(|bounding_box| {
ViewRegion::new(bounding_box, 0, 32, *self.zoom, self.visible_level())
})
}
pub fn view_projection(&self) -> ViewProjection {
self.camera.calc_view_proj(&self.perspective)
}
pub fn visible_level(&self) -> ZoomLevel {
self.zoom.level()
}
pub fn zoom(&self) -> Zoom {
*self.zoom
}
pub fn did_zoom_change(&self) -> bool {
self.zoom.did_change(0.05)
}
pub fn update_zoom(&mut self, new_zoom: Zoom) {
*self.zoom = new_zoom;
log::info!("zoom: {}", new_zoom);
}
pub fn camera(&self) -> &Camera {
self.camera.deref()
}
pub fn camera_mut(&mut self) -> &mut Camera {
self.camera.deref_mut()
}
pub fn did_camera_change(&self) -> bool {
self.camera.did_change(0.05)
}
pub fn update_references(&mut self) {
self.camera.update_reference();
self.zoom.update_reference();
}
}

View File

@ -19,7 +19,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
async-trait = "0.1.56"
maplibre = { path = "../maplibre", features = ["no-thread-safe-futures"] }
maplibre = { path = "../maplibre", default-features = false, features = [] }
maplibre-winit = { path = "../maplibre-winit", version = "0.0.1" }
log = "0.4.17"

View File

@ -121,7 +121,7 @@ const emitTypeScript = () => {
});
if (child.status !== 0) {
console.error("Failed to execute tsc")
throw new Error("Failed to execute tsc")
}
}
@ -154,7 +154,7 @@ const wasmPack = () => {
});
if (cargo.status !== 0) {
console.error("Failed to execute cargo build")
throw new Error("Failed to execute cargo build")
}
let wasmbindgen = spawnSync('wasm-bindgen', [
@ -170,7 +170,7 @@ const wasmPack = () => {
});
if (wasmbindgen.status !== 0) {
console.error("Failed to execute wasm-bindgen")
throw new Error("Failed to execute wasm-bindgen")
}
if (release) {
@ -186,10 +186,9 @@ const wasmPack = () => {
});
if (wasmOpt.status !== 0) {
console.error("Failed to execute wasm-opt")
throw new Error("Failed to execute wasm-opt")
}
}
}
const watchResult = async (result) => {
@ -238,6 +237,7 @@ const esbuild = async (name, globalName = undefined) => {
}
const start = async () => {
try {
console.log("Creating WASM...")
wasmPack();
@ -258,6 +258,9 @@ const start = async () => {
console.log("Emitting TypeScript types...")
emitTypeScript();
} catch (e) {
console.error("Failed to start building: " + e.message)
}
}
const _ = start()

View File

@ -31,39 +31,29 @@ export const startMapLibre = async (wasmPath: string | undefined, workerPath: st
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY / PAGES, shared: true})
await maplibre.default(wasmPath, memory)
maplibre.run(await maplibre.create_map(() => {
await maplibre.run_maplibre(() => {
return workerPath ? new Worker(workerPath, {
type: 'module'
}) : MultithreadedPoolWorker();
}))
});
} else {
const memory = new WebAssembly.Memory({initial: 1024, shared: false})
await maplibre.default(wasmPath, memory);
let callbacks: {worker_callback?: (message: MessageEvent) => void} = {}
let map = await maplibre.create_map(() => {
await maplibre.run_maplibre((ptr) => {
let worker: Worker = workerPath ? new Worker(workerPath, {
type: 'module'
}) : PoolWorker();
worker.onmessage = (message: MessageEvent) => {
callbacks.worker_callback(message)
let tag = message.data[0];
let data = new Uint8Array(message.data[1]);
// @ts-ignore TODO singlethreaded_main_entry may not be defined
maplibre.singlethreaded_main_entry(ptr, tag, data)
}
return worker;
})
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,9 +1,17 @@
#![feature(allocator_api, new_uninit)]
use std::{borrow::BorrowMut, cell::RefCell, mem, ops::Deref, panic, rc::Rc};
use std::{borrow::BorrowMut, cell::RefCell, mem, ops::Deref, rc::Rc};
use maplibre::{io::scheduler::NopScheduler, Map, MapBuilder};
use maplibre_winit::winit::{WinitEnvironment, WinitMapWindowConfig};
use maplibre::{
event_loop::EventLoop,
io::{apc::SchedulerAsyncProcedureCall, scheduler::NopScheduler},
kernel::{Kernel, KernelBuilder},
map::Map,
render::builder::{InitializedRenderer, RendererBuilder},
style::Style,
window::MapWindowConfig,
};
use maplibre_winit::{WinitEnvironment, WinitMapWindowConfig};
use wasm_bindgen::prelude::*;
use crate::platform::http_client::WHATWGFetchHttpClient;
@ -32,45 +40,42 @@ pub fn wasm_bindgen_start() {
if console_log::init_with_level(log::Level::Info).is_err() {
// Failed to initialize logging. No need to log a message.
}
panic::set_hook(Box::new(console_error_panic_hook::hook));
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
#[cfg(any(feature = "trace"))]
enable_tracing();
}
#[cfg(not(target_feature = "atomics"))]
pub type MapType = Map<
WinitEnvironment<
NopScheduler,
WHATWGFetchHttpClient,
platform::singlethreaded::transferables::LinearTransferables,
platform::singlethreaded::apc::PassingAsyncProcedureCall,
>,
type CurrentEnvironment = WinitEnvironment<
NopScheduler,
WHATWGFetchHttpClient,
platform::singlethreaded::apc::PassingAsyncProcedureCall,
(),
>;
#[cfg(target_feature = "atomics")]
pub type MapType = Map<
WinitEnvironment<
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
type CurrentEnvironment = WinitEnvironment<
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
WHATWGFetchHttpClient,
maplibre::io::apc::SchedulerAsyncProcedureCall<
WHATWGFetchHttpClient,
maplibre::io::transferables::DefaultTransferables,
maplibre::io::apc::SchedulerAsyncProcedureCall<
WHATWGFetchHttpClient,
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
>,
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler,
>,
(),
>;
pub type MapType = Map<CurrentEnvironment>;
#[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()
pub async fn run_maplibre(new_worker: js_sys::Function) {
let mut kernel_builder = KernelBuilder::new()
.with_map_window_config(WinitMapWindowConfig::new("maplibre".to_string()))
.with_http_client(WHATWGFetchHttpClient::new());
#[cfg(target_feature = "atomics")]
{
builder = builder
kernel_builder = kernel_builder
.with_apc(maplibre::io::apc::SchedulerAsyncProcedureCall::new(
WHATWGFetchHttpClient::new(),
platform::multithreaded::pool_scheduler::WebWorkerPoolScheduler::new(
@ -84,30 +89,22 @@ pub async fn create_map(new_worker: js_sys::Function) -> u32 {
#[cfg(not(target_feature = "atomics"))]
{
builder = builder
kernel_builder = kernel_builder
.with_apc(platform::singlethreaded::apc::PassingAsyncProcedureCall::new(new_worker, 4))
.with_scheduler(NopScheduler);
}
let map: MapType = builder.build().initialize().await;
let kernel: Kernel<WinitEnvironment<_, _, _, ()>> = kernel_builder.build();
Rc::into_raw(Rc::new(RefCell::new(map))) as u32
}
let mut map: MapType = Map::new(Style::default(), kernel).unwrap();
map.initialize_renderer(RendererBuilder::new())
.await
.unwrap();
#[wasm_bindgen]
pub unsafe fn clone_map(map_ptr: *const RefCell<MapType>) -> *const RefCell<MapType> {
let mut map = Rc::from_raw(map_ptr);
let rc = map.clone();
let cloned = Rc::into_raw(rc);
mem::forget(map);
cloned
}
#[wasm_bindgen]
pub unsafe fn run(map_ptr: *const RefCell<MapType>) {
let mut map = Rc::from_raw(map_ptr);
map.deref().borrow().run();
map.window_mut()
.take_event_loop()
.expect("Event loop is not available")
.run(map, None)
}
#[cfg(test)]

View File

@ -6,6 +6,7 @@ use wasm_bindgen::{prelude::*, JsCast};
use web_sys::Worker;
use super::pool::WorkerPool;
use crate::CurrentEnvironment;
pub struct WebWorkerPoolScheduler {
pool: WorkerPool,
@ -13,19 +14,19 @@ pub struct WebWorkerPoolScheduler {
impl WebWorkerPoolScheduler {
pub fn new(new_worker: js_sys::Function) -> Self {
Self {
pool: WorkerPool::new(
1,
Box::new(move || {
new_worker
.call0(&JsValue::undefined())
.unwrap() // FIXME (wasm-executor): Remove unwrap
.dyn_into::<Worker>()
.unwrap() // FIXME (wasm-executor): remove unwrap
}),
)
.unwrap(), // FIXME (wasm-executor): Remove unwrap
}
// TODO: Are expects here oke?
let pool = WorkerPool::new(
1,
Box::new(move || {
new_worker
.call0(&JsValue::undefined())
.expect("Unable to call new_worker function")
.dyn_into::<Worker>()
.expect("new_worker function did not return a Worker")
}),
)
.expect("Unable to create WorkerPool");
Self { pool }
}
}

View File

@ -16,29 +16,33 @@ use std::{
};
use js_sys::Uint8Array;
use log::info;
use maplibre::{
environment::Environment,
error::Error,
io::{
apc::{AsyncProcedure, AsyncProcedureCall, Context, Input, Message},
scheduler::Scheduler,
source_client::{HttpClient, HttpSourceClient, SourceClient},
transferables::Transferables,
},
kernel::Kernel,
};
use wasm_bindgen::{prelude::*, JsCast, JsValue};
use web_sys::{DedicatedWorkerGlobalScope, Worker};
use web_sys::{console::info, DedicatedWorkerGlobalScope, Worker};
use crate::{
platform::singlethreaded::transferables::{
InnerData, LinearTessellatedLayer, LinearTransferables,
},
MapType, WHATWGFetchHttpClient,
CurrentEnvironment, MapType, WHATWGFetchHttpClient,
};
type UsedTransferables = LinearTransferables;
type UsedHttpClient = WHATWGFetchHttpClient;
type UsedContext = PassingContext;
#[derive(Debug)]
enum SerializedMessageTag {
TileTessellated = 1,
UnavailableLayer = 2,
@ -120,7 +124,7 @@ pub struct PassingContext {
}
impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
fn send(&self, data: Message<UsedTransferables>) {
fn send(&self, data: Message<UsedTransferables>) -> Result<(), Error> {
let tag = data.tag();
let serialized = data.serialize();
@ -130,11 +134,12 @@ impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
serialized_array.set(&Uint8Array::view(serialized), 0);
}
let global = js_sys::global().unchecked_into::<DedicatedWorkerGlobalScope>(); // FIXME (wasm-executor): Remove unchecked
let global: DedicatedWorkerGlobalScope =
js_sys::global().dyn_into().map_err(|e| Error::APC)?;
let array = js_sys::Array::new();
array.push(&JsValue::from(tag as u32));
array.push(&serialized_array_buffer);
global.post_message(&array).unwrap(); // FIXME (wasm-executor) Remove unwrap
global.post_message(&array).map_err(|e| Error::APC)
}
fn source_client(&self) -> &SourceClient<UsedHttpClient> {
@ -142,18 +147,26 @@ impl Context<UsedTransferables, UsedHttpClient> for PassingContext {
}
}
type ReceivedType = RefCell<Vec<Message<UsedTransferables>>>;
pub struct PassingAsyncProcedureCall {
new_worker: Box<dyn Fn() -> Worker>,
workers: Vec<Worker>,
received: Vec<Message<UsedTransferables>>,
received: Rc<ReceivedType>, // FIXME (wasm-executor): Is RefCell fine?
}
impl PassingAsyncProcedureCall {
pub fn new(new_worker: js_sys::Function, initial_workers: u8) -> Self {
let received = Rc::new(RefCell::new(vec![]));
let received_ref = received.clone();
let create_new_worker = Box::new(move || {
new_worker
.call0(&JsValue::undefined())
.call1(
&JsValue::undefined(),
&JsValue::from(Rc::into_raw(received_ref.clone()) as u32),
)
.unwrap() // FIXME (wasm-executor): Remove unwrap
.dyn_into::<Worker>()
.unwrap() // FIXME (wasm-executor): Remove unwrap
@ -173,19 +186,20 @@ impl PassingAsyncProcedureCall {
Self {
new_worker: create_new_worker,
workers,
received: vec![],
received,
}
}
}
impl AsyncProcedureCall<UsedTransferables, UsedHttpClient> for PassingAsyncProcedureCall {
impl AsyncProcedureCall<UsedHttpClient> for PassingAsyncProcedureCall {
type Context = UsedContext;
type Transferables = UsedTransferables;
fn receive(&mut self) -> Option<Message<UsedTransferables>> {
self.received.pop()
fn receive(&self) -> Option<Message<UsedTransferables>> {
self.received.borrow_mut().pop()
}
fn schedule(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
fn call(&self, input: Input, procedure: AsyncProcedure<Self::Context>) {
let procedure_ptr = procedure as *mut AsyncProcedure<Self::Context> as u32; // FIXME (wasm-executor): is u32 fine, define an overflow safe function?
let input = serde_json::to_string(&input).unwrap(); // FIXME (wasm-executor): Remove unwrap
@ -205,7 +219,7 @@ pub async fn singlethreaded_worker_entry(procedure_ptr: u32, input: String) -> R
let input = serde_json::from_str::<Input>(&input).unwrap(); // FIXME (wasm-executor): Remove unwrap
let context = PassingContext {
source_client: SourceClient::Http(HttpSourceClient::new(WHATWGFetchHttpClient::new())),
source_client: SourceClient::new(HttpSourceClient::new(WHATWGFetchHttpClient::new())),
};
(procedure)(input, context).await;
@ -216,30 +230,24 @@ pub async fn singlethreaded_worker_entry(procedure_ptr: u32, input: String) -> R
/// Entry point invoked by the main thread.
#[wasm_bindgen]
pub unsafe fn singlethreaded_main_entry(
map_ptr: *const RefCell<MapType>,
received_ptr: *const ReceivedType,
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 mut received: Rc<ReceivedType> = Rc::from_raw(received_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);
info!("singlethreaded_main_entry {:?}", message.tag());
mem::forget(map); // FIXME (wasm-executor): Enforce this somehow
// MAJOR FIXME: Fix mutability
received.borrow_mut().push(message);
mem::forget(received); // FIXME (wasm-executor): Enforce this somehow
Ok(())
}

View File

@ -1,5 +1,6 @@
use bytemuck::{TransparentWrapper, Zeroable};
use bytemuck_derive::{Pod, Zeroable};
use log::warn;
use maplibre::{
benchmarking::tessellation::{IndexDataType, OverAlignedVertexBuffer},
coords::WorldTileCoords,
@ -122,6 +123,75 @@ impl TessellatedLayer for LinearTessellatedLayer {
feature_indices_len: feature_indices.len(),
});
if buffer.buffer.vertices.len() > 15000 {
warn!("vertices too large");
return Self {
data: Box::new(InnerData {
coords: WrapperWorldTileCoords::wrap(coords),
layer_name: [0; 32],
layer_name_len: 0,
vertices: LongVertexShader::wrap([ShaderVertex::zeroed(); 15000]),
vertices_len: 0,
indices: LongIndices::wrap([IndexDataType::zeroed(); 40000]),
indices_len: 0,
usable_indices: 0,
feature_indices: [0u32; 2048],
feature_indices_len: 0,
}),
};
}
if buffer.buffer.indices.len() > 40000 {
warn!("indices too large");
return Self {
data: Box::new(InnerData {
coords: WrapperWorldTileCoords::wrap(coords),
layer_name: [0; 32],
layer_name_len: 0,
vertices: LongVertexShader::wrap([ShaderVertex::zeroed(); 15000]),
vertices_len: 0,
indices: LongIndices::wrap([IndexDataType::zeroed(); 40000]),
indices_len: 0,
usable_indices: 0,
feature_indices: [0u32; 2048],
feature_indices_len: 0,
}),
};
}
if feature_indices.len() > 2048 {
warn!("feature_indices too large");
return Self {
data: Box::new(InnerData {
coords: WrapperWorldTileCoords::wrap(coords),
layer_name: [0; 32],
layer_name_len: 0,
vertices: LongVertexShader::wrap([ShaderVertex::zeroed(); 15000]),
vertices_len: 0,
indices: LongIndices::wrap([IndexDataType::zeroed(); 40000]),
indices_len: 0,
usable_indices: 0,
feature_indices: [0u32; 2048],
feature_indices_len: 0,
}),
};
}
data.vertices.0[0..buffer.buffer.vertices.len()].clone_from_slice(&buffer.buffer.vertices);
data.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);
@ -131,7 +201,8 @@ impl TessellatedLayer for LinearTessellatedLayer {
}
fn to_stored_layer(self) -> StoredLayer {
let layer = StoredLayer::TessellatedLayer {
// TODO: Avoid copies here
StoredLayer::TessellatedLayer {
coords: WrapperWorldTileCoords::peel(self.data.coords),
layer_name: String::from_utf8(Vec::from(
&self.data.layer_name[..self.data.layer_name_len],
@ -143,9 +214,7 @@ impl TessellatedLayer for LinearTessellatedLayer {
self.data.usable_indices,
),
feature_indices: Vec::from(&self.data.feature_indices[..self.data.feature_indices_len]),
};
layer
}
}
}