@loaders.gl/core 5.0.0-alpha.13 → 5.0.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/dist.dev.js +190 -63
  2. package/dist/dist.min.js +3 -3
  3. package/dist/index.cjs +205 -72
  4. package/dist/index.cjs.map +2 -2
  5. package/dist/lib/api/parse-file.js +2 -0
  6. package/dist/lib/api/parse-file.js.map +1 -1
  7. package/dist/lib/api/parse-in-batches.d.ts.map +1 -1
  8. package/dist/lib/api/parse-in-batches.js +5 -4
  9. package/dist/lib/api/parse-in-batches.js.map +1 -1
  10. package/dist/lib/api/parse.js +4 -2
  11. package/dist/lib/api/parse.js.map +1 -1
  12. package/dist/lib/init.js +1 -1
  13. package/dist/lib/loader-utils/get-fetch-function.d.ts.map +1 -1
  14. package/dist/lib/loader-utils/get-fetch-function.js +3 -1
  15. package/dist/lib/loader-utils/get-fetch-function.js.map +1 -1
  16. package/dist/lib/loader-utils/loader-context.d.ts.map +1 -1
  17. package/dist/lib/loader-utils/loader-context.js +4 -0
  18. package/dist/lib/loader-utils/loader-context.js.map +1 -1
  19. package/dist/lib/loader-utils/option-defaults.d.ts.map +1 -1
  20. package/dist/lib/loader-utils/option-defaults.js +1 -0
  21. package/dist/lib/loader-utils/option-defaults.js.map +1 -1
  22. package/dist/lib/loader-utils/option-utils.js +1 -1
  23. package/dist/lib/loader-utils/option-utils.js.map +1 -1
  24. package/dist/loader-utils/index.cjs +231 -92
  25. package/dist/loader-utils/index.cjs.map +2 -2
  26. package/dist/loader-utils/index.d.ts +1 -1
  27. package/dist/loader-utils/index.d.ts.map +1 -1
  28. package/dist/loader-utils/index.js +1 -1
  29. package/dist/loader-utils/index.js.map +1 -1
  30. package/dist/loader-utils/json-loader.d.ts +1 -1
  31. package/dist/loader-utils/json-loader.js +2 -2
  32. package/dist/loader-utils/lib/log-utils/log.js +1 -1
  33. package/dist/loader-utils/lib/request-utils/request-credentials.d.ts +3 -1
  34. package/dist/loader-utils/lib/request-utils/request-credentials.d.ts.map +1 -1
  35. package/dist/loader-utils/lib/request-utils/request-credentials.js +10 -16
  36. package/dist/loader-utils/lib/request-utils/request-credentials.js.map +1 -1
  37. package/dist/loader-utils/lib/worker-loader-utils/create-loader-worker.d.ts +3 -0
  38. package/dist/loader-utils/lib/worker-loader-utils/create-loader-worker.d.ts.map +1 -1
  39. package/dist/loader-utils/lib/worker-loader-utils/create-loader-worker.js +13 -12
  40. package/dist/loader-utils/lib/worker-loader-utils/create-loader-worker.js.map +1 -1
  41. package/dist/loader-utils/lib/worker-loader-utils/parse-with-worker.d.ts +6 -0
  42. package/dist/loader-utils/lib/worker-loader-utils/parse-with-worker.d.ts.map +1 -1
  43. package/dist/loader-utils/lib/worker-loader-utils/parse-with-worker.js +38 -7
  44. package/dist/loader-utils/lib/worker-loader-utils/parse-with-worker.js.map +1 -1
  45. package/dist/loader-utils/loader-types.d.ts +12 -4
  46. package/dist/loader-utils/loader-types.d.ts.map +1 -1
  47. package/dist/loader-utils/loader-types.js.map +1 -1
  48. package/dist/null-loader.js +1 -1
  49. package/dist/null-worker-node.js +60 -15
  50. package/dist/null-worker.js +59 -15
  51. package/dist/schema-utils/lib/table/tables/convert-table.d.ts +1 -1
  52. package/dist/schema-utils/lib/table/tables/convert-table.js +1 -1
  53. package/dist/worker-utils/index.cjs +170 -49
  54. package/dist/worker-utils/index.cjs.map +2 -2
  55. package/dist/worker-utils/index.d.ts +2 -2
  56. package/dist/worker-utils/index.d.ts.map +1 -1
  57. package/dist/worker-utils/index.js +1 -1
  58. package/dist/worker-utils/index.js.map +1 -1
  59. package/dist/worker-utils/lib/env-utils/version.js +2 -2
  60. package/dist/worker-utils/lib/worker-api/create-worker.d.ts.map +1 -1
  61. package/dist/worker-utils/lib/worker-api/create-worker.js +40 -2
  62. package/dist/worker-utils/lib/worker-api/create-worker.js.map +1 -1
  63. package/dist/worker-utils/lib/worker-api/get-worker-url.d.ts +2 -0
  64. package/dist/worker-utils/lib/worker-api/get-worker-url.d.ts.map +1 -1
  65. package/dist/worker-utils/lib/worker-api/get-worker-url.js +7 -11
  66. package/dist/worker-utils/lib/worker-api/get-worker-url.js.map +1 -1
  67. package/dist/worker-utils/lib/worker-api/process-on-worker.d.ts +6 -0
  68. package/dist/worker-utils/lib/worker-api/process-on-worker.d.ts.map +1 -1
  69. package/dist/worker-utils/lib/worker-api/process-on-worker.js +64 -27
  70. package/dist/worker-utils/lib/worker-api/process-on-worker.js.map +1 -1
  71. package/dist/worker-utils/lib/worker-farm/worker-job.d.ts.map +1 -1
  72. package/dist/worker-utils/lib/worker-farm/worker-job.js +3 -2
  73. package/dist/worker-utils/lib/worker-farm/worker-job.js.map +1 -1
  74. package/dist/worker-utils/lib/worker-farm/worker-pool.d.ts +1 -1
  75. package/dist/worker-utils/lib/worker-farm/worker-pool.d.ts.map +1 -1
  76. package/dist/worker-utils/lib/worker-farm/worker-pool.js +65 -12
  77. package/dist/worker-utils/lib/worker-farm/worker-pool.js.map +1 -1
  78. package/dist/worker-utils/null-worker-node.js +36 -2
  79. package/dist/worker-utils/null-worker-node.js.map +2 -2
  80. package/dist/worker-utils/null-worker.js +36 -2
  81. package/dist/worker-utils/null-worker.js.map +2 -2
  82. package/dist/worker-utils/types.d.ts +25 -4
  83. package/dist/worker-utils/types.d.ts.map +1 -1
  84. package/package.json +4 -4
  85. package/src/lib/api/parse-file.ts +2 -0
  86. package/src/lib/api/parse-in-batches.ts +5 -5
  87. package/src/lib/api/parse.ts +4 -2
  88. package/src/lib/loader-utils/get-fetch-function.ts +4 -2
  89. package/src/lib/loader-utils/loader-context.ts +4 -0
  90. package/src/lib/loader-utils/option-defaults.ts +1 -0
  91. package/src/lib/loader-utils/option-utils.ts +1 -1
  92. package/src/loader-utils/index.ts +1 -0
  93. package/src/loader-utils/json-loader.ts +1 -1
  94. package/src/loader-utils/lib/request-utils/request-credentials.ts +10 -12
  95. package/src/loader-utils/lib/worker-loader-utils/create-loader-worker.ts +38 -15
  96. package/src/loader-utils/lib/worker-loader-utils/parse-with-worker.ts +52 -7
  97. package/src/loader-utils/loader-types.ts +16 -4
  98. package/src/schema-utils/lib/table/tables/convert-table.ts +1 -1
  99. package/src/worker-utils/index.ts +3 -1
  100. package/src/worker-utils/lib/worker-api/create-worker.ts +41 -2
  101. package/src/worker-utils/lib/worker-api/get-worker-url.ts +13 -14
  102. package/src/worker-utils/lib/worker-api/process-on-worker.ts +128 -73
  103. package/src/worker-utils/lib/worker-farm/worker-job.ts +4 -2
  104. package/src/worker-utils/lib/worker-farm/worker-pool.ts +65 -12
  105. package/src/worker-utils/types.ts +26 -3
@@ -16,6 +16,7 @@ import WorkerFarm from '../worker-farm/worker-farm';
16
16
  import type {WorkerPoolTarget} from '../worker-farm/worker-pool';
17
17
  import {
18
18
  getCustomWorkerURL,
19
+ getConfiguredWorkerURL,
19
20
  getDefaultWorkerURL,
20
21
  getWorkerURL,
21
22
  getWorkerName
@@ -55,6 +56,22 @@ export function canProcessOnWorker(worker: WorkerObject, options?: WorkerOptions
55
56
  );
56
57
  }
57
58
 
59
+ /**
60
+ * Checks whether a worker supports stateful batch processing.
61
+ * @param worker Worker descriptor declaring built-in batch support.
62
+ * @param options Worker options, including an explicit capability for custom URLs or source.
63
+ */
64
+ export function canProcessOnWorkerInBatches(
65
+ worker: WorkerObject,
66
+ options: WorkerOptions = {}
67
+ ): boolean {
68
+ if (!canProcessOnWorker(worker, options)) return false;
69
+ if (options.workerInBatches !== undefined) return options.workerInBatches;
70
+ return Boolean(
71
+ worker.workerInBatches && !options.source && !getConfiguredWorkerURL(worker, options)
72
+ );
73
+ }
74
+
58
75
  /**
59
76
  * This function expects that the worker thread sends certain messages,
60
77
  * Creating such a worker can be automated if the worker is wrapper by a call to
@@ -77,18 +94,15 @@ export async function processOnWorker(
77
94
  const job = await workerPool.startJob(
78
95
  jobName,
79
96
  // eslint-disable-next-line
80
- onMessage.bind(null, context)
97
+ onMessage.bind(null, context, options),
98
+ undefined,
99
+ options.signal
81
100
  );
82
101
 
83
- const abortJob = (): void => job.abort(getAbortReason(options.signal));
84
- options.signal?.addEventListener('abort', abortJob, {once: true});
85
- if (options.signal?.aborted) {
86
- abortJob();
87
- }
88
-
89
102
  try {
103
+ if (options.signal?.aborted) job.abort(getAbortReason(options.signal));
90
104
  // AbortSignal cannot be structured-cloned. It controls the job on this thread only.
91
- const {signal: _signal, ...workerOptions} = options;
105
+ const {signal: _signal, onProgress: _onProgress, ...workerOptions} = options;
92
106
  const transferableOptions = getTransferListForWriter(workerOptions);
93
107
  const transferableContext = getTransferListForWriter(jobContext);
94
108
  if (job.isRunning) {
@@ -101,8 +115,9 @@ export async function processOnWorker(
101
115
 
102
116
  const result = await job.result;
103
117
  return result.result;
104
- } finally {
105
- options.signal?.removeEventListener('abort', abortJob);
118
+ } catch (error) {
119
+ job.abort(error);
120
+ throw error;
106
121
  }
107
122
  }
108
123
 
@@ -140,9 +155,18 @@ async function* processOnWorkerInBatchesIterator<InputBatch, OutputBatch>(
140
155
  const inputIterator = getAsyncIterator(input);
141
156
  let outputFinished = false;
142
157
  let inputFinished = false;
158
+ let inputCleanupRequested = false;
143
159
  let inputRequest = Promise.resolve();
144
160
  const batchJobFailed = new Error('Worker batch job failed');
145
161
 
162
+ /** Requests cleanup even when the output consumer is paused after yielding a batch. */
163
+ const closeJobInput = (): void => {
164
+ if (!inputFinished && !inputCleanupRequested) {
165
+ inputCleanupRequested = true;
166
+ closeInputIterator(inputIterator);
167
+ }
168
+ };
169
+
146
170
  const finishOutput = (): void => {
147
171
  if (!outputFinished) {
148
172
  outputFinished = true;
@@ -157,66 +181,76 @@ async function* processOnWorkerInBatchesIterator<InputBatch, OutputBatch>(
157
181
  }
158
182
  };
159
183
 
160
- const job = await workerPool.startJob(
161
- options.jobName || worker.name,
162
- (activeJob, type, payload) => {
163
- switch (type) {
164
- case 'input-request':
165
- inputRequest = inputRequest
166
- .then(async () => {
167
- const nextBatch = await inputIterator.next();
168
- if (!activeJob.isRunning) {
169
- return;
170
- }
171
- if (nextBatch.done) {
172
- inputFinished = true;
173
- activeJob.postMessage('input-done', {});
174
- } else {
175
- activeJob.postMessage('input-batch', {input: nextBatch.value});
176
- }
177
- })
178
- .catch(error => activeJob.abort(error));
179
- break;
180
-
181
- case 'output-batch':
182
- outputBatches.push(payload.result as OutputBatch);
183
- break;
184
-
185
- case 'done':
186
- activeJob.done(payload);
187
- finishOutput();
188
- break;
189
-
190
- case 'error': {
191
- const error = new Error(payload.error || 'Worker batch processing failed');
192
- activeJob.error(error);
193
- failOutput(error);
194
- break;
195
- }
196
-
197
- case 'process':
198
- void onMessage(context, activeJob, type, payload);
199
- break;
200
-
201
- default: {
202
- const error = new Error(`Unexpected worker batch message: ${type}`);
203
- activeJob.error(error);
204
- failOutput(error);
205
- break;
184
+ const job = await workerPool
185
+ .startJob(
186
+ options.jobName || worker.name,
187
+ (activeJob, type, payload) => {
188
+ switch (type) {
189
+ case 'progress':
190
+ reportProgress(activeJob, options, payload);
191
+ break;
192
+
193
+ case 'input-request':
194
+ inputRequest = inputRequest
195
+ .then(async () => {
196
+ if (!activeJob.isRunning) return;
197
+ const nextBatch = await inputIterator.next();
198
+ if (!activeJob.isRunning) {
199
+ return;
200
+ }
201
+ if (nextBatch.done) {
202
+ inputFinished = true;
203
+ activeJob.postMessage('input-done', {});
204
+ } else {
205
+ activeJob.postMessage('input-batch', {input: nextBatch.value});
206
+ }
207
+ })
208
+ .catch(error => activeJob.abort(error));
209
+ break;
210
+
211
+ case 'output-batch':
212
+ outputBatches.push(payload.result as OutputBatch);
213
+ break;
214
+
215
+ case 'done':
216
+ activeJob.done(payload);
217
+ finishOutput();
218
+ break;
219
+
220
+ case 'error': {
221
+ const error = new Error(payload.error || 'Worker batch processing failed');
222
+ activeJob.error(error);
223
+ failOutput(error);
224
+ break;
225
+ }
226
+
227
+ case 'process':
228
+ void onMessage(context, options, activeJob, type, payload);
229
+ break;
230
+
231
+ default: {
232
+ const error = new Error(`Unexpected worker batch message: ${type}`);
233
+ activeJob.error(error);
234
+ failOutput(error);
235
+ break;
236
+ }
206
237
  }
207
- }
208
- }
209
- );
238
+ },
239
+ undefined,
240
+ options.signal
241
+ )
242
+ .catch(error => {
243
+ closeJobInput();
244
+ throw error;
245
+ });
210
246
 
211
- const abortJob = (): void => job.abort(getAbortReason(options.signal));
212
- options.signal?.addEventListener('abort', abortJob, {once: true});
213
- if (options.signal?.aborted) {
214
- abortJob();
215
- }
216
- void job.result.catch(failOutput);
247
+ void job.result.catch(error => {
248
+ failOutput(error);
249
+ closeJobInput();
250
+ });
217
251
 
218
252
  try {
219
- const {signal: _signal, ...workerOptions} = options;
253
+ const {signal: _signal, onProgress: _onProgress, ...workerOptions} = options;
220
254
  if (job.isRunning) {
221
255
  job.postMessage('process-in-batches', {
222
256
  options: getTransferListForWriter(workerOptions),
@@ -239,13 +273,10 @@ async function* processOnWorkerInBatchesIterator<InputBatch, OutputBatch>(
239
273
  }
240
274
  await job.result;
241
275
  } finally {
242
- options.signal?.removeEventListener('abort', abortJob);
243
276
  if (job.isRunning) {
244
277
  job.abort(createAbortError('Worker batch iterator was closed'));
245
278
  }
246
- if (!inputFinished) {
247
- closeInputIterator(inputIterator);
248
- }
279
+ closeJobInput();
249
280
  }
250
281
  }
251
282
 
@@ -259,7 +290,7 @@ function throwIfAborted(signal?: AbortSignal): void {
259
290
 
260
291
  /** Returns a caller-provided abort reason or a cross-runtime `AbortError`. */
261
292
  function getAbortReason(signal?: AbortSignal): unknown {
262
- return signal?.reason ?? createAbortError('Worker job was aborted');
293
+ return signal ? signal.reason : createAbortError('Worker job was aborted');
263
294
  }
264
295
 
265
296
  /** Creates an abort error without requiring the DOMException global. */
@@ -353,11 +384,16 @@ function getWorkerPoolTarget(
353
384
  */
354
385
  async function onMessage(
355
386
  context: WorkerContext,
387
+ options: ProcessOnWorkerOptions,
356
388
  job: WorkerJob,
357
389
  type: WorkerMessageType,
358
390
  payload: WorkerMessagePayload
359
391
  ) {
360
392
  switch (type) {
393
+ case 'progress':
394
+ reportProgress(job, options, payload);
395
+ break;
396
+
361
397
  case 'done':
362
398
  // Worker is done
363
399
  job.done(payload);
@@ -370,13 +406,18 @@ async function onMessage(
370
406
 
371
407
  case 'process':
372
408
  // Worker is asking for us (main thread) to process something
373
- const {id, input, options} = payload;
409
+ const {id, input, options: processOptions} = payload;
374
410
  try {
375
411
  if (!context.process) {
376
412
  job.postMessage('error', {id, error: 'Worker not set up to process on main thread'});
377
413
  return;
378
414
  }
379
- const result = await context.process(input, options, undefined, payload.context || {});
415
+ const result = await context.process(
416
+ input,
417
+ {...processOptions, signal: options.signal, onProgress: options.onProgress},
418
+ undefined,
419
+ payload.context || {}
420
+ );
380
421
  job.postMessage('done', {id, result});
381
422
  } catch (error) {
382
423
  const message = error instanceof Error ? error.message : 'unknown error';
@@ -411,3 +452,17 @@ function onPreloadMessage(job: WorkerJob, type: WorkerMessageType, payload: Work
411
452
  console.warn(`process-on-worker: unknown preload message ${type}`);
412
453
  }
413
454
  }
455
+
456
+ /** Delivers progress; callback failures terminate the job and settle its caller. */
457
+ function reportProgress(
458
+ job: WorkerJob,
459
+ options: ProcessOnWorkerOptions,
460
+ payload: WorkerMessagePayload
461
+ ): void {
462
+ if (!job.isRunning || !payload.progress) return;
463
+ try {
464
+ options.onProgress?.(payload.progress);
465
+ } catch (error) {
466
+ job.abort(error);
467
+ }
468
+ }
@@ -32,6 +32,7 @@ export default class WorkerJob {
32
32
  * @param data any data structure, ideally consisting mostly of transferrable objects
33
33
  */
34
34
  postMessage(type: WorkerMessageType, payload: WorkerMessagePayload): void {
35
+ if (!this.isRunning) return;
35
36
  this.workerThread.postMessage({
36
37
  source: 'loaders.gl', // Lets worker ignore unrelated messages
37
38
  type,
@@ -69,9 +70,10 @@ export default class WorkerJob {
69
70
  if (!this.isRunning) {
70
71
  return;
71
72
  }
72
- const error = reason ?? createAbortError(`Worker job "${this.name}" was aborted`);
73
73
  this.workerThread.destroy();
74
- this.error(error);
74
+ this.error(
75
+ arguments.length ? reason : createAbortError(`Worker job "${this.name}" was aborted`)
76
+ );
75
77
  }
76
78
  }
77
79
 
@@ -50,6 +50,10 @@ type QueuedJob = {
50
50
  onError: OnError;
51
51
  onStart: (value: any) => void; // Resolve job start promise
52
52
  onReject: (reason: unknown) => void; // Reject job start promise
53
+ /** Removes the queued cancellation listener when the job leaves the queue. */
54
+ removeAbortListener: () => void;
55
+ /** Cancels this queued or active job. */
56
+ signal?: AbortSignal;
53
57
  };
54
58
 
55
59
  /**
@@ -99,9 +103,13 @@ export default class WorkerPool {
99
103
  */
100
104
  destroy(reason: unknown = new Error('Worker pool was destroyed')): void {
101
105
  // Abort active jobs so callers receive an error when a development worker is invalidated.
106
+ this.isDestroyed = true;
102
107
  this.activeJobs.forEach(job => job.abort(reason));
103
108
  const queuedJobs = this.jobQueue.splice(0);
104
- queuedJobs.forEach(queuedJob => queuedJob.onReject(reason));
109
+ queuedJobs.forEach(queuedJob => {
110
+ queuedJob.removeAbortListener();
111
+ queuedJob.onReject(reason);
112
+ });
105
113
  this.idleQueue.forEach(worker => worker.destroy());
106
114
  this.isDestroyed = true;
107
115
  }
@@ -129,15 +137,37 @@ export default class WorkerPool {
129
137
  async startJob(
130
138
  name: string,
131
139
  onMessage: OnMessage = (job, type, data) => job.done(data),
132
- onError: OnError = (job, error) => job.error(error)
140
+ onError: OnError = (job, error) => job.error(error),
141
+ signal?: AbortSignal
133
142
  ): Promise<WorkerJob> {
143
+ if (signal?.aborted) {
144
+ throw signal.reason;
145
+ }
134
146
  if (this.isDestroyed) {
135
147
  return Promise.reject(new Error('Worker pool was destroyed'));
136
148
  }
137
149
  // Promise resolves when thread starts working on this job
138
150
  const startPromise = new Promise<WorkerJob>((onStart, onReject) => {
139
151
  // Promise resolves when thread completes or fails working on this job
140
- this.jobQueue.push({name, onMessage, onError, onStart, onReject});
152
+ const abortQueuedJob = (): void => {
153
+ const index = this.jobQueue.indexOf(queuedJob);
154
+ if (index >= 0) {
155
+ this.jobQueue.splice(index, 1);
156
+ queuedJob.removeAbortListener();
157
+ onReject(signal?.reason);
158
+ }
159
+ };
160
+ const queuedJob: QueuedJob = {
161
+ name,
162
+ onMessage,
163
+ onError,
164
+ onStart,
165
+ onReject,
166
+ signal,
167
+ removeAbortListener: () => signal?.removeEventListener('abort', abortQueuedJob)
168
+ };
169
+ this.jobQueue.push(queuedJob);
170
+ signal?.addEventListener('abort', abortQueuedJob, {once: true});
141
171
  return this;
142
172
  });
143
173
  this._startQueuedJob(); // eslint-disable-line @typescript-eslint/no-floating-promises
@@ -155,7 +185,16 @@ export default class WorkerPool {
155
185
  return;
156
186
  }
157
187
 
158
- const workerThread = this._getAvailableWorker();
188
+ let workerThread: WorkerThread | null;
189
+ try {
190
+ workerThread = this._getAvailableWorker();
191
+ } catch (error) {
192
+ const queuedJob = this.jobQueue.shift();
193
+ queuedJob?.removeAbortListener();
194
+ queuedJob?.onReject(error);
195
+ void this._startQueuedJob();
196
+ return;
197
+ }
159
198
  if (!workerThread) {
160
199
  return;
161
200
  }
@@ -174,11 +213,17 @@ export default class WorkerPool {
174
213
 
175
214
  // Create a worker job to let the app access thread and manage job completion
176
215
  const job = new WorkerJob(queuedJob.name, workerThread);
216
+ queuedJob.removeAbortListener();
217
+ const abortJob = (): void => job.abort(queuedJob.signal?.reason);
218
+ queuedJob.signal?.addEventListener('abort', abortJob, {once: true});
219
+ if (queuedJob.signal?.aborted) abortJob();
177
220
  this.activeJobs.add(job);
178
221
  workerThread.ref();
179
222
 
180
223
  // Set the worker thread's message handlers
181
- workerThread.onMessage = data => queuedJob.onMessage(job, data.type, data.payload);
224
+ workerThread.onMessage = data => {
225
+ if (job.isRunning) queuedJob.onMessage(job, data.type, data.payload);
226
+ };
182
227
  workerThread.onError = error => queuedJob.onError(job, error);
183
228
 
184
229
  // Resolve the start promise so that the app can start sending messages to worker
@@ -191,9 +236,12 @@ export default class WorkerPool {
191
236
  // The job result promise carries worker errors back to the caller; do not duplicate-log
192
237
  // handled rejections here.
193
238
  } finally {
239
+ queuedJob.signal?.removeEventListener('abort', abortJob);
194
240
  this.activeJobs.delete(job);
195
241
  this.returnWorkerToQueue(workerThread);
196
242
  }
243
+ } else {
244
+ this.returnWorkerToQueue(workerThread);
197
245
  }
198
246
  }
199
247
 
@@ -244,13 +292,18 @@ export default class WorkerPool {
244
292
  if (this.count < this._getMaxConcurrency()) {
245
293
  this.count++;
246
294
  const name = `${this.name.toLowerCase()} (#${this.count} of ${this.maxConcurrency})`;
247
- return new WorkerThread({
248
- name,
249
- source: this.source,
250
- url: this.url,
251
- getUrl: this.getUrl,
252
- loadWorker: this.loadWorker
253
- });
295
+ try {
296
+ return new WorkerThread({
297
+ name,
298
+ source: this.source,
299
+ url: this.url,
300
+ getUrl: this.getUrl,
301
+ loadWorker: this.loadWorker
302
+ });
303
+ } catch (error) {
304
+ this.count--;
305
+ throw error;
306
+ }
254
307
  }
255
308
 
256
309
  // No worker available, have to wait
@@ -2,13 +2,29 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- /**
6
- * Worker Options
7
- */
5
+ /** Progress measured independently within a named phase. */
6
+ export type WorkerProgress = {
7
+ /** Operation being measured. */
8
+ phase: string;
9
+ /** Work completed in this phase. */
10
+ completed: number;
11
+ /** Expected work, when known. */
12
+ total?: number;
13
+ /** Measurement unit, such as bytes, rows, or tasks. */
14
+ unit: string;
15
+ };
16
+
17
+ /** Shared worker execution options. */
8
18
  export type WorkerOptions = {
19
+ /** Cancels queued or running work. */
20
+ signal?: AbortSignal;
21
+ /** Receives phase progress. Throwing aborts the job with that error. */
22
+ onProgress?: (progress: WorkerProgress) => void;
9
23
  // Worker farm options
10
24
  CDN?: string | null;
11
25
  worker?: boolean;
26
+ /** Declares batch-protocol support for a custom worker, or disables batch offloading. */
27
+ workerInBatches?: boolean;
12
28
  maxConcurrency?: number;
13
29
  maxMobileConcurrency?: number;
14
30
  reuseWorkers?: boolean;
@@ -26,6 +42,8 @@ export type WorkerOptions = {
26
42
  export type LoadWorker = () => Worker | null;
27
43
 
28
44
  export type WorkerContext = {
45
+ /** Reports progress from the active worker job. */
46
+ reportProgress?: (progress: WorkerProgress) => void;
29
47
  process?: Process;
30
48
  processInBatches?: ProcessInBatches;
31
49
  };
@@ -58,6 +76,8 @@ export type WorkerObject = {
58
76
  module: string;
59
77
  version: string;
60
78
  worker?: string | boolean;
79
+ /** Whether the built-in worker accepts stateful batch requests. */
80
+ workerInBatches?: boolean;
61
81
  /** Browser worker filename when it differs from the worker id. */
62
82
  workerFile?: string;
63
83
  /** Creates a built-in browser worker, typically using `type: 'module'`. */
@@ -93,6 +113,7 @@ export type WorkerObject = {
93
113
  <= error
94
114
  */
95
115
  export type WorkerMessageType =
116
+ | 'progress'
96
117
  | 'preload'
97
118
  | 'process'
98
119
  | 'done'
@@ -105,6 +126,8 @@ export type WorkerMessageType =
105
126
  | 'output-ack';
106
127
 
107
128
  export type WorkerMessagePayload = {
129
+ /** Phase progress from the worker. */
130
+ progress?: WorkerProgress;
108
131
  id?: number;
109
132
  options?: {[key: string]: any};
110
133
  context?: WorkerJobContext;