layer/geotiff/workerPool.tstypescript
import { Pool } from "geotiff";
import type { IGeoTIFFReaderParams } from "./types";
export const MAX_WORKER_POOL_SIZE = 4;
/**
* A shared worker pool.
* Call release() when done.
* The pool is destroyed when no one uses it anymore.
*/
export interface IWorkerPoolHandle {
pool: Pool | null;
release(): void;
}
const NO_WORKER_POOL: IWorkerPoolHandle = {
pool: null,
release: () => {}
};
let _sharedPool: Pool | null = null;
let _sharedPoolRefs: number = 0;
let _workerSupport: boolean | undefined = undefined;
function detectWorkerSupport(): boolean {
if (typeof Worker === "undefined" || typeof Worker.prototype === "undefined") {
return false;
}
if (typeof Worker.prototype.addEventListener !== "function" || typeof Worker.prototype.terminate !== "function") {
return false;
}
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
return false;
}
try {
const url = URL.createObjectURL(new Blob([""], { type: "application/javascript" }));
const probe = new Worker(url);
probe.terminate();
URL.revokeObjectURL(url);
return true;
} catch {
return false;
}
}
function isWorkerSupported(): boolean {
if (_workerSupport === undefined) {
_workerSupport = detectWorkerSupport();
}
return _workerSupport;
}
export function defaultWorkerPoolSize(): number {
const concurrency = typeof navigator !== "undefined" ? navigator.hardwareConcurrency || 2 : 2;
return Math.min(concurrency, MAX_WORKER_POOL_SIZE);
}
function destroyPool(pool: Pool): void {
// Pool.destroy() is async and rejects if worker creation failed
Promise.resolve(pool.destroy()).catch(() => {
//empty
});
}
function acquireSharedPool(): IWorkerPoolHandle {
if (!_sharedPool) {
_sharedPool = new Pool(defaultWorkerPoolSize());
}
const pool = _sharedPool;
_sharedPoolRefs++;
let released = false;
return {
pool,
release: () => {
if (released) {
return;
}
released = true;
_sharedPoolRefs--;
if (_sharedPoolRefs === 0 && _sharedPool === pool) {
_sharedPool = null;
destroyPool(pool);
}
}
};
}
/**
* Gets a worker pool for a GeoTIFF reader.
*
* Uses the provided geotiffWorkerPool without destroying it.
* Creates a dedicated pool if workerPoolSize is set.
* Otherwise, shares a pool between readers.
*
* Returns null if workers are unavailable (decoding runs on the main thread).
*/
export function acquireWorkerPool(options: IGeoTIFFReaderParams): IWorkerPoolHandle {
if (options.geotiffWorkerPool) {
return {
pool: options.geotiffWorkerPool,
release: () => {}
};
}
if (!isWorkerSupported()) {
return NO_WORKER_POOL;
}
if (options.workerPoolSize === undefined) {
return acquireSharedPool();
}
// Zero disables workers, the same contract geotiff Pool follows
if (options.workerPoolSize <= 0) {
return NO_WORKER_POOL;
}
const pool = new Pool(options.workerPoolSize);
return {
pool,
release: () => destroyPool(pool)
};
}