@draco-rust/decoder 0.2.0 → 0.3.0

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.
package/README.md CHANGED
@@ -60,7 +60,9 @@ if (!result.success) throw new Error(result.error);
60
60
  const { index, attributes } = result; // attributes[i]: { name, array, itemSize, normalized, ... }
61
61
  ```
62
62
 
63
- Without the second argument every attribute comes back in its own type.
63
+ Without the second argument every attribute comes back in its own type. The
64
+ index is a `Uint32Array`; a third argument `{ index: "auto" }` makes it a
65
+ `Uint16Array` wherever the points number at most 65536, half the bytes.
64
66
 
65
67
  ### On workers
66
68
 
@@ -80,6 +82,10 @@ them. The stream is copied to the worker; `pool.decode(bytes, requests, {
80
82
  transfer: true })` hands over the buffer itself instead. The pool runs on Web
81
83
  Workers, on Node's worker threads (Node 20.16 and later), and from a CDN. With
82
84
  `workers: 0`, or where there are no workers, it decodes on the calling thread.
85
+ `pool.decode` also takes `index`, as `decode_draco` does, and an `AbortSignal`
86
+ as `signal`: aborting rejects the promise at once. A decode a worker has
87
+ already started still runs to its end and its result is dropped; one not yet
88
+ sent is never sent.
83
89
  A bundler that follows `new URL("./worker.js", import.meta.url)`, as Vite and
84
90
  webpack do, emits the worker on its own.
85
91
 
@@ -114,7 +120,7 @@ package's other entries do not need it.
114
120
  ### From a CDN, without a bundler
115
121
 
116
122
  ```js
117
- import init, { parse_drc_bytes } from "https://cdn.jsdelivr.net/npm/@draco-rust/decoder@0.2/mesh/index.js";
123
+ import init, { parse_drc_bytes } from "https://cdn.jsdelivr.net/npm/@draco-rust/decoder@0.3/mesh/index.js";
118
124
  await init(); // finds index_bg.wasm next to the script
119
125
  ```
120
126
 
@@ -141,4 +147,4 @@ For a subpath entry, resolve its own file, for example
141
147
  - Licensed under Apache-2.0, like Draco.
142
148
  ## This build
143
149
 
144
- `@draco-rust/decoder@0.2.0` was built from draco-rust commit `8650e1ca`, with draco-core 2.3.2, draco-io 0.6.0, draco-gltf 0.7.0.
150
+ `@draco-rust/decoder@0.3.0` was built from draco-rust commit `4b72cfc8`, with draco-core 2.3.2, draco-io 0.6.0, draco-gltf 0.7.0.
package/index.d.ts CHANGED
@@ -35,41 +35,37 @@ export interface DracoAttribute {
35
35
  normalized: boolean;
36
36
  }
37
37
 
38
+ export interface DracoDecodeOptions {
39
+ /**
40
+ * The index's array: `"Uint32Array"`, the default, or `"auto"`, which uses a
41
+ * `Uint16Array` when every point number fits, at most 65536 points, and
42
+ * halves what a worker sends back.
43
+ */
44
+ index?: "Uint32Array" | "auto";
45
+ }
46
+
38
47
  export interface DracoDecodeResult {
39
48
  success: boolean;
40
49
  error?: string;
41
50
  warnings: string[];
42
51
  geometry?: "mesh" | "point_cloud";
43
52
  /** Triangle corners for a mesh, `null` for a point cloud. */
44
- index: Uint32Array | null;
53
+ index: Uint32Array | Uint16Array | null;
45
54
  attributes: DracoAttribute[];
46
55
  }
47
56
 
48
-
49
-
50
57
  /**
51
- * Decodes a Draco stream and returns its attributes as typed arrays.
52
- *
53
- * `attributes` is `undefined` for every attribute in its own component type,
54
- * or an array of requests `{ name, id, semantic, type }`:
55
- *
56
- * - `id`, a unique id, finds that attribute, as glTF's
57
- * `KHR_draco_mesh_compression` names them; a missing one is an error.
58
- * - `semantic` (`"POSITION"`, `"NORMAL"`, `"COLOR"`, `"TEX_COORD"` or
59
- * `"GENERIC"`) finds the first attribute of that type instead, as a `.drc`
60
- * file is read; a missing one is left out.
61
- * - `type`, a typed array's name such as `"Float32Array"` or
62
- * `"Uint16Array"`, converts the values the way upstream's
63
- * `GetAttributeDataArrayForAllPoints` does; without it the values keep
64
- * their own type. A value the type cannot hold is an error.
65
- *
66
- * The result is `{ success, error, warnings, geometry, index, attributes }`,
67
- * where `geometry` is `"mesh"` or `"point_cloud"` as the stream's header says,
68
- * `index` a `Uint32Array` of triangle corners for a mesh and `null` for a
69
- * point cloud, and each attribute `{ name, uniqueId, semantic, array,
70
- * itemSize, normalized }`.
58
+ * Decodes a Draco stream and returns its attributes as typed arrays: every
59
+ * attribute in its own type, or the ones `attributes` requests, converted as
60
+ * upstream's `GetAttributeDataArrayForAllPoints` converts them.
71
61
  */
72
- export function decode_draco(data: Uint8Array, attributes: DracoAttributeRequest[] | undefined): DracoDecodeResult;
62
+ export function decode_draco(
63
+ data: Uint8Array,
64
+ attributes?: DracoAttributeRequest[],
65
+ options?: DracoDecodeOptions,
66
+ ): DracoDecodeResult;
67
+
68
+
73
69
 
74
70
  /**
75
71
  * Install the panic hook, so a panic reads as a message rather than a trap.
@@ -95,7 +91,7 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
95
91
 
96
92
  export interface InitOutput {
97
93
  readonly memory: WebAssembly.Memory;
98
- readonly decode_draco: (a: number, b: number, c: number) => number;
94
+ readonly decode_draco: (a: number, b: number, c: number, d: number) => number;
99
95
  readonly init: () => void;
100
96
  readonly parse_drc_bytes: (a: number, b: number) => number;
101
97
  readonly supported_extensions: (a: number) => void;
package/index.js CHANGED
@@ -18,17 +18,22 @@
18
18
  *
19
19
  * The result is `{ success, error, warnings, geometry, index, attributes }`,
20
20
  * where `geometry` is `"mesh"` or `"point_cloud"` as the stream's header says,
21
- * `index` a `Uint32Array` of triangle corners for a mesh and `null` for a
22
- * point cloud, and each attribute `{ name, uniqueId, semantic, array,
23
- * itemSize, normalized }`.
21
+ * `index` the triangle corners for a mesh and `null` for a point cloud, and
22
+ * each attribute `{ name, uniqueId, semantic, array, itemSize, normalized }`.
23
+ * The index is a `Uint32Array`, or with `options.index` set to `"auto"` a
24
+ * `Uint16Array` where the points number at most 65536.
25
+ *
26
+ * Its TypeScript declaration is written by hand in the section above, where
27
+ * the trailing parameters can be optional.
24
28
  * @param {Uint8Array} data
25
- * @param {DracoAttributeRequest[] | undefined} attributes
26
- * @returns {DracoDecodeResult}
29
+ * @param {any} attributes
30
+ * @param {object | null} [options]
31
+ * @returns {any}
27
32
  */
28
- export function decode_draco(data, attributes) {
33
+ export function decode_draco(data, attributes, options) {
29
34
  const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_export);
30
35
  const len0 = WASM_VECTOR_LEN;
31
- const ret = wasm.decode_draco(ptr0, len0, addHeapObject(attributes));
36
+ const ret = wasm.decode_draco(ptr0, len0, addHeapObject(attributes), isLikeNone(options) ? 0 : addHeapObject(options));
32
37
  return takeObject(ret);
33
38
  }
34
39
 
package/index_bg.wasm CHANGED
Binary file
@@ -1,7 +1,7 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
- export const decode_draco: (a: number, b: number, c: number) => number;
4
+ export const decode_draco: (a: number, b: number, c: number, d: number) => number;
5
5
  export const init: () => void;
6
6
  export const parse_drc_bytes: (a: number, b: number) => number;
7
7
  export const supported_extensions: (a: number) => void;
package/legacy/index.d.ts CHANGED
@@ -35,41 +35,37 @@ export interface DracoAttribute {
35
35
  normalized: boolean;
36
36
  }
37
37
 
38
+ export interface DracoDecodeOptions {
39
+ /**
40
+ * The index's array: `"Uint32Array"`, the default, or `"auto"`, which uses a
41
+ * `Uint16Array` when every point number fits, at most 65536 points, and
42
+ * halves what a worker sends back.
43
+ */
44
+ index?: "Uint32Array" | "auto";
45
+ }
46
+
38
47
  export interface DracoDecodeResult {
39
48
  success: boolean;
40
49
  error?: string;
41
50
  warnings: string[];
42
51
  geometry?: "mesh" | "point_cloud";
43
52
  /** Triangle corners for a mesh, `null` for a point cloud. */
44
- index: Uint32Array | null;
53
+ index: Uint32Array | Uint16Array | null;
45
54
  attributes: DracoAttribute[];
46
55
  }
47
56
 
48
-
49
-
50
57
  /**
51
- * Decodes a Draco stream and returns its attributes as typed arrays.
52
- *
53
- * `attributes` is `undefined` for every attribute in its own component type,
54
- * or an array of requests `{ name, id, semantic, type }`:
55
- *
56
- * - `id`, a unique id, finds that attribute, as glTF's
57
- * `KHR_draco_mesh_compression` names them; a missing one is an error.
58
- * - `semantic` (`"POSITION"`, `"NORMAL"`, `"COLOR"`, `"TEX_COORD"` or
59
- * `"GENERIC"`) finds the first attribute of that type instead, as a `.drc`
60
- * file is read; a missing one is left out.
61
- * - `type`, a typed array's name such as `"Float32Array"` or
62
- * `"Uint16Array"`, converts the values the way upstream's
63
- * `GetAttributeDataArrayForAllPoints` does; without it the values keep
64
- * their own type. A value the type cannot hold is an error.
65
- *
66
- * The result is `{ success, error, warnings, geometry, index, attributes }`,
67
- * where `geometry` is `"mesh"` or `"point_cloud"` as the stream's header says,
68
- * `index` a `Uint32Array` of triangle corners for a mesh and `null` for a
69
- * point cloud, and each attribute `{ name, uniqueId, semantic, array,
70
- * itemSize, normalized }`.
58
+ * Decodes a Draco stream and returns its attributes as typed arrays: every
59
+ * attribute in its own type, or the ones `attributes` requests, converted as
60
+ * upstream's `GetAttributeDataArrayForAllPoints` converts them.
71
61
  */
72
- export function decode_draco(data: Uint8Array, attributes: DracoAttributeRequest[] | undefined): DracoDecodeResult;
62
+ export function decode_draco(
63
+ data: Uint8Array,
64
+ attributes?: DracoAttributeRequest[],
65
+ options?: DracoDecodeOptions,
66
+ ): DracoDecodeResult;
67
+
68
+
73
69
 
74
70
  /**
75
71
  * Install the panic hook, so a panic reads as a message rather than a trap.
@@ -95,7 +91,7 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
95
91
 
96
92
  export interface InitOutput {
97
93
  readonly memory: WebAssembly.Memory;
98
- readonly decode_draco: (a: number, b: number, c: number) => number;
94
+ readonly decode_draco: (a: number, b: number, c: number, d: number) => number;
99
95
  readonly init: () => void;
100
96
  readonly parse_drc_bytes: (a: number, b: number) => number;
101
97
  readonly supported_extensions: (a: number) => void;
package/legacy/index.js CHANGED
@@ -18,17 +18,22 @@
18
18
  *
19
19
  * The result is `{ success, error, warnings, geometry, index, attributes }`,
20
20
  * where `geometry` is `"mesh"` or `"point_cloud"` as the stream's header says,
21
- * `index` a `Uint32Array` of triangle corners for a mesh and `null` for a
22
- * point cloud, and each attribute `{ name, uniqueId, semantic, array,
23
- * itemSize, normalized }`.
21
+ * `index` the triangle corners for a mesh and `null` for a point cloud, and
22
+ * each attribute `{ name, uniqueId, semantic, array, itemSize, normalized }`.
23
+ * The index is a `Uint32Array`, or with `options.index` set to `"auto"` a
24
+ * `Uint16Array` where the points number at most 65536.
25
+ *
26
+ * Its TypeScript declaration is written by hand in the section above, where
27
+ * the trailing parameters can be optional.
24
28
  * @param {Uint8Array} data
25
- * @param {DracoAttributeRequest[] | undefined} attributes
26
- * @returns {DracoDecodeResult}
29
+ * @param {any} attributes
30
+ * @param {object | null} [options]
31
+ * @returns {any}
27
32
  */
28
- export function decode_draco(data, attributes) {
33
+ export function decode_draco(data, attributes, options) {
29
34
  const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_export);
30
35
  const len0 = WASM_VECTOR_LEN;
31
- const ret = wasm.decode_draco(ptr0, len0, addHeapObject(attributes));
36
+ const ret = wasm.decode_draco(ptr0, len0, addHeapObject(attributes), isLikeNone(options) ? 0 : addHeapObject(options));
32
37
  return takeObject(ret);
33
38
  }
34
39
 
Binary file
@@ -1,7 +1,7 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
- export const decode_draco: (a: number, b: number, c: number) => number;
4
+ export const decode_draco: (a: number, b: number, c: number, d: number) => number;
5
5
  export const init: () => void;
6
6
  export const parse_drc_bytes: (a: number, b: number) => number;
7
7
  export const supported_extensions: (a: number) => void;
@@ -0,0 +1,44 @@
1
+ export interface PoolOptions {
2
+ /**
3
+ * How many workers to run at most; they start as work arrives. Defaults to
4
+ * one less than the cores, between one and four. `0` runs on the calling
5
+ * thread, as does an environment without workers.
6
+ */
7
+ workers?: number;
8
+ /**
9
+ * The entry's wasm, when it is not next to this module where the pool looks
10
+ * for it: a URL, a `Response`, the bytes, or a compiled module.
11
+ */
12
+ wasm?: string | URL | Response | BufferSource | WebAssembly.Module;
13
+ }
14
+
15
+ export interface TaskOptions {
16
+ /**
17
+ * Hand the input's buffers to the worker instead of copying them, leaving
18
+ * them detached here. For buffers nothing else reads afterwards.
19
+ */
20
+ transfer?: boolean;
21
+ /**
22
+ * Abandons the task: the promise rejects with the signal's reason at once.
23
+ * A task a worker has already started still runs to its end, and its result
24
+ * is dropped; one not yet sent is never sent.
25
+ */
26
+ signal?: AbortSignal;
27
+ }
28
+
29
+ export interface Pool {
30
+ /** Compiles the wasm and starts one worker ahead of the first task. */
31
+ preload(): Promise<void>;
32
+ /** Changes the worker limit; idle workers past it stop now, busy ones when done. */
33
+ setWorkerLimit(workers: number): void;
34
+ readonly workerLimit: number;
35
+ /** Stops every worker and rejects what they held. */
36
+ dispose(): void;
37
+ }
38
+
39
+ export interface TaskPool extends Pool {
40
+ /** Runs the task `tasks.js` exports under `name` on a worker. */
41
+ run(name: string, args?: unknown[], options?: TaskOptions): Promise<any>;
42
+ }
43
+
44
+ export function createPool(options?: PoolOptions): TaskPool;
@@ -0,0 +1,250 @@
1
+ // Work off the calling thread: a pool of workers, each running this entry's
2
+ // own module and the tasks its `tasks.js` names. The wasm is compiled once here
3
+ // and handed to every worker; inputs go over by transfer of a copy, or of the
4
+ // caller's own buffers when asked, and results come back by transfer, so a
5
+ // task copies nothing it does not have to.
6
+ import init from './index.js';
7
+ import * as tasks from './tasks.js';
8
+
9
+ /** Leaves a core for the page, and stops at four, past which a page rarely has
10
+ * that many files in flight at once. */
11
+ const defaultWorkers = () =>
12
+ Math.max(1, Math.min(4, (globalThis.navigator?.hardwareConcurrency ?? 4) - 1));
13
+
14
+ const builtin = (name) =>
15
+ typeof process !== 'undefined' && typeof process.getBuiltinModule === 'function'
16
+ ? process.getBuiltinModule(name)
17
+ : undefined;
18
+
19
+ async function compile(wasm) {
20
+ if (wasm instanceof WebAssembly.Module) return wasm;
21
+ let source = wasm ?? new URL('./index_bg.wasm', import.meta.url);
22
+ if (typeof source === 'string' || source instanceof URL) {
23
+ const url = new URL(source, import.meta.url);
24
+ // Node's fetch does not read files.
25
+ const fs = url.protocol === 'file:' ? builtin('node:fs/promises') : undefined;
26
+ if (fs) return WebAssembly.compile(await fs.readFile(url));
27
+ source = await fetch(url);
28
+ }
29
+ if (typeof Response !== 'undefined' && source instanceof Response) {
30
+ if (!source.ok) throw new Error(`failed to fetch ${source.url}: ${source.status}`);
31
+ if (source.headers.get('content-type') === 'application/wasm') {
32
+ return WebAssembly.compileStreaming(source);
33
+ }
34
+ source = await source.arrayBuffer();
35
+ }
36
+ return WebAssembly.compile(source);
37
+ }
38
+
39
+ /** What the pool needs of a worker, the same over a Web Worker and a Node
40
+ * worker thread. */
41
+ function spawn(blobUrl) {
42
+ if (typeof Worker === 'function') {
43
+ const url = new URL('./worker.js', import.meta.url);
44
+ // A bundler recognises this exact form and emits the worker as a chunk.
45
+ const worker =
46
+ typeof location === 'undefined' || url.origin === location.origin
47
+ ? new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })
48
+ : new Worker(blobUrl(url), { type: 'module' });
49
+ return {
50
+ post: (message, transfer) => worker.postMessage(message, transfer),
51
+ listen: (onMessage, onError) => {
52
+ worker.addEventListener('message', (event) => onMessage(event.data));
53
+ worker.addEventListener('error', (event) => {
54
+ event.preventDefault();
55
+ onError(new Error(event.message || 'the worker failed'));
56
+ });
57
+ worker.addEventListener('messageerror', () => onError(new Error('a result could not be read')));
58
+ },
59
+ busy: () => {},
60
+ terminate: () => worker.terminate(),
61
+ };
62
+ }
63
+ const threads = builtin('node:worker_threads');
64
+ if (!threads) return null;
65
+ const worker = new threads.Worker(new URL('./worker.js', import.meta.url));
66
+ // An idle worker does not keep Node running; one with work does.
67
+ worker.unref();
68
+ return {
69
+ post: (message, transfer) => worker.postMessage(message, transfer),
70
+ listen: (onMessage, onError) => {
71
+ worker.on('message', onMessage);
72
+ worker.on('error', onError);
73
+ worker.on('exit', (code) => onError(new Error(`the worker exited with code ${code}`)));
74
+ },
75
+ busy: (busy) => (busy ? worker.ref() : worker.unref()),
76
+ terminate: () => worker.terminate(),
77
+ };
78
+ }
79
+
80
+ const workersAvailable = () =>
81
+ typeof Worker === 'function' || builtin('node:worker_threads') !== undefined;
82
+
83
+ const isPlain = (value) => {
84
+ if (value === null || typeof value !== 'object') return false;
85
+ const proto = Object.getPrototypeOf(value);
86
+ return proto === Object.prototype || proto === null;
87
+ };
88
+
89
+ /**
90
+ * The arguments as they go to a worker, and the buffers that go with them.
91
+ * Without `transfer` every typed array is copied first -- just its own bytes,
92
+ * where structured cloning would copy the whole buffer behind a view -- and
93
+ * the copy is what moves; with it the caller's buffers move and are left
94
+ * detached.
95
+ */
96
+ function outgoing(args, transfer) {
97
+ const buffers = new Set();
98
+ const visit = (value) => {
99
+ if (ArrayBuffer.isView(value)) {
100
+ const view = transfer ? value : value.slice();
101
+ if (!(view.buffer instanceof ArrayBuffer)) return view;
102
+ buffers.add(view.buffer);
103
+ return view;
104
+ }
105
+ if (value instanceof ArrayBuffer) {
106
+ const buffer = transfer ? value : value.slice(0);
107
+ buffers.add(buffer);
108
+ return buffer;
109
+ }
110
+ if (Array.isArray(value)) return value.map(visit);
111
+ if (isPlain(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, visit(item)]));
112
+ return value;
113
+ };
114
+ const sent = args.map(visit);
115
+ return { sent, buffers: [...buffers] };
116
+ }
117
+
118
+ const aborted = (signal) => signal.reason ?? new DOMException('The task was aborted', 'AbortError');
119
+
120
+ export function createPool(options = {}) {
121
+ let limit = options.workers ?? defaultWorkers();
122
+ let compiled = null;
123
+ let local = null;
124
+ let blob = null;
125
+ let disposed = false;
126
+ let nextId = 0;
127
+ const slots = [];
128
+
129
+ const load = () => (compiled ??= compile(options.wasm));
130
+ // A worker script must share the page's origin. From a CDN, a worker made
131
+ // here imports it instead, which a module worker may do across origins.
132
+ const blobUrl = (url) =>
133
+ (blob ??= URL.createObjectURL(
134
+ new Blob([`import ${JSON.stringify(url.href)};`], { type: 'text/javascript' }),
135
+ ));
136
+
137
+ function retire(slot, error) {
138
+ const at = slots.indexOf(slot);
139
+ if (at >= 0) slots.splice(at, 1);
140
+ slot.worker.terminate();
141
+ for (const { reject } of slot.pending.values()) reject(error);
142
+ slot.pending.clear();
143
+ }
144
+
145
+ function open(module) {
146
+ const worker = spawn(blobUrl);
147
+ if (!worker) return null;
148
+ const slot = { worker, pending: new Map() };
149
+ worker.listen(
150
+ (message) => {
151
+ const task = slot.pending.get(message.id);
152
+ if (!task) return;
153
+ slot.pending.delete(message.id);
154
+ if (slot.pending.size === 0) {
155
+ worker.busy(false);
156
+ if (slots.indexOf(slot) >= limit) retire(slot);
157
+ }
158
+ if ('error' in message) task.reject(new Error(message.error));
159
+ else task.resolve(message.result);
160
+ },
161
+ // A worker that fails takes only its own tasks down; the next task
162
+ // starts another.
163
+ (error) => {
164
+ if (slots.includes(slot)) retire(slot, error);
165
+ },
166
+ );
167
+ worker.post({ type: 'init', module });
168
+ slots.push(slot);
169
+ return slot;
170
+ }
171
+
172
+ /** The least loaded worker, or a new one while every worker is busy and
173
+ * the limit allows another. */
174
+ function pick(module) {
175
+ let best = null;
176
+ for (const slot of slots.slice(0, limit)) {
177
+ if (!best || slot.pending.size < best.pending.size) best = slot;
178
+ }
179
+ if ((!best || best.pending.size > 0) && slots.length < limit) return open(module) ?? best;
180
+ return best;
181
+ }
182
+
183
+ const onThisThread = () =>
184
+ (local ??= load().then((module) => init({ module_or_path: module })));
185
+
186
+ async function run(name, args = [], { transfer = false, signal } = {}) {
187
+ if (disposed) throw new Error('the pool was disposed');
188
+ if (typeof tasks[name] !== 'function') throw new TypeError(`no task named ${name}`);
189
+ signal?.throwIfAborted();
190
+ if (limit > 0 && workersAvailable()) {
191
+ const module = await load();
192
+ if (disposed) throw new Error('the pool was disposed');
193
+ signal?.throwIfAborted();
194
+ const slot = pick(module);
195
+ if (slot) {
196
+ const id = nextId++;
197
+ const { sent, buffers } = outgoing(args, transfer);
198
+ return new Promise((resolve, reject) => {
199
+ const task = { resolve, reject };
200
+ if (signal) {
201
+ // A task a worker has started runs to its end. Aborting it settles
202
+ // the caller now and drops the result when it comes; the task stays
203
+ // counted against its worker until then, so the load stays true.
204
+ const onAbort = () => {
205
+ task.resolve = task.reject = () => {};
206
+ reject(aborted(signal));
207
+ };
208
+ signal.addEventListener('abort', onAbort, { once: true });
209
+ const settle = (done) => (value) => {
210
+ signal.removeEventListener('abort', onAbort);
211
+ done(value);
212
+ };
213
+ task.resolve = settle(resolve);
214
+ task.reject = settle(reject);
215
+ }
216
+ slot.pending.set(id, task);
217
+ slot.worker.busy(true);
218
+ slot.worker.post({ id, task: name, args: sent }, buffers);
219
+ });
220
+ }
221
+ }
222
+ await onThisThread();
223
+ signal?.throwIfAborted();
224
+ return tasks[name](...args);
225
+ }
226
+
227
+ return {
228
+ run,
229
+ async preload() {
230
+ if (limit > 0 && workersAvailable()) {
231
+ const module = await load();
232
+ if (!disposed && slots.length === 0) open(module);
233
+ } else {
234
+ await onThisThread();
235
+ }
236
+ },
237
+ setWorkerLimit(workers) {
238
+ limit = workers;
239
+ for (const slot of slots.slice(limit)) if (slot.pending.size === 0) retire(slot);
240
+ },
241
+ get workerLimit() {
242
+ return limit;
243
+ },
244
+ dispose() {
245
+ disposed = true;
246
+ for (const slot of [...slots]) retire(slot, new Error('the pool was disposed'));
247
+ if (blob) URL.revokeObjectURL(blob);
248
+ },
249
+ };
250
+ }
package/legacy/pool.d.ts CHANGED
@@ -1,28 +1,11 @@
1
- import type { DracoAttributeRequest, DracoDecodeResult } from './index.js';
1
+ import type { DracoAttributeRequest, DracoDecodeOptions, DracoDecodeResult } from './index.js';
2
+ import type { Pool, PoolOptions, TaskOptions } from './pool-core.js';
2
3
 
3
- export interface DecoderPoolOptions {
4
- /**
5
- * How many workers to run at most; they start as work arrives. Defaults to
6
- * one less than the cores, between one and four. `0` decodes on the calling
7
- * thread, as does an environment without workers.
8
- */
9
- workers?: number;
10
- /**
11
- * The entry's wasm, when it is not next to this module where the pool looks
12
- * for it: a URL, a `Response`, the bytes, or a compiled module.
13
- */
14
- wasm?: string | URL | Response | BufferSource | WebAssembly.Module;
15
- }
4
+ export type DecoderPoolOptions = PoolOptions;
16
5
 
17
- export interface DecodeOptions {
18
- /**
19
- * Hand the stream's buffer to the worker instead of copying it, leaving it
20
- * detached here. For a buffer nothing else reads afterwards.
21
- */
22
- transfer?: boolean;
23
- }
6
+ export interface DecodeOptions extends DracoDecodeOptions, TaskOptions {}
24
7
 
25
- export interface DecoderPool {
8
+ export interface DecoderPool extends Pool {
26
9
  /**
27
10
  * `decode_draco` on a worker. A stream that does not decode resolves with
28
11
  * `success: false`, as `decode_draco` returns it; a worker that fails rejects
@@ -33,13 +16,7 @@ export interface DecoderPool {
33
16
  attributes?: DracoAttributeRequest[],
34
17
  options?: DecodeOptions,
35
18
  ): Promise<DracoDecodeResult>;
36
- /** Compiles the wasm and starts one worker ahead of the first decode. */
37
- preload(): Promise<void>;
38
- /** Changes the worker limit; idle workers past it stop now, busy ones when done. */
39
- setWorkerLimit(workers: number): void;
40
- readonly workerLimit: number;
41
- /** Stops every worker and rejects what they held. */
42
- dispose(): void;
43
19
  }
44
20
 
45
21
  export function createDecoderPool(options?: DecoderPoolOptions): DecoderPool;
22
+ export { createDecoderPool as createPool };