@draco-rust/decoder 0.1.2 → 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 +27 -21
- package/index.d.ts +21 -25
- package/index.js +12 -7
- package/index_bg.wasm +0 -0
- package/index_bg.wasm.d.ts +1 -1
- package/legacy/index.d.ts +21 -25
- package/legacy/index.js +12 -7
- package/legacy/index_bg.wasm +0 -0
- package/legacy/index_bg.wasm.d.ts +1 -1
- package/legacy/pool-core.d.ts +44 -0
- package/legacy/pool-core.js +250 -0
- package/legacy/pool.d.ts +6 -29
- package/legacy/pool.js +15 -184
- package/legacy/tasks.js +2 -0
- package/legacy/three.d.ts +11 -57
- package/legacy/three.js +66 -107
- package/legacy/worker.js +24 -10
- package/mesh/index.d.ts +21 -25
- package/mesh/index.js +12 -7
- package/mesh/index_bg.wasm +0 -0
- package/mesh/index_bg.wasm.d.ts +1 -1
- package/mesh/pool-core.d.ts +44 -0
- package/mesh/pool-core.js +250 -0
- package/mesh/pool.d.ts +6 -29
- package/mesh/pool.js +15 -184
- package/mesh/tasks.js +2 -0
- package/mesh/three.d.ts +11 -57
- package/mesh/three.js +66 -107
- package/mesh/worker.js +24 -10
- package/package.json +10 -2
- package/point-cloud/index.d.ts +21 -25
- package/point-cloud/index.js +12 -7
- package/point-cloud/index_bg.wasm +0 -0
- package/point-cloud/index_bg.wasm.d.ts +1 -1
- package/point-cloud/pool-core.d.ts +44 -0
- package/point-cloud/pool-core.js +250 -0
- package/point-cloud/pool.d.ts +6 -29
- package/point-cloud/pool.js +15 -184
- package/point-cloud/tasks.js +2 -0
- package/point-cloud/three.d.ts +11 -57
- package/point-cloud/three.js +66 -107
- package/point-cloud/worker.js +24 -10
- package/pool-core.d.ts +44 -0
- package/pool-core.js +250 -0
- package/pool.d.ts +6 -29
- package/pool.js +15 -184
- package/tasks.js +2 -0
- package/three.d.ts +11 -57
- package/three.js +66 -107
- package/worker.js +24 -10
package/pool.js
CHANGED
|
@@ -1,193 +1,24 @@
|
|
|
1
|
-
// Decoding off the calling thread
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
// decode copies nothing it does not have to.
|
|
5
|
-
import init, { decode_draco } from './index.js';
|
|
1
|
+
// Decoding off the calling thread, on the shared pool: decode_draco on a
|
|
2
|
+
// worker, with the stream copied over unless the caller hands it away.
|
|
3
|
+
import { createPool as createTaskPool } from './pool-core.js';
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const defaultWorkers = () =>
|
|
10
|
-
Math.max(1, Math.min(4, (globalThis.navigator?.hardwareConcurrency ?? 4) - 1));
|
|
11
|
-
|
|
12
|
-
const builtin = (name) =>
|
|
13
|
-
typeof process !== 'undefined' && typeof process.getBuiltinModule === 'function'
|
|
14
|
-
? process.getBuiltinModule(name)
|
|
15
|
-
: undefined;
|
|
16
|
-
|
|
17
|
-
async function compile(wasm) {
|
|
18
|
-
if (wasm instanceof WebAssembly.Module) return wasm;
|
|
19
|
-
let source = wasm ?? new URL('./index_bg.wasm', import.meta.url);
|
|
20
|
-
if (typeof source === 'string' || source instanceof URL) {
|
|
21
|
-
const url = new URL(source, import.meta.url);
|
|
22
|
-
// Node's fetch does not read files.
|
|
23
|
-
const fs = url.protocol === 'file:' ? builtin('node:fs/promises') : undefined;
|
|
24
|
-
if (fs) return WebAssembly.compile(await fs.readFile(url));
|
|
25
|
-
source = await fetch(url);
|
|
26
|
-
}
|
|
27
|
-
if (typeof Response !== 'undefined' && source instanceof Response) {
|
|
28
|
-
if (!source.ok) throw new Error(`failed to fetch ${source.url}: ${source.status}`);
|
|
29
|
-
if (source.headers.get('content-type') === 'application/wasm') {
|
|
30
|
-
return WebAssembly.compileStreaming(source);
|
|
31
|
-
}
|
|
32
|
-
source = await source.arrayBuffer();
|
|
33
|
-
}
|
|
34
|
-
return WebAssembly.compile(source);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** What the pool needs of a worker, the same over a Web Worker and a Node
|
|
38
|
-
* worker thread. */
|
|
39
|
-
function spawn(blobUrl) {
|
|
40
|
-
if (typeof Worker === 'function') {
|
|
41
|
-
const url = new URL('./worker.js', import.meta.url);
|
|
42
|
-
// A bundler recognises this exact form and emits the worker as a chunk.
|
|
43
|
-
const worker =
|
|
44
|
-
typeof location === 'undefined' || url.origin === location.origin
|
|
45
|
-
? new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })
|
|
46
|
-
: new Worker(blobUrl(url), { type: 'module' });
|
|
47
|
-
return {
|
|
48
|
-
post: (message, transfer) => worker.postMessage(message, transfer),
|
|
49
|
-
listen: (onMessage, onError) => {
|
|
50
|
-
worker.addEventListener('message', (event) => onMessage(event.data));
|
|
51
|
-
worker.addEventListener('error', (event) => {
|
|
52
|
-
event.preventDefault();
|
|
53
|
-
onError(new Error(event.message || 'the decoder worker failed'));
|
|
54
|
-
});
|
|
55
|
-
worker.addEventListener('messageerror', () => onError(new Error('a decode result could not be read')));
|
|
56
|
-
},
|
|
57
|
-
busy: () => {},
|
|
58
|
-
terminate: () => worker.terminate(),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
const threads = builtin('node:worker_threads');
|
|
62
|
-
if (!threads) return null;
|
|
63
|
-
const worker = new threads.Worker(new URL('./worker.js', import.meta.url));
|
|
64
|
-
// An idle worker does not keep Node running; one with work does.
|
|
65
|
-
worker.unref();
|
|
66
|
-
return {
|
|
67
|
-
post: (message, transfer) => worker.postMessage(message, transfer),
|
|
68
|
-
listen: (onMessage, onError) => {
|
|
69
|
-
worker.on('message', onMessage);
|
|
70
|
-
worker.on('error', onError);
|
|
71
|
-
worker.on('exit', (code) => onError(new Error(`the decoder worker exited with code ${code}`)));
|
|
72
|
-
},
|
|
73
|
-
busy: (busy) => (busy ? worker.ref() : worker.unref()),
|
|
74
|
-
terminate: () => worker.terminate(),
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const workersAvailable = () =>
|
|
79
|
-
typeof Worker === 'function' || builtin('node:worker_threads') !== undefined;
|
|
5
|
+
const bytesOf = (data) =>
|
|
6
|
+
ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data);
|
|
80
7
|
|
|
81
8
|
export function createDecoderPool(options = {}) {
|
|
82
|
-
|
|
83
|
-
let compiled = null;
|
|
84
|
-
let local = null;
|
|
85
|
-
let blob = null;
|
|
86
|
-
let disposed = false;
|
|
87
|
-
let nextId = 0;
|
|
88
|
-
const slots = [];
|
|
89
|
-
|
|
90
|
-
const load = () => (compiled ??= compile(options.wasm));
|
|
91
|
-
// A worker script must share the page's origin. From a CDN, a worker made
|
|
92
|
-
// here imports it instead, which a module worker may do across origins.
|
|
93
|
-
const blobUrl = (url) =>
|
|
94
|
-
(blob ??= URL.createObjectURL(
|
|
95
|
-
new Blob([`import ${JSON.stringify(url.href)};`], { type: 'text/javascript' }),
|
|
96
|
-
));
|
|
97
|
-
|
|
98
|
-
function retire(slot, error) {
|
|
99
|
-
const at = slots.indexOf(slot);
|
|
100
|
-
if (at >= 0) slots.splice(at, 1);
|
|
101
|
-
slot.worker.terminate();
|
|
102
|
-
for (const { reject } of slot.pending.values()) reject(error);
|
|
103
|
-
slot.pending.clear();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function open(module) {
|
|
107
|
-
const worker = spawn(blobUrl);
|
|
108
|
-
if (!worker) return null;
|
|
109
|
-
const slot = { worker, pending: new Map() };
|
|
110
|
-
worker.listen(
|
|
111
|
-
(message) => {
|
|
112
|
-
const task = slot.pending.get(message.id);
|
|
113
|
-
if (!task) return;
|
|
114
|
-
slot.pending.delete(message.id);
|
|
115
|
-
if (slot.pending.size === 0) {
|
|
116
|
-
worker.busy(false);
|
|
117
|
-
if (slots.indexOf(slot) >= limit) retire(slot);
|
|
118
|
-
}
|
|
119
|
-
if ('error' in message) task.reject(new Error(message.error));
|
|
120
|
-
else task.resolve(message.result);
|
|
121
|
-
},
|
|
122
|
-
// A worker that fails takes only its own tasks down; the next decode
|
|
123
|
-
// starts another.
|
|
124
|
-
(error) => {
|
|
125
|
-
if (slots.includes(slot)) retire(slot, error);
|
|
126
|
-
},
|
|
127
|
-
);
|
|
128
|
-
worker.post({ type: 'init', module });
|
|
129
|
-
slots.push(slot);
|
|
130
|
-
return slot;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** The least loaded worker, or a new one while every worker is busy and
|
|
134
|
-
* the limit allows another. */
|
|
135
|
-
function pick(module) {
|
|
136
|
-
let best = null;
|
|
137
|
-
for (const slot of slots.slice(0, limit)) {
|
|
138
|
-
if (!best || slot.pending.size < best.pending.size) best = slot;
|
|
139
|
-
}
|
|
140
|
-
if ((!best || best.pending.size > 0) && slots.length < limit) return open(module) ?? best;
|
|
141
|
-
return best;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const onThisThread = () =>
|
|
145
|
-
(local ??= load().then((module) => init({ module_or_path: module })));
|
|
146
|
-
|
|
147
|
-
async function decode(data, attributes, { transfer = false } = {}) {
|
|
148
|
-
if (disposed) throw new Error('the decoder pool was disposed');
|
|
149
|
-
const bytes = ArrayBuffer.isView(data)
|
|
150
|
-
? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
151
|
-
: new Uint8Array(data);
|
|
152
|
-
if (limit > 0 && workersAvailable()) {
|
|
153
|
-
const module = await load();
|
|
154
|
-
if (disposed) throw new Error('the decoder pool was disposed');
|
|
155
|
-
const slot = pick(module);
|
|
156
|
-
if (slot) {
|
|
157
|
-
const id = nextId++;
|
|
158
|
-
const sent = transfer ? bytes : bytes.slice();
|
|
159
|
-
return new Promise((resolve, reject) => {
|
|
160
|
-
slot.pending.set(id, { resolve, reject });
|
|
161
|
-
slot.worker.busy(true);
|
|
162
|
-
slot.worker.post({ id, data: sent, attributes }, [sent.buffer]);
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
await onThisThread();
|
|
167
|
-
return decode_draco(bytes, attributes);
|
|
168
|
-
}
|
|
169
|
-
|
|
9
|
+
const pool = createTaskPool(options);
|
|
170
10
|
return {
|
|
171
|
-
decode,
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const module = await load();
|
|
175
|
-
if (!disposed && slots.length === 0) open(module);
|
|
176
|
-
} else {
|
|
177
|
-
await onThisThread();
|
|
178
|
-
}
|
|
179
|
-
},
|
|
180
|
-
setWorkerLimit(workers) {
|
|
181
|
-
limit = workers;
|
|
182
|
-
for (const slot of slots.slice(limit)) if (slot.pending.size === 0) retire(slot);
|
|
11
|
+
decode(data, attributes, { transfer = false, signal, index } = {}) {
|
|
12
|
+
const decodeOptions = index === undefined ? undefined : { index };
|
|
13
|
+
return pool.run('decode_draco', [bytesOf(data), attributes, decodeOptions], { transfer, signal });
|
|
183
14
|
},
|
|
15
|
+
preload: () => pool.preload(),
|
|
16
|
+
setWorkerLimit: (workers) => pool.setWorkerLimit(workers),
|
|
184
17
|
get workerLimit() {
|
|
185
|
-
return
|
|
186
|
-
},
|
|
187
|
-
dispose() {
|
|
188
|
-
disposed = true;
|
|
189
|
-
for (const slot of [...slots]) retire(slot, new Error('the decoder pool was disposed'));
|
|
190
|
-
if (blob) URL.revokeObjectURL(blob);
|
|
18
|
+
return pool.workerLimit;
|
|
191
19
|
},
|
|
20
|
+
dispose: () => pool.dispose(),
|
|
192
21
|
};
|
|
193
22
|
}
|
|
23
|
+
|
|
24
|
+
export { createDecoderPool as createPool };
|
package/tasks.js
ADDED
package/three.d.ts
CHANGED
|
@@ -1,65 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LoadingManager } from 'three';
|
|
2
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
2
3
|
import type { DecoderPool, DecoderPoolOptions } from './pool.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
* The three.js classes the loader builds with, passed in so the package uses
|
|
6
|
-
* the application's own copy of three: `import * as THREE from "three"` will
|
|
7
|
-
* do. With `InterleavedBuffer` and `InterleavedBufferAttribute`, an attribute
|
|
8
|
-
* whose items are not a multiple of four bytes is padded to one, as DRACOLoader
|
|
9
|
-
* pads it since r181; with `Color` and `ColorManagement`, a `.drc` file's sRGB
|
|
10
|
-
* vertex colours are made linear as DRACOLoader makes them.
|
|
11
|
-
*/
|
|
12
|
-
export interface ThreeClasses<Geometry> {
|
|
13
|
-
BufferGeometry: new () => Geometry;
|
|
14
|
-
BufferAttribute: new (array: any, itemSize: number, normalized?: boolean) => any;
|
|
15
|
-
InterleavedBuffer?: new (array: any, stride: number) => any;
|
|
16
|
-
InterleavedBufferAttribute?: new (buffer: any, itemSize: number, offset: number) => any;
|
|
17
|
-
Color?: new () => any;
|
|
18
|
-
/** `colorSpaceToWorking` since three r177, `toWorkingColorSpace` before. */
|
|
19
|
-
ColorManagement?:
|
|
20
|
-
| { colorSpaceToWorking(color: any, colorSpace: string): any }
|
|
21
|
-
| { toWorkingColorSpace(color: any, colorSpace: string): any };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface DracoLoaderOptions extends DecoderPoolOptions {
|
|
5
|
+
export interface DRACOLoaderOptions extends DecoderPoolOptions {
|
|
25
6
|
/** A pool to share with other loaders, instead of one of the loader's own. */
|
|
26
7
|
pool?: DecoderPool;
|
|
27
8
|
}
|
|
28
9
|
|
|
29
|
-
export interface DecodeGeometryOptions {
|
|
30
|
-
/** three.js attribute name to Draco unique id, as glTF names them. Without
|
|
31
|
-
* it, `position`, `normal`, `color` and `uv` are read by type. */
|
|
32
|
-
attributeIDs?: Record<string, number>;
|
|
33
|
-
/** three.js attribute name to the typed array it comes back in. */
|
|
34
|
-
attributeTypes?: Record<string, DracoArrayType>;
|
|
35
|
-
/** `"srgb"` makes vertex colours linear; the default leaves them as stored. */
|
|
36
|
-
vertexColorSpace?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The part of three.js's DRACOLoader that GLTFLoader and `.drc` users call. */
|
|
40
|
-
export interface DracoLoader<Geometry> {
|
|
41
|
-
readonly pool: DecoderPool;
|
|
42
|
-
decodeGeometry(buffer: ArrayBuffer | ArrayBufferView, options?: DecodeGeometryOptions): Promise<Geometry>;
|
|
43
|
-
decodeDracoFile(
|
|
44
|
-
buffer: ArrayBuffer,
|
|
45
|
-
callback: (geometry: Geometry) => void,
|
|
46
|
-
attributeIDs?: Record<string, number> | null,
|
|
47
|
-
attributeTypes?: Record<string, DracoArrayType> | null,
|
|
48
|
-
vertexColorSpace?: string,
|
|
49
|
-
onError?: (error: unknown) => void,
|
|
50
|
-
): Promise<void>;
|
|
51
|
-
parse(buffer: ArrayBuffer, onLoad: (geometry: Geometry) => void, onError?: (error: unknown) => void): void;
|
|
52
|
-
parseAsync(buffer: ArrayBuffer | ArrayBufferView): Promise<Geometry>;
|
|
53
|
-
preload(): this;
|
|
54
|
-
setWorkerLimit(workers: number): this;
|
|
55
|
-
dispose(): this;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
10
|
/**
|
|
59
|
-
*
|
|
60
|
-
* `
|
|
11
|
+
* three.js's `DRACOLoader`, decoding on this entry's worker pool instead of
|
|
12
|
+
* the Draco decoder three fetches from `setDecoderPath`. It is three's class,
|
|
13
|
+
* so it goes wherever one is expected:
|
|
14
|
+
* `gltfLoader.setDRACOLoader(new DRACOLoader())`.
|
|
61
15
|
*/
|
|
62
|
-
export
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
16
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
17
|
+
constructor(manager?: LoadingManager, options?: DRACOLoaderOptions);
|
|
18
|
+
readonly pool: DecoderPool;
|
|
19
|
+
}
|
package/three.js
CHANGED
|
@@ -1,123 +1,82 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// three.js's DRACOLoader, decoding on this entry's worker pool. Everything a
|
|
2
|
+
// caller sees -- load, parse, decodeDracoFile, GLTFLoader's use of it, the
|
|
3
|
+
// geometry it builds -- is three's own class; only the decode is replaced, so
|
|
4
|
+
// the result follows whichever three the application has.
|
|
5
|
+
import { REVISION } from 'three';
|
|
6
|
+
import { DRACOLoader as ThreeDRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
|
|
4
7
|
import { createDecoderPool } from './pool.js';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
// From r181 DRACOLoader pads an attribute whose items are not a multiple of
|
|
10
|
+
// four bytes and builds it interleaved, from the stride its worker reports.
|
|
11
|
+
const PADS = Number.parseInt(REVISION, 10) >= 181;
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function createDracoLoader(three, options = {}) {
|
|
18
|
-
const { BufferGeometry, BufferAttribute, InterleavedBuffer, InterleavedBufferAttribute, Color, ColorManagement } =
|
|
19
|
-
three;
|
|
20
|
-
if (typeof BufferGeometry !== 'function' || typeof BufferAttribute !== 'function') {
|
|
21
|
-
throw new TypeError("createDracoLoader needs three.js's BufferGeometry and BufferAttribute");
|
|
13
|
+
function padded(array, itemSize) {
|
|
14
|
+
const bytes = array.BYTES_PER_ELEMENT;
|
|
15
|
+
const stride = (Math.ceil((itemSize * bytes) / 4) * 4) / bytes;
|
|
16
|
+
if (!PADS || stride === itemSize) return { array, stride: itemSize };
|
|
17
|
+
const count = array.length / itemSize;
|
|
18
|
+
const out = new array.constructor(count * stride);
|
|
19
|
+
for (let i = 0; i < count; i += 1) {
|
|
20
|
+
out.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// decode a second time; GLTFLoader asks again for a primitive it shares, and
|
|
26
|
-
// gets the first answer, as DRACOLoader gives it.
|
|
27
|
-
const tasks = new WeakMap();
|
|
22
|
+
return { array: out, stride };
|
|
23
|
+
}
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function toWorkingColorSpace(attribute) {
|
|
32
|
-
const convert = ColorManagement?.colorSpaceToWorking ?? ColorManagement?.toWorkingColorSpace;
|
|
33
|
-
if (!Color || !convert) return;
|
|
34
|
-
const color = new Color();
|
|
35
|
-
for (let i = 0; i < attribute.count; i += 1) {
|
|
36
|
-
color.fromBufferAttribute(attribute, i);
|
|
37
|
-
convert.call(ColorManagement, color, SRGB);
|
|
38
|
-
attribute.setXYZ(i, color.r, color.g, color.b);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
25
|
+
export class DRACOLoader extends ThreeDRACOLoader {
|
|
26
|
+
#tasks = new WeakMap();
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function attributeOf(array, itemSize) {
|
|
46
|
-
const bytes = array.BYTES_PER_ELEMENT;
|
|
47
|
-
const stride = Math.ceil((itemSize * bytes) / 4) * (4 / bytes);
|
|
48
|
-
if (stride === itemSize || !InterleavedBuffer || !InterleavedBufferAttribute) {
|
|
49
|
-
return new BufferAttribute(array, itemSize);
|
|
50
|
-
}
|
|
51
|
-
const count = array.length / itemSize;
|
|
52
|
-
const padded = new array.constructor(count * stride);
|
|
53
|
-
for (let i = 0; i < count; i += 1) {
|
|
54
|
-
padded.set(array.subarray(i * itemSize, (i + 1) * itemSize), i * stride);
|
|
55
|
-
}
|
|
56
|
-
return new InterleavedBufferAttribute(new InterleavedBuffer(padded, stride), itemSize, 0);
|
|
28
|
+
constructor(manager, options = {}) {
|
|
29
|
+
super(manager);
|
|
30
|
+
this.pool = options.pool ?? createDecoderPool(options);
|
|
57
31
|
}
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
33
|
+
// The buffer goes to a worker by transfer, which leaves nothing here to
|
|
34
|
+
// decode again; a second request for it gets the first answer, as three's
|
|
35
|
+
// own task cache gives it.
|
|
36
|
+
decodeGeometry(buffer, taskConfig) {
|
|
37
|
+
const key = JSON.stringify(taskConfig);
|
|
38
|
+
const cached = this.#tasks.get(buffer);
|
|
39
|
+
if (cached?.key === key) return cached.promise;
|
|
40
|
+
if (cached && buffer.byteLength === 0) {
|
|
41
|
+
return Promise.reject(new Error('THREE.DRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred.'));
|
|
42
|
+
}
|
|
43
|
+
const { attributeIDs, attributeTypes, useUniqueIDs, vertexColorSpace } = taskConfig;
|
|
44
|
+
const requests = Object.keys(attributeIDs).map((name) => ({
|
|
62
45
|
name,
|
|
63
|
-
...(
|
|
64
|
-
type: attributeTypes
|
|
46
|
+
...(useUniqueIDs ? { id: attributeIDs[name] } : { semantic: attributeIDs[name] }),
|
|
47
|
+
type: attributeTypes[name],
|
|
65
48
|
}));
|
|
66
|
-
const
|
|
67
|
-
|
|
49
|
+
const promise = this.pool.decode(buffer, requests, { transfer: buffer instanceof ArrayBuffer }).then((result) => {
|
|
50
|
+
if (!result.success) throw new Error(`THREE.DRACOLoader: Decoding failed: ${result.error}`);
|
|
51
|
+
return this._createGeometry({
|
|
52
|
+
index: result.index ? { array: result.index, itemSize: 1 } : null,
|
|
53
|
+
attributes: result.attributes.map(({ name, array, itemSize }) => ({
|
|
54
|
+
name,
|
|
55
|
+
itemSize,
|
|
56
|
+
...padded(array, itemSize),
|
|
57
|
+
...(name === 'color' ? { vertexColorSpace } : {}),
|
|
58
|
+
})),
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
if (buffer instanceof ArrayBuffer) this.#tasks.set(buffer, { key, promise });
|
|
62
|
+
return promise;
|
|
63
|
+
}
|
|
68
64
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (name === 'color') {
|
|
74
|
-
if (vertexColorSpace === SRGB) toWorkingColorSpace(attribute);
|
|
75
|
-
attribute.normalized = !(array instanceof Float32Array);
|
|
76
|
-
}
|
|
77
|
-
geometry.setAttribute(name, attribute);
|
|
78
|
-
}
|
|
79
|
-
return geometry;
|
|
65
|
+
setWorkerLimit(workerLimit) {
|
|
66
|
+
super.setWorkerLimit(workerLimit);
|
|
67
|
+
this.pool.setWorkerLimit(workerLimit);
|
|
68
|
+
return this;
|
|
80
69
|
}
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (cached && buffer.byteLength === 0) {
|
|
87
|
-
return Promise.reject(new Error('this buffer was already decoded with other settings and handed to a worker'));
|
|
88
|
-
}
|
|
89
|
-
const promise = decode(buffer, attributeIDs, attributeTypes, vertexColorSpace);
|
|
90
|
-
if (buffer instanceof ArrayBuffer) tasks.set(buffer, { key, promise });
|
|
91
|
-
return promise;
|
|
71
|
+
preload() {
|
|
72
|
+
// A failure shows again on the first decode, which is where it is handled.
|
|
73
|
+
this.pool.preload().catch(() => {});
|
|
74
|
+
return this;
|
|
92
75
|
}
|
|
93
76
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return decodeGeometry(buffer, { attributeIDs, attributeTypes, vertexColorSpace }).then(callback).catch(onError);
|
|
100
|
-
},
|
|
101
|
-
/** A `.drc` file's contents, as DRACOLoader's `parse`. */
|
|
102
|
-
parse(buffer, onLoad, onError = () => {}) {
|
|
103
|
-
loader.decodeDracoFile(buffer, onLoad, null, null, SRGB, onError);
|
|
104
|
-
},
|
|
105
|
-
parseAsync(buffer) {
|
|
106
|
-
return decodeGeometry(buffer, { vertexColorSpace: SRGB });
|
|
107
|
-
},
|
|
108
|
-
preload() {
|
|
109
|
-
// A failure shows again on the first decode, which is where it is handled.
|
|
110
|
-
pool.preload().catch(() => {});
|
|
111
|
-
return loader;
|
|
112
|
-
},
|
|
113
|
-
setWorkerLimit(workers) {
|
|
114
|
-
pool.setWorkerLimit(workers);
|
|
115
|
-
return loader;
|
|
116
|
-
},
|
|
117
|
-
dispose() {
|
|
118
|
-
pool.dispose();
|
|
119
|
-
return loader;
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
return loader;
|
|
77
|
+
dispose() {
|
|
78
|
+
super.dispose();
|
|
79
|
+
this.pool.dispose();
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
123
82
|
}
|
package/worker.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// A
|
|
2
|
-
//
|
|
3
|
-
// transfer. Runs as a Web Worker and as a Node
|
|
4
|
-
|
|
1
|
+
// A pool worker. The pool sends it the compiled module once, then tasks by
|
|
2
|
+
// name from this entry's `tasks.js`; it answers each with its result, handing
|
|
3
|
+
// every array in it back by transfer. Runs as a Web Worker and as a Node
|
|
4
|
+
// worker thread.
|
|
5
|
+
import init from './index.js';
|
|
6
|
+
import * as tasks from './tasks.js';
|
|
5
7
|
|
|
6
8
|
const web = typeof self !== 'undefined' && typeof self.postMessage === 'function';
|
|
7
9
|
const port = web ? self : process.getBuiltinModule('node:worker_threads').parentPort;
|
|
@@ -10,18 +12,30 @@ const listen = (handler) =>
|
|
|
10
12
|
|
|
11
13
|
let ready = null;
|
|
12
14
|
|
|
15
|
+
/** Every buffer in a result, except the module's own memory, which stays. */
|
|
16
|
+
function buffersOf(value, memory, found = new Set()) {
|
|
17
|
+
if (ArrayBuffer.isView(value)) {
|
|
18
|
+
if (value.buffer instanceof ArrayBuffer && value.buffer !== memory) found.add(value.buffer);
|
|
19
|
+
} else if (value instanceof ArrayBuffer) {
|
|
20
|
+
if (value !== memory) found.add(value);
|
|
21
|
+
} else if (Array.isArray(value)) {
|
|
22
|
+
for (const item of value) buffersOf(item, memory, found);
|
|
23
|
+
} else if (value !== null && typeof value === 'object') {
|
|
24
|
+
for (const item of Object.values(value)) buffersOf(item, memory, found);
|
|
25
|
+
}
|
|
26
|
+
return found;
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
listen(async (message) => {
|
|
14
30
|
if (message.type === 'init') {
|
|
15
31
|
ready = init({ module_or_path: message.module });
|
|
16
32
|
return;
|
|
17
33
|
}
|
|
18
|
-
const { id,
|
|
34
|
+
const { id, task, args } = message;
|
|
19
35
|
try {
|
|
20
|
-
await ready;
|
|
21
|
-
const result =
|
|
22
|
-
|
|
23
|
-
if (result.index) transfer.push(result.index.buffer);
|
|
24
|
-
port.postMessage({ id, result }, transfer);
|
|
36
|
+
const { memory } = await ready;
|
|
37
|
+
const result = await tasks[task](...args);
|
|
38
|
+
port.postMessage({ id, result }, [...buffersOf(result, memory.buffer)]);
|
|
25
39
|
} catch (error) {
|
|
26
40
|
port.postMessage({ id, error: String(error?.message ?? error) });
|
|
27
41
|
}
|