mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
Rename io scheduler and tessellator state
This commit is contained in:
parent
0c7e94ee30
commit
7be02fe847
@ -1,13 +1,18 @@
|
||||
//! Handles IO related processing as well as multithreading.
|
||||
|
||||
use crate::coords::TileCoords;
|
||||
use crate::coords::{TileCoords, WorldTileCoords};
|
||||
use crate::error::Error;
|
||||
use crate::render::ShaderVertex;
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer};
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use vector_tile::tile::Layer;
|
||||
|
||||
pub mod scheduler;
|
||||
pub mod static_tile_fetcher;
|
||||
pub mod tile_cache;
|
||||
pub mod web_tile_fetcher;
|
||||
pub mod workflow;
|
||||
|
||||
pub struct HttpFetcherConfig {
|
||||
/// Under which path should we cache requests.
|
||||
@ -38,3 +43,59 @@ pub trait TileFetcher {
|
||||
async fn fetch_tile(&self, coords: &TileCoords) -> Result<Vec<u8>, Error>;
|
||||
fn sync_fetch_tile(&self, coords: &TileCoords) -> Result<Vec<u8>, Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum TileResult {
|
||||
Unavailable {
|
||||
coords: WorldTileCoords,
|
||||
},
|
||||
Tile {
|
||||
coords: WorldTileCoords,
|
||||
data: Box<[u8]>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LayerResult {
|
||||
UnavailableLayer {
|
||||
coords: WorldTileCoords,
|
||||
layer_name: String,
|
||||
},
|
||||
TessellatedLayer {
|
||||
coords: WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
/// Holds for each feature the count of indices
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: Layer,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Debug for LayerResult {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "LayerResult{}", self.get_coords())
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerResult {
|
||||
pub fn get_coords(&self) -> WorldTileCoords {
|
||||
match self {
|
||||
LayerResult::UnavailableLayer { coords, .. } => *coords,
|
||||
LayerResult::TessellatedLayer { coords, .. } => *coords,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer_name(&self) -> &str {
|
||||
match self {
|
||||
LayerResult::UnavailableLayer { layer_name, .. } => layer_name.as_str(),
|
||||
LayerResult::TessellatedLayer { layer_data, .. } => layer_data.name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TileRequest {
|
||||
pub coords: WorldTileCoords,
|
||||
pub layers: HashSet<String>,
|
||||
}
|
||||
|
||||
pub type TileRequestID = u32;
|
||||
|
||||
246
src/io/scheduler.rs
Normal file
246
src/io/scheduler.rs
Normal file
@ -0,0 +1,246 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::sync::mpsc::{channel, Receiver, RecvError, SendError, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
//use crossbeam_channel::{unbounded as channel, Receiver, RecvError, SendError, Sender};
|
||||
use log::{error, info, warn};
|
||||
|
||||
use style_spec::source::TileAdressingScheme;
|
||||
use vector_tile::parse_tile_bytes;
|
||||
use vector_tile::tile::Layer;
|
||||
|
||||
/// Describes through which channels work-requests travel. It describes the flow of work.
|
||||
use crate::coords::{TileCoords, WorldTileCoords};
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::web_tile_fetcher::WebTileFetcher;
|
||||
use crate::io::{
|
||||
HttpFetcherConfig, LayerResult, TileFetcher, TileRequest, TileRequestID, TileResult,
|
||||
};
|
||||
|
||||
use crate::render::ShaderVertex;
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer, Tessellated};
|
||||
|
||||
pub struct ThreadLocalTessellatorState {
|
||||
tile_request_state: Arc<Mutex<TileRequestState>>,
|
||||
layer_result_sender: Sender<LayerResult>,
|
||||
}
|
||||
|
||||
impl ThreadLocalTessellatorState {
|
||||
pub fn tessellate_layers(
|
||||
&self,
|
||||
request_id: TileRequestID,
|
||||
data: Box<[u8]>,
|
||||
) -> Result<(), SendError<LayerResult>> {
|
||||
if let Ok(mut tile_request_state) = self.tile_request_state.lock() {
|
||||
if let Some(tile_request) = tile_request_state.finish_tile_request(request_id) {
|
||||
self.tessellate_layers_with_request(
|
||||
TileResult::Tile {
|
||||
coords: tile_request.coords,
|
||||
data,
|
||||
},
|
||||
&tile_request,
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn tessellate_layers_with_request(
|
||||
&self,
|
||||
tile_result: TileResult,
|
||||
tile_request: &TileRequest,
|
||||
) -> Result<(), SendError<LayerResult>> {
|
||||
if let TileResult::Tile { data, coords } = tile_result {
|
||||
info!("parsing tile {} with {}bytes", &coords, data.len());
|
||||
let tile = parse_tile_bytes(&data).expect("failed to load tile");
|
||||
|
||||
for to_load in &tile_request.layers {
|
||||
if let Some(layer) = tile
|
||||
.layers()
|
||||
.iter()
|
||||
.find(|layer| to_load.as_str() == layer.name())
|
||||
{
|
||||
if let Some((buffer, feature_indices)) = layer.tessellate() {
|
||||
self.layer_result_sender
|
||||
.send(LayerResult::TessellatedLayer {
|
||||
coords,
|
||||
buffer: buffer.into(),
|
||||
feature_indices,
|
||||
layer_data: layer.clone(),
|
||||
})?;
|
||||
}
|
||||
|
||||
info!("layer {} ready: {}", to_load, &coords);
|
||||
} else {
|
||||
self.layer_result_sender
|
||||
.send(LayerResult::UnavailableLayer {
|
||||
coords,
|
||||
layer_name: to_load.to_string(),
|
||||
})?;
|
||||
|
||||
info!("layer {} not found: {}", to_load, &coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IOScheduler {
|
||||
layer_result_sender: Sender<LayerResult>,
|
||||
layer_result_receiver: Receiver<LayerResult>,
|
||||
tile_request_state: Arc<Mutex<TileRequestState>>,
|
||||
tile_cache: TileCache,
|
||||
}
|
||||
|
||||
const _: () = {
|
||||
fn assert_send<T: Send>() {}
|
||||
|
||||
fn assert_all() {
|
||||
assert_send::<ThreadLocalTessellatorState>();
|
||||
}
|
||||
};
|
||||
|
||||
impl Drop for IOScheduler {
|
||||
fn drop(&mut self) {
|
||||
warn!("WorkerLoop dropped. This should only happen when the application is stopped!");
|
||||
}
|
||||
}
|
||||
|
||||
impl IOScheduler {
|
||||
pub fn create() -> Self {
|
||||
let (layer_result_sender, layer_result_receiver) = channel();
|
||||
Self {
|
||||
layer_result_sender,
|
||||
layer_result_receiver,
|
||||
tile_request_state: Arc::new(Mutex::new(TileRequestState::new())),
|
||||
tile_cache: TileCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_cache(&self) {
|
||||
while let Ok(result) = self.layer_result_receiver.try_recv() {
|
||||
self.tile_cache.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_tessellator_state(&self) -> ThreadLocalTessellatorState {
|
||||
ThreadLocalTessellatorState {
|
||||
tile_request_state: self.tile_request_state.clone(),
|
||||
layer_result_sender: self.layer_result_sender.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_tile(
|
||||
&mut self,
|
||||
tile_request: TileRequest,
|
||||
) -> Result<(), SendError<TileRequest>> {
|
||||
let TileRequest { coords, layers } = &tile_request;
|
||||
|
||||
if let Some(missing_layers) = self
|
||||
.tile_cache
|
||||
.get_missing_tessellated_layer_names_at(coords, layers.clone())
|
||||
{
|
||||
if missing_layers.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
loop {
|
||||
if let Ok(mut tile_request_state) = self.tile_request_state.try_lock() {
|
||||
if let Some(id) = tile_request_state.start_tile_request(tile_request.clone()) {
|
||||
info!("new tile request: {}", &coords);
|
||||
let tile_coords = coords.into_tile(TileAdressingScheme::TMS);
|
||||
|
||||
/* crate::platform::fetch_tile(
|
||||
format!(
|
||||
"https://maps.tuerantuer.org/europe_germany/{z}/{x}/{y}.pbf",
|
||||
x = tile_coords.x,
|
||||
y = tile_coords.y,
|
||||
z = tile_coords.z,
|
||||
)
|
||||
.as_str(),
|
||||
id,
|
||||
);*/
|
||||
|
||||
let state = self.new_tessellator_state();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let fetcher = WebTileFetcher::new(HttpFetcherConfig {
|
||||
cache_path: "/tmp/mapr-cache".to_string(),
|
||||
});
|
||||
|
||||
if let Ok(data) = fetcher.fetch_tile(&tile_coords).await {
|
||||
state
|
||||
.tessellate_layers(id, data.into_boxed_slice())
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tessellated_layers_at(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
skip_layers: &HashSet<String>,
|
||||
) -> Vec<LayerResult> {
|
||||
self.tile_cache
|
||||
.get_tessellated_layers_at(coords, skip_layers)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TileRequestState {
|
||||
current_id: TileRequestID,
|
||||
pending_tile_requests: HashMap<TileRequestID, TileRequest>,
|
||||
pending_coords: HashSet<WorldTileCoords>,
|
||||
}
|
||||
|
||||
impl TileRequestState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
current_id: 1,
|
||||
pending_tile_requests: Default::default(),
|
||||
pending_coords: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_tile_request_pending(&self, coords: &WorldTileCoords) -> bool {
|
||||
self.pending_coords.contains(coords)
|
||||
}
|
||||
|
||||
pub fn start_tile_request(&mut self, tile_request: TileRequest) -> Option<TileRequestID> {
|
||||
if self.is_tile_request_pending(&tile_request.coords) {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.pending_coords.insert(tile_request.coords.clone());
|
||||
let id = self.current_id;
|
||||
self.pending_tile_requests.insert(id, tile_request);
|
||||
self.current_id += 1;
|
||||
Some(id)
|
||||
}
|
||||
|
||||
/*pub fn finish_tile_request(&mut self, id: TileRequestID) -> Option<TileRequest> {
|
||||
self.pending_tile_requests.remove(&id).map(|request| {
|
||||
self.pending_coords.remove(&request.coords);
|
||||
request
|
||||
})
|
||||
}*/
|
||||
|
||||
pub fn finish_tile_request(&self, id: TileRequestID) -> Option<&TileRequest> {
|
||||
self.pending_tile_requests.get(&id)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::coords::{WorldTileCoords};
|
||||
use crate::io::workflow::LayerResult;
|
||||
use crate::coords::WorldTileCoords;
|
||||
use crate::io::LayerResult;
|
||||
use std::collections::{btree_map, BTreeMap, HashSet};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
||||
@ -1,247 +0,0 @@
|
||||
/// Describes through which channels work-requests travel. It describes the flow of work.
|
||||
use crate::coords::WorldTileCoords;
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::web_tile_fetcher::WebTileFetcher;
|
||||
use crate::io::{HttpFetcherConfig, TileFetcher};
|
||||
use crate::render::ShaderVertex;
|
||||
use crate::tessellation::{IndexDataType, OverAlignedVertexBuffer, Tessellated};
|
||||
//use crossbeam_channel::{unbounded as channel, Receiver, RecvError, SendError, Sender};
|
||||
use log::{error, info, warn};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::sync::mpsc::{channel, Receiver, RecvError, SendError, Sender};
|
||||
|
||||
use style_spec::source::TileAdressingScheme;
|
||||
use vector_tile::parse_tile_bytes;
|
||||
use vector_tile::tile::Layer;
|
||||
|
||||
pub struct Workflow {
|
||||
layer_result_receiver: Receiver<LayerResult>,
|
||||
tile_request_dispatcher: TileRequestDispatcher,
|
||||
download_tessellate_loop: Option<DownloadTessellateLoop>,
|
||||
tile_cache: TileCache,
|
||||
}
|
||||
|
||||
impl Drop for Workflow {
|
||||
fn drop(&mut self) {
|
||||
warn!("WorkerLoop dropped. This should only happen when the application is stopped!");
|
||||
}
|
||||
}
|
||||
|
||||
impl Workflow {
|
||||
pub fn create() -> Self {
|
||||
let (tile_request_sender, tile_request_receiver) = channel();
|
||||
|
||||
let tile_request_dispatcher = TileRequestDispatcher::new(tile_request_sender);
|
||||
|
||||
let (layer_result_sender, layer_result_receiver) = channel();
|
||||
|
||||
let download_tessellate_loop =
|
||||
DownloadTessellateLoop::new(tile_request_receiver, layer_result_sender);
|
||||
|
||||
Self {
|
||||
layer_result_receiver,
|
||||
tile_request_dispatcher,
|
||||
download_tessellate_loop: Some(download_tessellate_loop),
|
||||
tile_cache: TileCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_cache(&self) {
|
||||
while let Ok(result) = self.layer_result_receiver.try_recv() {
|
||||
self.tile_cache.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_tile(
|
||||
&mut self,
|
||||
tile_request: TileRequest,
|
||||
) -> Result<(), SendError<TileRequest>> {
|
||||
self.tile_request_dispatcher
|
||||
.request_tile(tile_request, &self.tile_cache)
|
||||
}
|
||||
|
||||
pub fn get_tessellated_layers_at(
|
||||
&self,
|
||||
coords: &WorldTileCoords,
|
||||
skip_layers: &HashSet<String>,
|
||||
) -> Vec<LayerResult> {
|
||||
self.tile_cache
|
||||
.get_tessellated_layers_at(coords, skip_layers)
|
||||
}
|
||||
|
||||
pub fn take_download_loop(&mut self) -> DownloadTessellateLoop {
|
||||
self.download_tessellate_loop.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum LayerResult {
|
||||
UnavailableLayer {
|
||||
coords: WorldTileCoords,
|
||||
layer_name: String,
|
||||
},
|
||||
TessellatedLayer {
|
||||
coords: WorldTileCoords,
|
||||
buffer: OverAlignedVertexBuffer<ShaderVertex, IndexDataType>,
|
||||
/// Holds for each feature the count of indices
|
||||
feature_indices: Vec<u32>,
|
||||
layer_data: Layer,
|
||||
},
|
||||
}
|
||||
|
||||
impl Debug for LayerResult {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "LayerResult{}", self.get_coords())
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerResult {
|
||||
pub fn get_coords(&self) -> WorldTileCoords {
|
||||
match self {
|
||||
LayerResult::UnavailableLayer { coords, .. } => *coords,
|
||||
LayerResult::TessellatedLayer { coords, .. } => *coords,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layer_name(&self) -> &str {
|
||||
match self {
|
||||
LayerResult::UnavailableLayer { layer_name, .. } => layer_name.as_str(),
|
||||
LayerResult::TessellatedLayer { layer_data, .. } => layer_data.name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TileRequest {
|
||||
pub coords: WorldTileCoords,
|
||||
pub layers: HashSet<String>,
|
||||
}
|
||||
|
||||
pub struct TileRequestDispatcher {
|
||||
request_sender: Sender<TileRequest>,
|
||||
pending_tiles: HashSet<WorldTileCoords>,
|
||||
}
|
||||
|
||||
impl TileRequestDispatcher {
|
||||
pub fn new(request_sender: Sender<TileRequest>) -> Self {
|
||||
Self {
|
||||
pending_tiles: Default::default(),
|
||||
request_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_tile(
|
||||
&mut self,
|
||||
tile_request: TileRequest,
|
||||
tile_cache: &TileCache,
|
||||
) -> Result<(), SendError<TileRequest>> {
|
||||
let TileRequest { coords, layers } = &tile_request;
|
||||
|
||||
if let Some(missing_layers) =
|
||||
tile_cache.get_missing_tessellated_layer_names_at(coords, layers.clone())
|
||||
{
|
||||
if missing_layers.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.pending_tiles.contains(coords) {
|
||||
return Ok(());
|
||||
}
|
||||
self.pending_tiles.insert(*coords);
|
||||
|
||||
info!("new tile request: {}", &coords);
|
||||
self.request_sender.send(tile_request)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DownloadTessellateLoop {
|
||||
request_receiver: Receiver<TileRequest>,
|
||||
result_sender: Sender<LayerResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SendReceiveError<S> {
|
||||
Send(SendError<S>),
|
||||
Receive(RecvError),
|
||||
}
|
||||
|
||||
impl<S> From<RecvError> for SendReceiveError<S> {
|
||||
fn from(e: RecvError) -> Self {
|
||||
SendReceiveError::Receive(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> From<SendError<S>> for SendReceiveError<S> {
|
||||
fn from(e: SendError<S>) -> Self {
|
||||
SendReceiveError::Send(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl DownloadTessellateLoop {
|
||||
pub fn new(
|
||||
request_receiver: Receiver<TileRequest>,
|
||||
result_sender: Sender<LayerResult>,
|
||||
) -> Self {
|
||||
Self {
|
||||
request_receiver,
|
||||
result_sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_loop(&self) -> Result<(), SendReceiveError<LayerResult>> {
|
||||
let fetcher = WebTileFetcher::new(HttpFetcherConfig {
|
||||
cache_path: "/tmp/mapr-cache".to_string(),
|
||||
});
|
||||
// let fetcher = StaticTileFetcher::new();
|
||||
|
||||
loop {
|
||||
// Internally uses Condvar probably: Condvar is also supported on WASM
|
||||
// see https://github.com/rust-lang/rust/blob/effea9a2a0d501db5722d507690a1a66236933bf/library/std/src/sys/wasm/atomics/condvar.rs
|
||||
if let TileRequest {
|
||||
coords,
|
||||
layers: layers_to_load,
|
||||
} = self.request_receiver.recv()?
|
||||
{
|
||||
let tile_coords = coords.into_tile(TileAdressingScheme::TMS);
|
||||
match fetcher.fetch_tile(&tile_coords).await {
|
||||
Ok(data) => {
|
||||
info!("preparing tile {} with {}bytes", &tile_coords, data.len());
|
||||
let tile = parse_tile_bytes(data.as_slice()).expect("failed to load tile1");
|
||||
|
||||
for to_load in layers_to_load {
|
||||
if let Some(layer) = tile
|
||||
.layers()
|
||||
.iter()
|
||||
.find(|layer| to_load.as_str() == layer.name())
|
||||
{
|
||||
if let Some((buffer, feature_indices)) = layer.tessellate() {
|
||||
self.result_sender.send(LayerResult::TessellatedLayer {
|
||||
coords,
|
||||
buffer: buffer.into(),
|
||||
feature_indices,
|
||||
layer_data: layer.clone(),
|
||||
})?;
|
||||
}
|
||||
|
||||
info!("layer {} ready: {}", to_load, &coords);
|
||||
} else {
|
||||
self.result_sender.send(LayerResult::UnavailableLayer {
|
||||
coords,
|
||||
layer_name: to_load.to_string(),
|
||||
})?;
|
||||
|
||||
info!("layer {} not found: {}", to_load, &coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("fetching tile failed: {:?}", &err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,22 +3,20 @@
|
||||
//! * Platform Events like suspend/resume
|
||||
//! * Render a new frame
|
||||
|
||||
|
||||
use log::{error, info, trace};
|
||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
|
||||
|
||||
use crate::input::{InputController, UpdateState};
|
||||
|
||||
use crate::io::workflow::{Workflow};
|
||||
use crate::io::scheduler::IOScheduler;
|
||||
use crate::platform::Instant;
|
||||
use crate::render::render_state::RenderState;
|
||||
|
||||
pub async fn setup(
|
||||
window: winit::window::Window,
|
||||
event_loop: EventLoop<()>,
|
||||
mut workflow: Box<Workflow>,
|
||||
mut workflow: Box<IOScheduler>,
|
||||
) {
|
||||
info!("== mapr ==");
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::io::workflow::Workflow;
|
||||
use crate::io::scheduler::IOScheduler;
|
||||
use crate::main_loop;
|
||||
pub use std::time::Instant;
|
||||
use tokio::task;
|
||||
@ -19,8 +19,8 @@ pub async fn main() {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut workflow = Workflow::create();
|
||||
let download_tessellate_loop = workflow.take_download_loop();
|
||||
let mut scheduler = IOScheduler::create();
|
||||
let download_tessellate_loop = scheduler.take_download_loop();
|
||||
|
||||
let join_handle = task::spawn_blocking(move || {
|
||||
Handle::current().block_on(async move {
|
||||
@ -30,6 +30,6 @@ pub async fn main() {
|
||||
});
|
||||
});
|
||||
|
||||
main_loop::setup(window, event_loop, Box::new(workflow)).await;
|
||||
main_loop::setup(window, event_loop, Box::new(scheduler)).await;
|
||||
join_handle.await.unwrap()
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::WindowBuilder;
|
||||
|
||||
use crate::io::workflow::Workflow;
|
||||
use crate::io::scheduler::IOScheduler;
|
||||
use crate::main_loop;
|
||||
pub use std::time::Instant;
|
||||
use tokio::task;
|
||||
@ -20,8 +20,8 @@ pub async fn mapr_apple_main() {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut workflow = Workflow::create();
|
||||
let download_tessellate_loop = workflow.take_download_loop();
|
||||
let mut scheduler = IOScheduler::create();
|
||||
let download_tessellate_loop = scheduler.take_download_loop();
|
||||
|
||||
let join_handle = task::spawn_blocking(move || {
|
||||
Handle::current().block_on(async move {
|
||||
@ -31,6 +31,6 @@ pub async fn mapr_apple_main() {
|
||||
});
|
||||
});
|
||||
|
||||
main_loop::setup(window, event_loop, Box::new(workflow)).await;
|
||||
main_loop::setup(window, event_loop, Box::new(scheduler)).await;
|
||||
join_handle.await.unwrap()
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
//! Module which is used if android, apple and web is not used.
|
||||
|
||||
|
||||
use crate::io::workflow::Workflow;
|
||||
use crate::io::scheduler::IOScheduler;
|
||||
use crate::main_loop;
|
||||
use log::error;
|
||||
pub use std::time::Instant;
|
||||
@ -23,17 +22,16 @@ pub async fn mapr_generic_main() {
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let mut workflow = Workflow::create();
|
||||
let download_tessellate_loop = workflow.take_download_loop();
|
||||
let mut scheduler = IOScheduler::create();
|
||||
|
||||
let join_handle = task::spawn_blocking(move || {
|
||||
/* let join_handle = task::spawn_blocking(move || {
|
||||
Handle::current().block_on(async move {
|
||||
if let Err(e) = download_tessellate_loop.run_loop().await {
|
||||
error!("Worker loop errored {:?}", e)
|
||||
}
|
||||
});
|
||||
});
|
||||
});*/
|
||||
|
||||
main_loop::setup(window, event_loop, Box::new(workflow)).await;
|
||||
join_handle.await.unwrap()
|
||||
main_loop::setup(window, event_loop, Box::new(scheduler)).await;
|
||||
/* join_handle.await.unwrap()*/
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ mod http_fetcher;
|
||||
|
||||
use std::panic;
|
||||
|
||||
use log::error;
|
||||
use log::info;
|
||||
use log::Level;
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event_loop::EventLoop;
|
||||
@ -22,9 +24,11 @@ pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8
|
||||
#[cfg(feature = "web-webgl")]
|
||||
pub const COLOR_TEXTURE_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
|
||||
|
||||
use crate::coords::{TileCoords, WorldTileCoords};
|
||||
use crate::io::scheduler::{IOScheduler, ThreadLocalTessellatorState, TileResult};
|
||||
use crate::io::tile_cache::TileCache;
|
||||
use crate::io::workflow::Workflow;
|
||||
pub use http_fetcher::PlatformHttpFetcher;
|
||||
use style_spec::source::TileAdressingScheme;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
@ -35,27 +39,46 @@ pub fn start() {
|
||||
panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn create_workflow() -> *mut Workflow {
|
||||
let workflow = Box::new(Workflow::create());
|
||||
let workflow_ptr = Box::into_raw(workflow);
|
||||
return workflow_ptr;
|
||||
#[wasm_bindgen()]
|
||||
extern "C" {
|
||||
pub fn fetch_tile(url: &str, request_id: u32);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn run_worker_loop(workflow_ptr: *mut Workflow) {
|
||||
let mut workflow: Box<Workflow> = unsafe { Box::from_raw(workflow_ptr) };
|
||||
|
||||
// Either call forget or the worker loop to keep it alive
|
||||
if let Err(e) = workflow.take_download_loop().run_loop().await {
|
||||
error!("Worker loop errored {:?}", e)
|
||||
}
|
||||
//std::mem::forget(workflow);
|
||||
pub fn create_scheduler() -> *mut IOScheduler {
|
||||
let scheduler = Box::new(IOScheduler::create());
|
||||
let scheduler_ptr = Box::into_raw(scheduler);
|
||||
return scheduler_ptr;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn run(workflow_ptr: *mut Workflow) {
|
||||
let workflow: Box<Workflow> = unsafe { Box::from_raw(workflow_ptr) };
|
||||
pub fn new_tessellator_state(workflow_ptr: *mut IOScheduler) -> *mut ThreadLocalTessellatorState {
|
||||
let workflow: Box<IOScheduler> = unsafe { Box::from_raw(workflow_ptr) };
|
||||
let tessellator_state = Box::new(workflow.new_tessellator_state());
|
||||
let tessellator_state_ptr = Box::into_raw(tessellator_state);
|
||||
// Call forget such that workflow does not get deallocated
|
||||
std::mem::forget(workflow);
|
||||
return tessellator_state_ptr;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn tessellate_layers(
|
||||
tessellator_state_ptr: *mut ThreadLocalTessellatorState,
|
||||
request_id: u32,
|
||||
data: Box<[u8]>,
|
||||
) {
|
||||
let tessellator_state: Box<ThreadLocalTessellatorState> =
|
||||
unsafe { Box::from_raw(tessellator_state_ptr) };
|
||||
|
||||
tessellator_state.tessellate_layers(request_id, data);
|
||||
|
||||
// Call forget such that workflow does not get deallocated
|
||||
std::mem::forget(tessellator_state);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn run(workflow_ptr: *mut IOScheduler) {
|
||||
let workflow: Box<IOScheduler> = unsafe { Box::from_raw(workflow_ptr) };
|
||||
let event_loop = EventLoop::new();
|
||||
|
||||
let web_window: WebSysWindow = web_sys::window().unwrap();
|
||||
|
||||
@ -3,9 +3,10 @@ use std::collections::HashSet;
|
||||
use std::default::Default;
|
||||
use std::{cmp, iter};
|
||||
|
||||
use crate::coords::{ViewRegion};
|
||||
use crate::coords::ViewRegion;
|
||||
|
||||
use crate::io::workflow::{LayerResult, TileRequest, Workflow};
|
||||
use crate::io::scheduler::IOScheduler;
|
||||
use crate::io::{LayerResult, TileRequest};
|
||||
use wgpu::{Buffer, Limits, Queue};
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::window::Window;
|
||||
@ -346,7 +347,7 @@ impl RenderState {
|
||||
|
||||
// TODO: Could we draw inspiration from StagingBelt (https://docs.rs/wgpu/latest/wgpu/util/struct.StagingBelt.html)?
|
||||
// TODO: What is StagingBelt for?
|
||||
pub fn upload_tile_geometry(&mut self, workflow: &mut Workflow) {
|
||||
pub fn upload_tile_geometry(&mut self, scheduler: &mut IOScheduler) {
|
||||
let visible_z = self.visible_z();
|
||||
let view_region = self
|
||||
.camera
|
||||
@ -365,7 +366,7 @@ impl RenderState {
|
||||
"water".to_string(),
|
||||
]),
|
||||
};
|
||||
workflow.request_tile(tile_request).unwrap();
|
||||
scheduler.request_tile(tile_request).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,7 +393,7 @@ impl RenderState {
|
||||
for coords in view_region.iter() {
|
||||
let loaded_layers = self.buffer_pool.get_loaded_layers(&coords);
|
||||
|
||||
let layers = workflow.get_tessellated_layers_at(&coords, &loaded_layers);
|
||||
let layers = scheduler.get_tessellated_layers_at(&coords, &loaded_layers);
|
||||
for result in layers {
|
||||
match result {
|
||||
LayerResult::UnavailableLayer { .. } => {}
|
||||
|
||||
61
web/index.ts
61
web/index.ts
@ -1,10 +1,16 @@
|
||||
import init from "./dist/libs/mapr"
|
||||
import init, { create_scheduler, new_tessellator_state, run } from "./dist/libs/mapr"
|
||||
// @ts-ignore
|
||||
import {Spector} from "spectorjs"
|
||||
import {WebWorkerMessageType} from "./types"
|
||||
|
||||
declare var WEBGL: boolean
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
fetch_tile: (url: string, x: number, y: number, z: number, callback: (data: Uint8Array) => void) => void;
|
||||
}
|
||||
}
|
||||
|
||||
const isWebGLSupported = () => {
|
||||
try {
|
||||
const canvas = document.createElement('canvas')
|
||||
@ -51,37 +57,54 @@ const checkRequirements = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
const preventDefaultTouchActions = () => {
|
||||
document.body.querySelectorAll("canvas").forEach(canvas => {
|
||||
canvas.addEventListener("touchstart", e => e.preventDefault())
|
||||
canvas.addEventListener("touchmove", e => e.preventDefault())
|
||||
})
|
||||
}
|
||||
|
||||
const registerServiceWorker = () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register(new URL('./service-worker.ts', import.meta.url))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const start = async () => {
|
||||
if (!checkRequirements()) {
|
||||
return
|
||||
}
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register(new URL('./service-worker.ts', import.meta.url))
|
||||
})
|
||||
}
|
||||
|
||||
document.body.querySelectorAll("canvas").forEach(canvas => {
|
||||
canvas.addEventListener("touchstart", e => e.preventDefault())
|
||||
canvas.addEventListener("touchmove", e => e.preventDefault())
|
||||
})
|
||||
registerServiceWorker()
|
||||
preventDefaultTouchActions();
|
||||
|
||||
let MEMORY_PAGES = 16 * 1024
|
||||
const memory = new WebAssembly.Memory({initial: 1024, maximum: MEMORY_PAGES, shared: true})
|
||||
const module = await init(undefined, memory)
|
||||
await init(undefined, memory)
|
||||
const schedulerPtr = create_scheduler()
|
||||
|
||||
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
|
||||
type: "module",
|
||||
})
|
||||
const createWorker = (id: number) => {
|
||||
const worker = new Worker(new URL('./worker.ts', import.meta.url), {
|
||||
type: "module",
|
||||
name: `worker_${id}`
|
||||
})
|
||||
worker.postMessage({type: "init", memory} as WebWorkerMessageType)
|
||||
|
||||
worker.postMessage({type: "init", memory} as WebWorkerMessageType)
|
||||
return worker
|
||||
}
|
||||
|
||||
let workflowPtr = module.create_workflow()
|
||||
worker.postMessage({type: "run_worker_loop", workflowPtr: workflowPtr} as WebWorkerMessageType)
|
||||
let workers: [number, Worker][] = Array.from(new Array(2).keys(), (id) => [new_tessellator_state(schedulerPtr), createWorker(id)])
|
||||
|
||||
await module.run(workflowPtr)
|
||||
window.fetch_tile = (url: string, request_id: number) => {
|
||||
const [tessellatorState, worker] = workers[Math.floor(Math.random() * workers.length)]
|
||||
worker.postMessage({type: "fetch_tile", tessellatorState: tessellatorState, url, request_id} as WebWorkerMessageType)
|
||||
}
|
||||
|
||||
|
||||
await run(schedulerPtr)
|
||||
}
|
||||
|
||||
start().then(() => console.log("started via wasm"))
|
||||
|
||||
11
web/types.ts
11
web/types.ts
@ -1 +1,10 @@
|
||||
export type WebWorkerMessageType = { type: 'init', memory: WebAssembly.Memory } | {type: 'run_worker_loop', workflowPtr: number}
|
||||
export type WebWorkerMessageType = {
|
||||
type: 'init',
|
||||
memory: WebAssembly.Memory
|
||||
} | {
|
||||
type: 'fetch_tile',
|
||||
tessellatorState: number,
|
||||
url: string,
|
||||
request_id: number,
|
||||
}
|
||||
|
||||
|
||||
@ -1,25 +1,32 @@
|
||||
import init, { InitOutput } from "./dist/libs/mapr";
|
||||
import {WebWorkerMessageType} from "./types";
|
||||
import init, {InitOutput, tessellate_layers} from "./dist/libs/mapr"
|
||||
import {WebWorkerMessageType} from "./types"
|
||||
|
||||
let module: Promise<InitOutput> = null;
|
||||
let module: Promise<InitOutput> = null
|
||||
|
||||
onmessage = async message => {
|
||||
let messageData: WebWorkerMessageType = message.data;
|
||||
let messageData: WebWorkerMessageType = message.data
|
||||
console.dir(messageData)
|
||||
|
||||
switch (messageData.type) {
|
||||
case "init":
|
||||
if (module != null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
module = init(undefined, messageData.memory);
|
||||
module = init(undefined, messageData.memory)
|
||||
break
|
||||
case "run_worker_loop":
|
||||
let workflowPtr = messageData.workflowPtr;
|
||||
(await module).run_worker_loop(workflowPtr);
|
||||
case "fetch_tile":
|
||||
let {tessellatorState, url, request_id} = messageData
|
||||
await module
|
||||
|
||||
console.log("Fetching from " + self.name)
|
||||
|
||||
let result = await fetch(url)
|
||||
let buffer = await result.arrayBuffer()
|
||||
|
||||
tessellate_layers(tessellatorState, request_id, new Uint8Array(buffer))
|
||||
break
|
||||
default:
|
||||
console.warn("WebWorker received unknown message!")
|
||||
break;
|
||||
|
||||
break
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user