Skip to content

Commit b2d2eaf

Browse files
committed
fix: detached buffer during contouring in worker
1 parent 22053ae commit b2d2eaf

File tree

3 files changed

+59
-21
lines changed

3 files changed

+59
-21
lines changed

src/om-protocol.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import { type GetResourceResponse, type RequestParameters } from 'maplibre-gl';
22

33
import { setupGlobalCache } from '@openmeteo/file-reader';
44

5-
import { WorkerPool } from './worker-pool';
6-
7-
import { getIndexFromLatLong } from './utils/math';
5+
import { TilePromise, WorkerPool } from './worker-pool';
86

97
import {
108
getBorderPoints,
@@ -119,7 +117,7 @@ const getTile = async (
119117
{ z, x, y }: TileIndex,
120118
omUrl: string,
121119
type: 'image' | 'arrayBuffer'
122-
): Promise<ImageBitmap> => {
120+
): TilePromise => {
123121
const key = `${omUrl}/${tileSize}/${z}/${x}/${y}`;
124122

125123
return await workerPool.requestTile({
@@ -305,7 +303,7 @@ export const omProtocol = async (
305303
params: RequestParameters,
306304
abortController?: AbortController,
307305
omProtocolSettings = defaultOmProtocolSettings
308-
): Promise<GetResourceResponse<TileJSON | ImageBitmap>> => {
306+
): Promise<GetResourceResponse<TileJSON | ImageBitmap | ArrayBuffer>> => {
309307
if (params.type == 'json') {
310308
try {
311309
await initOMFile(params.url, omProtocolSettings);

src/worker-pool.ts

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,26 @@ export interface TileRequest {
2323
mapBounds: number[];
2424
}
2525

26-
export type TileResponse = {
26+
export type TileResponse = ImageBitmap | ArrayBuffer;
27+
export type TilePromise = Promise<TileResponse>;
28+
29+
export type WorkerResponse = {
2730
type: 'returnImage' | 'returnArrayBuffer';
28-
tile: ImageBitmap;
31+
tile: TileResponse;
2932
key: string;
3033
};
3134

3235
export class WorkerPool {
3336
private workers: Worker[] = [];
3437
private nextWorker = 0;
3538
/** Stores pending tile requests by key to avoid duplicate requests for the same tile */
36-
private pendingTiles = new Map<string, Promise<ImageBitmap>>();
39+
private pendingTiles = new Map<string, TilePromise>();
3740

38-
/** Stores resolve functions for pending promises, used to fulfill promises when worker responses arrive */
39-
private resolvers = new Map<string, (tile: ImageBitmap) => void>();
41+
/**
42+
* Stores an array of resolve functions for each pending key.
43+
* This allows for multiple subscribers for the same tile key.
44+
*/
45+
private resolvers = new Map<string, Array<(tile: TileResponse) => void>>();
4046

4147
constructor() {
4248
if (typeof window === 'undefined' || typeof Worker === 'undefined') {
@@ -47,16 +53,35 @@ export class WorkerPool {
4753
for (let i = 0; i < workerCount; i++) {
4854
const worker = new TileWorker();
4955
worker.onmessage = (message: MessageEvent) => this.handleMessage(message);
56+
worker.onerror = (error: ErrorEvent) => this.handleError(error);
5057
this.workers.push(worker);
5158
}
5259
}
5360

5461
private handleMessage(message: MessageEvent): void {
55-
const data = message.data as TileResponse;
62+
const data = message.data as WorkerResponse;
5663
if (data.type.startsWith('return')) {
57-
const resolve = this.resolvers.get(data.key);
58-
if (resolve) {
59-
resolve(data.tile);
64+
const resolveFns = this.resolvers.get(data.key);
65+
66+
if (resolveFns && resolveFns.length > 0) {
67+
const originalTile = data.tile;
68+
69+
// The first subscriber can receive the original (transferred) buffer.
70+
const firstResolver = resolveFns.shift()!;
71+
firstResolver(originalTile);
72+
73+
// All other subscribers must receive a clone.
74+
resolveFns.forEach((resolve) => {
75+
if (originalTile instanceof ArrayBuffer) {
76+
// Create a copy for each subsequent subscriber.
77+
resolve(originalTile.slice(0));
78+
} else {
79+
// ImageBitmaps are safe to share without cloning.
80+
resolve(originalTile);
81+
}
82+
});
83+
84+
// Clean up now that all promises for this key are resolved.
6085
this.resolvers.delete(data.key);
6186
this.pendingTiles.delete(data.key);
6287
} else {
@@ -65,6 +90,11 @@ export class WorkerPool {
6590
}
6691
}
6792

93+
private handleError(error: ErrorEvent): void {
94+
// Simplified error handler: just log for now
95+
console.error('Error in worker:', error.message, error);
96+
}
97+
6898
public getNextWorker(): Worker | undefined {
6999
if (this.workers.length === 0) return undefined;
70100

@@ -73,22 +103,29 @@ export class WorkerPool {
73103
return worker;
74104
}
75105

76-
public requestTile(request: TileRequest): Promise<ImageBitmap> {
77-
const existingPromise = this.pendingTiles.get(request.key);
78-
if (existingPromise) {
79-
return existingPromise;
106+
public requestTile(request: TileRequest): TilePromise {
107+
// If a request for this key is already in flight...
108+
if (this.pendingTiles.has(request.key)) {
109+
// ...create a new promise and add its resolver to the list for this key.
110+
return new Promise<TileResponse>((resolve) => {
111+
this.resolvers.get(request.key)!.push(resolve);
112+
});
80113
}
81114

115+
// This is the first request for this key.
82116
const worker = this.getNextWorker();
83117
if (!worker) {
84118
return Promise.reject(new Error('No workers available (likely running in SSR)'));
85119
}
86120

87-
const promise = new Promise<ImageBitmap>((resolve) => {
88-
this.resolvers.set(request.key, resolve);
121+
// Create the promise and store its resolver in a new array.
122+
const promise = new Promise<TileResponse>((resolve) => {
123+
this.resolvers.set(request.key, [resolve]);
89124
});
90125

126+
// Store the master promise to indicate a request is in-flight.
91127
this.pendingTiles.set(request.key, promise);
128+
92129
worker.postMessage(request);
93130

94131
return promise;

src/worker.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ self.onmessage = async (message) => {
295295
}
296296

297297
const buffer = pbf.finish();
298-
postMessage({ type: 'returnArrayBuffer', tile: buffer, key: key }, { transfer: [buffer] });
298+
postMessage(
299+
{ type: 'returnArrayBuffer', tile: buffer.buffer, key: key },
300+
{ transfer: [buffer.buffer] }
301+
);
299302
}
300303
};

0 commit comments

Comments
 (0)