Add documentation

This commit is contained in:
Maximilian Ammann 2022-10-30 20:42:08 +01:00
parent 58c852f096
commit 92301d005e
8 changed files with 105 additions and 16 deletions

View File

@ -12,6 +12,14 @@ use crate::{
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;

View File

@ -97,7 +97,7 @@ impl HeadlessMap {
coords: WorldTileCoords,
source_layers: &[&str],
) -> Result<StoredTile, Error> {
let source_client = &self.kernel.source_client;
let source_client = self.kernel.source_client();
let data = source_client.fetch(&coords).await?.into_boxed_slice();

View File

@ -20,9 +20,12 @@ use crate::{
},
};
/// 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,12 +33,15 @@ 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 {
/// Send a message back to the caller.
fn send(&self, data: Message<T>);
fn source_client(&self) -> &SourceClient<HC>;
@ -46,15 +52,62 @@ pub type AsyncProcedureFuture = Pin<Box<(dyn Future<Output = ()> + Send + 'stati
#[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;
/// 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;
/// 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)]
@ -101,7 +154,7 @@ impl<HC: HttpClient, S: Scheduler> AsyncProcedureCall<HC> for SchedulerAsyncProc
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

View File

@ -3,13 +3,40 @@ use crate::{
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> {
pub map_window_config: E::MapWindowConfig,
pub apc: E::AsyncProcedureCall,
pub scheduler: E::Scheduler,
pub source_client: SourceClient<E::HttpClient>,
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>,

View File

@ -102,7 +102,7 @@ where
where
E: Environment<MapWindowConfig = MWC>,
{
self.initialize(&kernel.map_window_config).await
self.initialize(kernel.map_window_config()).await
}
}
@ -123,7 +123,7 @@ impl<MWC: MapWindowConfig> UninitializedRenderer<MWC> {
where
E: Environment<MapWindowConfig = MWC>,
{
self.initialize_headless(&kernel.map_window_config).await
self.initialize_headless(kernel.map_window_config()).await
}
}

View File

@ -35,7 +35,7 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
..
}: &mut MapContext,
) {
if let Some(result) = self.kernel.apc.receive() {
if let Some(result) = self.kernel.apc().receive() {
match result {
Message::TileTessellated(tranferred) => {
let coords = tranferred.coords();

View File

@ -150,7 +150,8 @@ impl<E: Environment> RequestStage<E> {
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);
}*/
@ -158,7 +159,7 @@ impl<E: Environment> RequestStage<E> {
tile_repository.create_tile(coords);
tracing::info!("new tile request: {}", &coords);
self.kernel.apc.schedule(
self.kernel.apc().call(
Input::TileRequest(TileRequest {
coords,
layers: layers.clone(),

View File

@ -185,7 +185,7 @@ impl AsyncProcedureCall<UsedTransferables, UsedHttpClient> for PassingAsyncProce
self.received.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