@needle-tools/gltf-build-pipeline 2.13.0 → 2.13.1

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 (103) hide show
  1. package/CHANGELOG.md +520 -517
  2. package/README.md +38 -38
  3. package/dist/cache/cache.d.ts +55 -55
  4. package/dist/cache/cache.js +326 -326
  5. package/dist/cache/index.d.ts +1 -1
  6. package/dist/cache/index.js +1 -1
  7. package/dist/cli/index.d.ts +2 -2
  8. package/dist/cli/index.js +198 -198
  9. package/dist/config/index.d.ts +53 -53
  10. package/dist/config/index.js +152 -152
  11. package/dist/constants.d.ts +7 -7
  12. package/dist/constants.js +7 -7
  13. package/dist/extensions/NEEDLE_compression_texture/NEEDLE_compression_texture.d.ts +24 -24
  14. package/dist/extensions/NEEDLE_compression_texture/NEEDLE_compression_texture.js +54 -54
  15. package/dist/extensions/NEEDLE_compression_texture/index.d.ts +1 -1
  16. package/dist/extensions/NEEDLE_compression_texture/index.js +1 -1
  17. package/dist/extensions/NEEDLE_mesh_compression/NEEDLE_mesh_compression.d.ts +27 -27
  18. package/dist/extensions/NEEDLE_mesh_compression/NEEDLE_mesh_compression.js +88 -88
  19. package/dist/extensions/NEEDLE_mesh_compression/index.d.ts +1 -1
  20. package/dist/extensions/NEEDLE_mesh_compression/index.js +1 -1
  21. package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.d.ts +27 -27
  22. package/dist/extensions/NEEDLE_opaque/NEEDLE_opaque.js +980 -980
  23. package/dist/extensions/NEEDLE_opaque/index.d.ts +3 -3
  24. package/dist/extensions/NEEDLE_opaque/index.js +30 -30
  25. package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.d.ts +48 -48
  26. package/dist/extensions/NEEDLE_progressive/NEEDLE_progressive.js +69 -69
  27. package/dist/extensions/NEEDLE_progressive/index.d.ts +1 -1
  28. package/dist/extensions/NEEDLE_progressive/index.js +1 -1
  29. package/dist/extensions/NEEDLE_progressive_mesh_settings/index.d.ts +23 -23
  30. package/dist/extensions/NEEDLE_progressive_mesh_settings/index.js +41 -41
  31. package/dist/extensions/NEEDLE_progressive_texture_settings/index.d.ts +25 -25
  32. package/dist/extensions/NEEDLE_progressive_texture_settings/index.js +38 -38
  33. package/dist/extensions/index.d.ts +7 -7
  34. package/dist/extensions/index.js +17 -17
  35. package/dist/extensions/utils.d.ts +6 -6
  36. package/dist/extensions/utils.js +72 -72
  37. package/dist/functions/weld.3.d.ts +76 -76
  38. package/dist/functions/weld.3.js +414 -414
  39. package/dist/index.d.ts +8 -8
  40. package/dist/index.js +8 -8
  41. package/dist/scripts/index.d.ts +2 -2
  42. package/dist/scripts/index.js +2 -2
  43. package/dist/scripts/pack-gltf.d.ts +15 -15
  44. package/dist/scripts/pack-gltf.js +135 -133
  45. package/dist/scripts/stats.d.ts +2 -2
  46. package/dist/scripts/stats.js +19 -19
  47. package/dist/transforms/index.d.ts +9 -9
  48. package/dist/transforms/index.js +9 -9
  49. package/dist/transforms/needle_animation_transform.d.ts +2 -2
  50. package/dist/transforms/needle_animation_transform.js +5 -5
  51. package/dist/transforms/needle_asset.d.ts +3 -3
  52. package/dist/transforms/needle_asset.js +50 -50
  53. package/dist/transforms/needle_common.d.ts +17 -17
  54. package/dist/transforms/needle_common.js +1 -1
  55. package/dist/transforms/needle_mesh_transform.d.ts +15 -15
  56. package/dist/transforms/needle_mesh_transform.js +223 -223
  57. package/dist/transforms/needle_progressive.d.ts +45 -45
  58. package/dist/transforms/needle_progressive.js +1043 -1043
  59. package/dist/transforms/needle_texture_transform.d.ts +13 -13
  60. package/dist/transforms/needle_texture_transform.js +257 -257
  61. package/dist/transforms/needle_toktx.d.ts +26 -26
  62. package/dist/transforms/needle_toktx.js +243 -243
  63. package/dist/transforms/needle_webp.d.ts +15 -15
  64. package/dist/transforms/needle_webp.js +53 -53
  65. package/dist/transforms/toktx.d.ts +52 -52
  66. package/dist/transforms/toktx.js +221 -221
  67. package/dist/transforms/toktx_types.d.ts +78 -78
  68. package/dist/transforms/toktx_types.js +62 -62
  69. package/dist/transforms/util.d.ts +21 -21
  70. package/dist/transforms/util.js +90 -90
  71. package/dist/utils/compression_utils.d.ts +2 -2
  72. package/dist/utils/compression_utils.js +6 -6
  73. package/dist/utils/fileutils.d.ts +19 -19
  74. package/dist/utils/fileutils.js +177 -177
  75. package/dist/utils/guid.d.ts +1 -1
  76. package/dist/utils/guid.js +5 -5
  77. package/dist/utils/index.d.ts +10 -10
  78. package/dist/utils/index.js +10 -10
  79. package/dist/utils/merge.d.ts +12 -12
  80. package/dist/utils/merge.js +31 -31
  81. package/dist/utils/mesh.d.ts +13 -13
  82. package/dist/utils/mesh.js +124 -124
  83. package/dist/utils/nodeio.d.ts +6 -6
  84. package/dist/utils/nodeio.js +86 -86
  85. package/dist/utils/stats.d.ts +70 -70
  86. package/dist/utils/stats.js +258 -258
  87. package/dist/utils/texture.d.ts +5 -5
  88. package/dist/utils/texture.js +14 -14
  89. package/dist/utils/validate.d.ts +5 -5
  90. package/dist/utils/validate.js +17 -17
  91. package/dist/utils/version.d.ts +1 -1
  92. package/dist/utils/version.gen.d.ts +1 -1
  93. package/dist/utils/version.gen.js +1 -1
  94. package/dist/utils/version.js +4 -4
  95. package/package.json +79 -79
  96. package/scripts/clear-caches.mjs +3 -3
  97. package/scripts/defaults.mjs +11 -11
  98. package/scripts/fileutils.mjs +5 -5
  99. package/scripts/index.mjs +41 -41
  100. package/scripts/limit-caches.mjs +3 -3
  101. package/scripts/make-progressive.mjs +16 -16
  102. package/scripts/pack-gltf.mjs +106 -106
  103. package/tsconfig.json +19 -19
@@ -1,326 +1,326 @@
1
- import { ExtensibleProperty, Texture, Primitive, Accessor, Mesh } from "@gltf-transform/core";
2
- import tmp from 'tmp';
3
- import fs, { statSync } from 'fs';
4
- import path from 'path';
5
- import { getVersion } from "../utils/version.js";
6
- // import * as CHECKDISCSPACE from 'check-disk-space'
7
- import xxhash from "xxhash-wasm";
8
- let hasher = null;
9
- xxhash().then(module => {
10
- hasher = module.create32();
11
- });
12
- /**
13
- * Get the available space on the cache directory in bytes.
14
- */
15
- // export async function getCacheAvailableSpaceOnDisc(): Promise<number> {
16
- // try {
17
- // const dir = getCacheDirectory();
18
- // const res = await CHECKDISCSPACE.default(dir).catch(() => { return { free: 0 } });
19
- // return res.free;
20
- // }
21
- // catch (e) {
22
- // console.error(`Error checking disk space: ${e}`);
23
- // }
24
- // return 0;
25
- // }
26
- const buildPipelineVersion = getVersion(false);
27
- const debugLogs = process.argv.includes("--debug");
28
- /**
29
- * Clear the cache.
30
- * This will remove all files in the cache directory.
31
- */
32
- export function clearCache(logger) {
33
- const dir = getCacheDirectory(true);
34
- fs.rmSync(dir, { recursive: true });
35
- if (!logger)
36
- console.log(`Cleared cache at ${dir}`);
37
- else
38
- logger.info(`Cleared cache at ${dir}`);
39
- }
40
- export const cacheSizeLimit = 1024 * 5;
41
- /**
42
- * Ensure that the cache size does not exceed the given maximum size in MB.
43
- * @param maxSizeMB The maximum size in MB.
44
- */
45
- export function limitCacheSize(maxSizeMB = cacheSizeLimit) {
46
- const dir = getCacheDirectory(true);
47
- console.log(`Limit cache size to ${maxSizeMB.toFixed(2)} MB at ${dir}`);
48
- let totalSize = 0;
49
- const files = new Array();
50
- function recurseDirectories(dir) {
51
- fs.readdirSync(dir).forEach(file => {
52
- const path = `${dir}/${file}`;
53
- const stats = statSync(path);
54
- if (stats.isDirectory()) {
55
- recurseDirectories(path);
56
- }
57
- else {
58
- totalSize += stats.size;
59
- files.push({ path, size: stats.size });
60
- }
61
- });
62
- }
63
- recurseDirectories(dir);
64
- const maxSizeInBytes = maxSizeMB * 1024 * 1024;
65
- console.log(`Current cache size is ${(totalSize / (1024 * 1024)).toFixed(1)} MB`);
66
- if (totalSize < maxSizeInBytes)
67
- return;
68
- files.sort((a, b) => {
69
- const mtimeA = fs.statSync(a.path).mtimeMs;
70
- const mtimeB = fs.statSync(b.path).mtimeMs;
71
- return mtimeA - mtimeB;
72
- });
73
- const targetSize = maxSizeInBytes * 0.9;
74
- // remove oldest files first
75
- let filesRemoved = 0;
76
- while (totalSize > targetSize) {
77
- const file = files.shift();
78
- if (!file)
79
- break;
80
- if (fs.statSync(file.path).isDirectory()) {
81
- continue;
82
- }
83
- filesRemoved++;
84
- fs.rmSync(file.path);
85
- totalSize -= file.size;
86
- }
87
- console.log(`Cache size reduced to ${(totalSize / (1024 * 1024)).toFixed(1)} MB (${filesRemoved} files removed)`);
88
- }
89
- /**
90
- * Add data to the cache.
91
- * @param key The key to store the data under.
92
- * @param bytes The data to store as a Uint8Array.
93
- */
94
- export function addToCache(key, bytes) {
95
- const dir = getCacheDirectory();
96
- const path = `${dir}/${key}.bin`;
97
- if (debugLogs)
98
- console.log(`Added ${(bytes.byteLength / 1024).toFixed(1)} KB to cache at \"${path}\"`);
99
- if (bytes instanceof Buffer)
100
- bytes = new Uint8Array(bytes);
101
- fs.writeFileSync(path, bytes);
102
- }
103
- /**
104
- * Try to get the data from the cache.
105
- * @param key The key to look up.
106
- * @returns The data as a Uint8Array or null if not found.
107
- * @throws If the cache file is corrupted.
108
- */
109
- export function tryGetFromCache(key) {
110
- const dir = getCacheDirectory();
111
- const path = `${dir}/${key}.bin`;
112
- if (fs.existsSync(path)) {
113
- const bytes = fs.readFileSync(path);
114
- if (bytes instanceof Buffer)
115
- return new Uint8Array(bytes);
116
- return bytes;
117
- }
118
- return null;
119
- }
120
- /** Add meta data to the cache.
121
- * @param key The key to store the meta data for.
122
- * @param meta The meta data object to store. It will be serialized to JSON and stored in a file with the key as the name with the extension ".meta".
123
- */
124
- export function addMetaToCache(key, meta) {
125
- const dir = getCacheDirectory();
126
- const metaPath = `${dir}/${key}.meta`;
127
- meta['needle-build-pipeline:version'] = buildPipelineVersion;
128
- fs.writeFileSync(metaPath, JSON.stringify(meta));
129
- }
130
- /** Try to get the meta data from the cache.
131
- * @param key The key to look up.
132
- * @param ignoreVersion If true, the version check is skipped. If false, the version must match the current build pipeline version.
133
- * @returns The meta data object or null if not found.
134
- */
135
- export function tryGetMetaFromCache(key, ignoreVersion = false) {
136
- try {
137
- const dir = getCacheDirectory();
138
- const path = `${dir}/${key}.meta`;
139
- if (fs.existsSync(path)) {
140
- const bytes = fs.readFileSync(path);
141
- if (bytes?.length > 0) {
142
- const text = bytes.toString('utf-8');
143
- if (text) {
144
- const obj = JSON.parse(text);
145
- if (ignoreVersion)
146
- return obj;
147
- if (obj['needle-build-pipeline:version'] === buildPipelineVersion)
148
- return obj;
149
- }
150
- }
151
- }
152
- return null;
153
- }
154
- catch (e) {
155
- console.error(`Error reading cache meta: ${e.message}`);
156
- return null;
157
- }
158
- }
159
- /**
160
- * Get the cache key for a property.
161
- * @param property The property to get the key for.
162
- * @returns The key as a string.
163
- */
164
- export function getKey(property) {
165
- let prefix = "prop";
166
- if (property instanceof Texture) {
167
- prefix = "image";
168
- }
169
- let hash = getHash(property);
170
- const key = `${prefix}-${hash}`;
171
- return key;
172
- }
173
- export function getKeyWithHash(property) {
174
- let prefix = "prop";
175
- if (property instanceof Texture) {
176
- prefix = "image";
177
- }
178
- let hash = getHash(property);
179
- const key = `${prefix}-${hash}`;
180
- return { key, hash };
181
- }
182
- /**
183
- * Get the hash for a property or an object.
184
- * @param property The property to get the hash for.
185
- * @param level The current level of recursion.
186
- * @returns The hash as a number.
187
- */
188
- export function getHash(property, level = 0) {
189
- let hashStr = "";
190
- let hash = 0;
191
- if (property instanceof ExtensibleProperty) {
192
- const extensions = property.listExtensions();
193
- const extras = property.getExtras();
194
- // const slots = listTextureSlots(doc, texture);
195
- // const channels = getTextureChannelMask(doc, texture);
196
- hash += hashObject({
197
- extensions,
198
- extras,
199
- });
200
- if (property instanceof Texture) {
201
- const image = property.getImage();
202
- const bytes = image?.buffer;
203
- if (bytes)
204
- hash += hashBuffer(bytes, image.byteOffset, image.byteLength);
205
- }
206
- else if (property instanceof Mesh) {
207
- const weights = property.getWeights();
208
- hash += hashObject(weights);
209
- const primitives = property.listPrimitives();
210
- for (const prim of primitives) {
211
- hash += hashString(getHash(prim, level + 1));
212
- }
213
- }
214
- else if (property instanceof Primitive) {
215
- const indices = property.getIndices();
216
- const attributes = property.listAttributes();
217
- const targets = property.listTargets();
218
- // TODO: hash buffer view if possible
219
- const semantics = property.listSemantics();
220
- const semanticsNames = semantics.map(s => {
221
- return {
222
- name: s,
223
- };
224
- });
225
- hash += hashObject({
226
- indices,
227
- attributes,
228
- targets,
229
- semantics: semanticsNames
230
- });
231
- if (indices) {
232
- const indexBuffer = indices.getArray();
233
- if (indexBuffer) {
234
- hash += hashBuffer(indexBuffer.buffer, indexBuffer.byteOffset, indexBuffer.byteLength);
235
- }
236
- }
237
- // We can not pass the whole buffer into hashObject since very large meshes will cause errors when trying to stringify
238
- // See https://linear.app/needle/issue/NE-6414
239
- for (const sem of semantics) {
240
- const arr = property.getAttribute(sem)?.getArray();
241
- if (arr) {
242
- hash += hashBuffer(arr.buffer, arr.byteOffset, arr.byteLength);
243
- }
244
- }
245
- }
246
- }
247
- else {
248
- hash += hashObject(property);
249
- }
250
- return hashStr + hash;
251
- }
252
- const cleanBuildPipelineVersion = getVersion(false).replace(/[^a-zA-Z0-9]/g, "_");
253
- function getCacheDirectory(root) {
254
- const tmpDir = path.join(tmp.tmpdir, "Needle", "build-pipeline-cache", root ? "" : cleanBuildPipelineVersion);
255
- if (!fs.existsSync(tmpDir)) {
256
- fs.mkdirSync(tmpDir, { recursive: true });
257
- }
258
- return tmpDir;
259
- }
260
- function hashObject(obj, depth = 0) {
261
- if (obj instanceof Accessor) {
262
- const buffer = obj.getArray();
263
- return hashBuffer(buffer.buffer);
264
- }
265
- else if (obj == null) {
266
- return 0;
267
- }
268
- if (typeof obj !== 'object') {
269
- return hashString(String(obj));
270
- }
271
- try {
272
- const str = JSON.stringify(obj);
273
- return hashString(str);
274
- }
275
- catch (err) {
276
- console.warn("WARN: [Cache] error during hashing of object:\n" + err);
277
- if (debugLogs)
278
- debugger;
279
- // Fallback to a simple hash of the keys and values
280
- // TODO: maybe we want to exclude this object now from the cache if it fails to serialize?
281
- let fallbackHashj = 0;
282
- if (typeof obj === 'object') {
283
- if (obj === null)
284
- return 0;
285
- for (const key in obj) {
286
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
287
- const value = obj[key];
288
- fallbackHashj += hashString(key);
289
- if (typeof value === 'object') {
290
- if (depth > 10) {
291
- fallbackHashj += hashString("[object]");
292
- }
293
- else {
294
- fallbackHashj += hashObject(value, depth + 1);
295
- }
296
- }
297
- else {
298
- fallbackHashj += hashString(String(value));
299
- }
300
- }
301
- }
302
- }
303
- return fallbackHashj;
304
- }
305
- }
306
- function hashBuffer(buffer, offset, length) {
307
- if (hasher) {
308
- const view = new Uint8Array(buffer, offset, length);
309
- return hasher.update(view).digest();
310
- }
311
- let hash = 0;
312
- const view = new Uint8Array(buffer, offset, length);
313
- for (let i = 0; i < view.length; i++) {
314
- hash = ((hash << 5) - hash) + view[i];
315
- hash |= 0;
316
- }
317
- return hash;
318
- }
319
- function hashString(str) {
320
- let hash = 0;
321
- for (let i = 0; i < str.length; i++) {
322
- hash = ((hash << 5) - hash) + str.charCodeAt(i);
323
- hash |= 0;
324
- }
325
- return hash;
326
- }
1
+ import { ExtensibleProperty, Texture, Primitive, Accessor, Mesh } from "@gltf-transform/core";
2
+ import tmp from 'tmp';
3
+ import fs, { statSync } from 'fs';
4
+ import path from 'path';
5
+ import { getVersion } from "../utils/version.js";
6
+ // import * as CHECKDISCSPACE from 'check-disk-space'
7
+ import xxhash from "xxhash-wasm";
8
+ let hasher = null;
9
+ xxhash().then(module => {
10
+ hasher = module.create32();
11
+ });
12
+ /**
13
+ * Get the available space on the cache directory in bytes.
14
+ */
15
+ // export async function getCacheAvailableSpaceOnDisc(): Promise<number> {
16
+ // try {
17
+ // const dir = getCacheDirectory();
18
+ // const res = await CHECKDISCSPACE.default(dir).catch(() => { return { free: 0 } });
19
+ // return res.free;
20
+ // }
21
+ // catch (e) {
22
+ // console.error(`Error checking disk space: ${e}`);
23
+ // }
24
+ // return 0;
25
+ // }
26
+ const buildPipelineVersion = getVersion(false);
27
+ const debugLogs = process.argv.includes("--debug");
28
+ /**
29
+ * Clear the cache.
30
+ * This will remove all files in the cache directory.
31
+ */
32
+ export function clearCache(logger) {
33
+ const dir = getCacheDirectory(true);
34
+ fs.rmSync(dir, { recursive: true });
35
+ if (!logger)
36
+ console.log(`Cleared cache at ${dir}`);
37
+ else
38
+ logger.info(`Cleared cache at ${dir}`);
39
+ }
40
+ export const cacheSizeLimit = 1024 * 5;
41
+ /**
42
+ * Ensure that the cache size does not exceed the given maximum size in MB.
43
+ * @param maxSizeMB The maximum size in MB.
44
+ */
45
+ export function limitCacheSize(maxSizeMB = cacheSizeLimit) {
46
+ const dir = getCacheDirectory(true);
47
+ console.log(`Limit cache size to ${maxSizeMB.toFixed(2)} MB at ${dir}`);
48
+ let totalSize = 0;
49
+ const files = new Array();
50
+ function recurseDirectories(dir) {
51
+ fs.readdirSync(dir).forEach(file => {
52
+ const path = `${dir}/${file}`;
53
+ const stats = statSync(path);
54
+ if (stats.isDirectory()) {
55
+ recurseDirectories(path);
56
+ }
57
+ else {
58
+ totalSize += stats.size;
59
+ files.push({ path, size: stats.size });
60
+ }
61
+ });
62
+ }
63
+ recurseDirectories(dir);
64
+ const maxSizeInBytes = maxSizeMB * 1024 * 1024;
65
+ console.log(`Current cache size is ${(totalSize / (1024 * 1024)).toFixed(1)} MB`);
66
+ if (totalSize < maxSizeInBytes)
67
+ return;
68
+ files.sort((a, b) => {
69
+ const mtimeA = fs.statSync(a.path).mtimeMs;
70
+ const mtimeB = fs.statSync(b.path).mtimeMs;
71
+ return mtimeA - mtimeB;
72
+ });
73
+ const targetSize = maxSizeInBytes * 0.9;
74
+ // remove oldest files first
75
+ let filesRemoved = 0;
76
+ while (totalSize > targetSize) {
77
+ const file = files.shift();
78
+ if (!file)
79
+ break;
80
+ if (fs.statSync(file.path).isDirectory()) {
81
+ continue;
82
+ }
83
+ filesRemoved++;
84
+ fs.rmSync(file.path);
85
+ totalSize -= file.size;
86
+ }
87
+ console.log(`Cache size reduced to ${(totalSize / (1024 * 1024)).toFixed(1)} MB (${filesRemoved} files removed)`);
88
+ }
89
+ /**
90
+ * Add data to the cache.
91
+ * @param key The key to store the data under.
92
+ * @param bytes The data to store as a Uint8Array.
93
+ */
94
+ export function addToCache(key, bytes) {
95
+ const dir = getCacheDirectory();
96
+ const path = `${dir}/${key}.bin`;
97
+ if (debugLogs)
98
+ console.log(`Added ${(bytes.byteLength / 1024).toFixed(1)} KB to cache at \"${path}\"`);
99
+ if (bytes instanceof Buffer)
100
+ bytes = new Uint8Array(bytes);
101
+ fs.writeFileSync(path, bytes);
102
+ }
103
+ /**
104
+ * Try to get the data from the cache.
105
+ * @param key The key to look up.
106
+ * @returns The data as a Uint8Array or null if not found.
107
+ * @throws If the cache file is corrupted.
108
+ */
109
+ export function tryGetFromCache(key) {
110
+ const dir = getCacheDirectory();
111
+ const path = `${dir}/${key}.bin`;
112
+ if (fs.existsSync(path)) {
113
+ const bytes = fs.readFileSync(path);
114
+ if (bytes instanceof Buffer)
115
+ return new Uint8Array(bytes);
116
+ return bytes;
117
+ }
118
+ return null;
119
+ }
120
+ /** Add meta data to the cache.
121
+ * @param key The key to store the meta data for.
122
+ * @param meta The meta data object to store. It will be serialized to JSON and stored in a file with the key as the name with the extension ".meta".
123
+ */
124
+ export function addMetaToCache(key, meta) {
125
+ const dir = getCacheDirectory();
126
+ const metaPath = `${dir}/${key}.meta`;
127
+ meta['needle-build-pipeline:version'] = buildPipelineVersion;
128
+ fs.writeFileSync(metaPath, JSON.stringify(meta));
129
+ }
130
+ /** Try to get the meta data from the cache.
131
+ * @param key The key to look up.
132
+ * @param ignoreVersion If true, the version check is skipped. If false, the version must match the current build pipeline version.
133
+ * @returns The meta data object or null if not found.
134
+ */
135
+ export function tryGetMetaFromCache(key, ignoreVersion = false) {
136
+ try {
137
+ const dir = getCacheDirectory();
138
+ const path = `${dir}/${key}.meta`;
139
+ if (fs.existsSync(path)) {
140
+ const bytes = fs.readFileSync(path);
141
+ if (bytes?.length > 0) {
142
+ const text = bytes.toString('utf-8');
143
+ if (text) {
144
+ const obj = JSON.parse(text);
145
+ if (ignoreVersion)
146
+ return obj;
147
+ if (obj['needle-build-pipeline:version'] === buildPipelineVersion)
148
+ return obj;
149
+ }
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ catch (e) {
155
+ console.error(`Error reading cache meta: ${e.message}`);
156
+ return null;
157
+ }
158
+ }
159
+ /**
160
+ * Get the cache key for a property.
161
+ * @param property The property to get the key for.
162
+ * @returns The key as a string.
163
+ */
164
+ export function getKey(property) {
165
+ let prefix = "prop";
166
+ if (property instanceof Texture) {
167
+ prefix = "image";
168
+ }
169
+ let hash = getHash(property);
170
+ const key = `${prefix}-${hash}`;
171
+ return key;
172
+ }
173
+ export function getKeyWithHash(property) {
174
+ let prefix = "prop";
175
+ if (property instanceof Texture) {
176
+ prefix = "image";
177
+ }
178
+ let hash = getHash(property);
179
+ const key = `${prefix}-${hash}`;
180
+ return { key, hash };
181
+ }
182
+ /**
183
+ * Get the hash for a property or an object.
184
+ * @param property The property to get the hash for.
185
+ * @param level The current level of recursion.
186
+ * @returns The hash as a number.
187
+ */
188
+ export function getHash(property, level = 0) {
189
+ let hashStr = "";
190
+ let hash = 0;
191
+ if (property instanceof ExtensibleProperty) {
192
+ const extensions = property.listExtensions();
193
+ const extras = property.getExtras();
194
+ // const slots = listTextureSlots(doc, texture);
195
+ // const channels = getTextureChannelMask(doc, texture);
196
+ hash += hashObject({
197
+ extensions,
198
+ extras,
199
+ });
200
+ if (property instanceof Texture) {
201
+ const image = property.getImage();
202
+ const bytes = image?.buffer;
203
+ if (bytes)
204
+ hash += hashBuffer(bytes, image.byteOffset, image.byteLength);
205
+ }
206
+ else if (property instanceof Mesh) {
207
+ const weights = property.getWeights();
208
+ hash += hashObject(weights);
209
+ const primitives = property.listPrimitives();
210
+ for (const prim of primitives) {
211
+ hash += hashString(getHash(prim, level + 1));
212
+ }
213
+ }
214
+ else if (property instanceof Primitive) {
215
+ const indices = property.getIndices();
216
+ const attributes = property.listAttributes();
217
+ const targets = property.listTargets();
218
+ // TODO: hash buffer view if possible
219
+ const semantics = property.listSemantics();
220
+ const semanticsNames = semantics.map(s => {
221
+ return {
222
+ name: s,
223
+ };
224
+ });
225
+ hash += hashObject({
226
+ indices,
227
+ attributes,
228
+ targets,
229
+ semantics: semanticsNames
230
+ });
231
+ if (indices) {
232
+ const indexBuffer = indices.getArray();
233
+ if (indexBuffer) {
234
+ hash += hashBuffer(indexBuffer.buffer, indexBuffer.byteOffset, indexBuffer.byteLength);
235
+ }
236
+ }
237
+ // We can not pass the whole buffer into hashObject since very large meshes will cause errors when trying to stringify
238
+ // See https://linear.app/needle/issue/NE-6414
239
+ for (const sem of semantics) {
240
+ const arr = property.getAttribute(sem)?.getArray();
241
+ if (arr) {
242
+ hash += hashBuffer(arr.buffer, arr.byteOffset, arr.byteLength);
243
+ }
244
+ }
245
+ }
246
+ }
247
+ else {
248
+ hash += hashObject(property);
249
+ }
250
+ return hashStr + hash;
251
+ }
252
+ const cleanBuildPipelineVersion = getVersion(false).replace(/[^a-zA-Z0-9]/g, "_");
253
+ function getCacheDirectory(root) {
254
+ const tmpDir = path.join(tmp.tmpdir, "Needle", "build-pipeline-cache", root ? "" : cleanBuildPipelineVersion);
255
+ if (!fs.existsSync(tmpDir)) {
256
+ fs.mkdirSync(tmpDir, { recursive: true });
257
+ }
258
+ return tmpDir;
259
+ }
260
+ function hashObject(obj, depth = 0) {
261
+ if (obj instanceof Accessor) {
262
+ const buffer = obj.getArray();
263
+ return hashBuffer(buffer.buffer);
264
+ }
265
+ else if (obj == null) {
266
+ return 0;
267
+ }
268
+ if (typeof obj !== 'object') {
269
+ return hashString(String(obj));
270
+ }
271
+ try {
272
+ const str = JSON.stringify(obj);
273
+ return hashString(str);
274
+ }
275
+ catch (err) {
276
+ console.warn("WARN: [Cache] error during hashing of object:\n" + err);
277
+ if (debugLogs)
278
+ debugger;
279
+ // Fallback to a simple hash of the keys and values
280
+ // TODO: maybe we want to exclude this object now from the cache if it fails to serialize?
281
+ let fallbackHashj = 0;
282
+ if (typeof obj === 'object') {
283
+ if (obj === null)
284
+ return 0;
285
+ for (const key in obj) {
286
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
287
+ const value = obj[key];
288
+ fallbackHashj += hashString(key);
289
+ if (typeof value === 'object') {
290
+ if (depth > 10) {
291
+ fallbackHashj += hashString("[object]");
292
+ }
293
+ else {
294
+ fallbackHashj += hashObject(value, depth + 1);
295
+ }
296
+ }
297
+ else {
298
+ fallbackHashj += hashString(String(value));
299
+ }
300
+ }
301
+ }
302
+ }
303
+ return fallbackHashj;
304
+ }
305
+ }
306
+ function hashBuffer(buffer, offset, length) {
307
+ if (hasher) {
308
+ const view = new Uint8Array(buffer, offset, length);
309
+ return hasher.update(view).digest();
310
+ }
311
+ let hash = 0;
312
+ const view = new Uint8Array(buffer, offset, length);
313
+ for (let i = 0; i < view.length; i++) {
314
+ hash = ((hash << 5) - hash) + view[i];
315
+ hash |= 0;
316
+ }
317
+ return hash;
318
+ }
319
+ function hashString(str) {
320
+ let hash = 0;
321
+ for (let i = 0; i < str.length; i++) {
322
+ hash = ((hash << 5) - hash) + str.charCodeAt(i);
323
+ hash |= 0;
324
+ }
325
+ return hash;
326
+ }
@@ -1 +1 @@
1
- export * from "./cache.js";
1
+ export * from "./cache.js";
@@ -1 +1 @@
1
- export * from "./cache.js";
1
+ export * from "./cache.js";
@@ -1,2 +1,2 @@
1
- #! /usr/bin/env node
2
- export {};
1
+ #! /usr/bin/env node
2
+ export {};