s2maps-gpu
Version:
S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.
204 lines (203 loc) • 7.7 kB
JavaScript
import SourceWorker from './source.worker.js';
import TileWorker from './tile.worker.js';
const AVAILABLE_WORKERS = Math.floor((window.navigator.hardwareConcurrency ?? 4) / 2);
/**
* # Worker Pool
*
* Manages the tile workers and the source worker
*
* The Source worker manages sources, builds/fetches requests, and sends them to the tile workers to be processed
*
* The Tile Workers process the raw data into renderable / interactive data for the GPU or end user.
*
* Communications channels are created for:
* - SourceWorker<->TileWorker
* - TileWorker->Worker Pool->Map Object
* - SourceWorker->Worker Pool->Map Object
*
* There is a two way channel for SourceWorker<->TileWorker mostly because of glyphs,images, etc.
*/
export class WorkerPool {
workerCount = Math.max(Math.min(AVAILABLE_WORKERS, 6), 2);
workers = [];
sourceWorker;
maps = {}; // MapID: S2Map
/** setup workers and channels between all */
constructor() {
// create source worker
const sourceWorker = (this.sourceWorker = new SourceWorker());
sourceWorker.onmessage = this.#onMessage.bind(this);
// create process workers
for (let i = 0; i < this.workerCount; i++) {
const tileWorker = new TileWorker();
tileWorker.onmessage = this.#onMessage.bind(this);
this.workers.push(tileWorker);
// build communication channels; port1 can postMessage, and port2 can onMessage
const channelA = new MessageChannel();
const channelB = new MessageChannel();
const portMessage = {
type: 'port',
id: i,
totalWorkers: this.workerCount,
};
sourceWorker.postMessage(portMessage, [channelA.port1, channelB.port2]);
tileWorker.postMessage(portMessage, [channelB.port1, channelA.port2]);
}
}
/**
* Handle messages from the workers (forward them to the appropriate map)
* @param message - the message
*/
#onMessage(message) {
this.maps[message.data.mapID].injectData(message.data);
}
/**
* Add a map to the worker pool for communication
* @param map - the s2map
*/
addMap(map) {
this.maps[map.id] = map;
}
/**
* Request the source worker load a style
* @param mapID - the id of the map
* @param style - the style url to fetch
* @param analytics - basic analytics
* @param apiKey - the api key if needed
* @param urlMap - the url map
*/
requestStyle(mapID, style, analytics, apiKey, urlMap) {
const msg = {
mapID,
type: 'requestStyle',
style,
apiKey,
urlMap,
analytics,
};
this.sourceWorker.postMessage(msg);
}
/**
* Inject a style. The style is already built, so just send it to the workers
* @param mapID - the id of the map
* @param style - the style data
*/
injectStyle(mapID, style) {
const msg = { mapID, type: 'style', style };
this.sourceWorker.postMessage(msg);
for (const worker of this.workers)
worker.postMessage(msg);
}
// NOTE: TEMPORARY SOLUTION :(
/** delete the worker pool. This is a temporary solution as the worker pool should be a singleton. */
delete() {
this.sourceWorker.terminate();
for (const worker of this.workers)
worker.terminate();
window.S2WorkerPool = new WorkerPool();
}
// delete (mapID: string) {
// this.sourceWorker.postMessage({ mapID, type: 'delete' })
// }
/**
* Request tiles
* @param mapID - the id of the map
* @param tiles - the tiles to fetch data for
* @param sources - the sources to fetch data for. If empty request data for all sources
*/
tileRequest(mapID, tiles, sources = []) {
const msg = { mapID, type: 'tilerequest', tiles, sources };
this.sourceWorker.postMessage(msg);
}
/**
* Request temporal tile data
* @param mapID - the id of the map
* @param tiles - the tiles to fetch data for
* @param sourceNames - the sources to fetch data for. If empty request data for all sources
*/
timeRequest(mapID, tiles, sourceNames = []) {
const msg = { mapID, type: 'timerequest', tiles, sourceNames };
this.sourceWorker.postMessage(msg);
}
/**
* Add marker(s) to the map
* @param mapID - the id of the map to add marker(s) to
* @param markers - the marker(s) to add
* @param sourceName - the name of the source to add the marker(s) to
*/
addMarkers(mapID, markers, sourceName) {
const msg = { mapID, type: 'addMarkers', markers, sourceName };
this.sourceWorker.postMessage(msg);
}
/**
* Delete marker(s) from the map
* @param mapID - the id of the map to delete marker(s) from
* @param ids - the id(s) of the marker(s) to delete
* @param sourceName - the name of the source to delete the marker(s) from
*/
deleteMarkers(mapID, ids, sourceName) {
const msg = { mapID, type: 'deleteMarkers', ids, sourceName };
this.sourceWorker.postMessage(msg);
}
/**
* Add a source to the map
* @param mapID - the id of the map to add the source to
* @param sourceName - the name of the source to add the source to
* @param source - the source
* @param tileRequest - the list of tiles of all existing tiles in the map already to build this source data for
*/
addSource(mapID, sourceName, source, tileRequest) {
const msg = { mapID, type: 'addSource', sourceName, source, tileRequest };
this.sourceWorker.postMessage(msg);
for (const worker of this.workers)
worker.postMessage(msg);
}
/**
* Delete a source from the map
* @param mapID - the id of the map to delete the source from
* @param sourceNames - the name(s) of the source(s) to delete
*/
deleteSource(mapID, sourceNames) {
const msg = { mapID, type: 'deleteSource', sourceNames };
this.sourceWorker.postMessage(msg);
for (const worker of this.workers)
worker.postMessage(msg);
}
/**
* Add a style layer to the map
* @param mapID - the id of the map to add the layer to
* @param layer - the style layer
* @param index - the index to add the layer at
* @param tileRequest - the list of tiles of all existing tiles in the map already to adjust
*/
addLayer(mapID, layer, index, tileRequest) {
const msg = { mapID, type: 'addLayer', layer, index, tileRequest };
this.sourceWorker.postMessage(msg);
for (const worker of this.workers)
worker.postMessage(msg);
}
/**
* Delete a style layer from the map
* @param mapID - the id of the map to delete the layer from
* @param index - the index of the style layer to delete
*/
deleteLayer(mapID, index) {
const msg = { mapID, type: 'deleteLayer', index };
this.sourceWorker.postMessage(msg);
for (const worker of this.workers)
worker.postMessage(msg);
}
/**
* Reorder style layers
* @param mapID - the id of the map
* @param layerChanges - the layer changes to make
*/
reorderLayers(mapID, layerChanges) {
const msg = { mapID, type: 'reorderLayers', layerChanges };
this.sourceWorker.postMessage(msg);
for (const worker of this.workers)
worker.postMessage(msg);
}
}
if (window.S2WorkerPool === undefined)
window.S2WorkerPool = new WorkerPool();