@needle-tools/gltf-build-pipeline 2.15.11 → 2.16.0-next.fc19d36
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/dist/cache/cache.d.ts +2 -0
- package/dist/cache/cache.js +2 -2
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.js +1 -0
- package/dist/cache/job-cache.d.ts +26 -0
- package/dist/cache/job-cache.js +84 -0
- package/dist/cli/index.js +36 -2
- package/dist/utils/version.gen.d.ts +1 -1
- package/dist/utils/version.gen.js +1 -1
- package/package.json +1 -1
package/dist/cache/cache.d.ts
CHANGED
|
@@ -54,3 +54,5 @@ export declare function getKeyWithHash(property: ExtensibleProperty): {
|
|
|
54
54
|
* @returns The hash as a number.
|
|
55
55
|
*/
|
|
56
56
|
export declare function getHash(property: ExtensibleProperty | object, level?: number): string;
|
|
57
|
+
export declare function hashBuffer(buffer: ArrayBufferLike, offset?: number, length?: number): number;
|
|
58
|
+
export declare function hashString(str: string): number;
|
package/dist/cache/cache.js
CHANGED
|
@@ -307,7 +307,7 @@ function hashObject(obj, depth = 0) {
|
|
|
307
307
|
return fallbackHash;
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
|
-
function hashBuffer(buffer, offset, length) {
|
|
310
|
+
export function hashBuffer(buffer, offset, length) {
|
|
311
311
|
if (xxHashModule) {
|
|
312
312
|
const view = new Uint8Array(buffer, offset, length);
|
|
313
313
|
return xxHashModule.h32Raw(view);
|
|
@@ -320,7 +320,7 @@ function hashBuffer(buffer, offset, length) {
|
|
|
320
320
|
}
|
|
321
321
|
return hash;
|
|
322
322
|
}
|
|
323
|
-
function hashString(str) {
|
|
323
|
+
export function hashString(str) {
|
|
324
324
|
let hash = 0;
|
|
325
325
|
for (let i = 0; i < str.length; i++) {
|
|
326
326
|
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
package/dist/cache/index.d.ts
CHANGED
package/dist/cache/index.js
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ILogger } from '@gltf-transform/core';
|
|
2
|
+
import { Config } from '../config/index.js';
|
|
3
|
+
interface JobCacheOptions {
|
|
4
|
+
compress: boolean;
|
|
5
|
+
progressive: boolean;
|
|
6
|
+
config: Config;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Compute a job cache key from pipeline options and input file content.
|
|
10
|
+
* The key uniquely identifies a specific input + configuration combination.
|
|
11
|
+
* Currently only supports .glb files (hashes the full file content).
|
|
12
|
+
* @returns The job key string, or null if the file format is not supported.
|
|
13
|
+
*/
|
|
14
|
+
export declare function computeJobKey(options: JobCacheOptions, inputFilePath: string): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Save the result of a completed job to the cache.
|
|
17
|
+
* Stores the content of each output file and a manifest linking them.
|
|
18
|
+
*/
|
|
19
|
+
export declare function saveJobResult(jobKey: string, outputFiles: string[], logger: ILogger): void;
|
|
20
|
+
/**
|
|
21
|
+
* Try to restore a previously cached job result.
|
|
22
|
+
* Loads all cached files into memory first, then writes them only if all are available.
|
|
23
|
+
* Returns the list of restored file paths, or null if the cache is incomplete.
|
|
24
|
+
*/
|
|
25
|
+
export declare function tryRestoreJobResult(jobKey: string, outputDir: string, logger: ILogger): string[] | null;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// TODO: avoid storing LOD files twice in cache. Currently LODs are stored by the per-asset cache
|
|
2
|
+
// (e.g. "image-{hash}", "packed-{hash}") AND again by the job cache ("job-{key}-fN").
|
|
3
|
+
// Instead, progressive_results should carry { path, cacheKey? } so the job manifest can reference
|
|
4
|
+
// existing per-asset cache keys for LODs and only store the main GLB under a job-specific key.
|
|
5
|
+
// This requires threading cache key collection through make_progressive and packGLTF.
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { addMetaToCache, addToCache, tryGetFromCache, tryGetMetaFromCache, hashBuffer, hashString } from './cache.js';
|
|
9
|
+
/**
|
|
10
|
+
* Compute a job cache key from pipeline options and input file content.
|
|
11
|
+
* The key uniquely identifies a specific input + configuration combination.
|
|
12
|
+
* Currently only supports .glb files (hashes the full file content).
|
|
13
|
+
* @returns The job key string, or null if the file format is not supported.
|
|
14
|
+
*/
|
|
15
|
+
// TODO: add support for .gltf files (need to hash referenced .bin files as well)
|
|
16
|
+
export function computeJobKey(options, inputFilePath) {
|
|
17
|
+
if (!inputFilePath.endsWith('.glb'))
|
|
18
|
+
return null;
|
|
19
|
+
const fileBytes = readFileSync(inputFilePath);
|
|
20
|
+
const fileHash = hashBuffer(fileBytes.buffer, fileBytes.byteOffset, fileBytes.byteLength);
|
|
21
|
+
const optionsStr = JSON.stringify({
|
|
22
|
+
compress: options.compress,
|
|
23
|
+
progressive: options.progressive,
|
|
24
|
+
config: options.config,
|
|
25
|
+
});
|
|
26
|
+
const optionsHash = hashString(optionsStr);
|
|
27
|
+
return `job-${optionsHash}-${fileHash}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Save the result of a completed job to the cache.
|
|
31
|
+
* Stores the content of each output file and a manifest linking them.
|
|
32
|
+
*/
|
|
33
|
+
export function saveJobResult(jobKey, outputFiles, logger) {
|
|
34
|
+
const entries = [];
|
|
35
|
+
for (let i = 0; i < outputFiles.length; i++) {
|
|
36
|
+
const filePath = outputFiles[i];
|
|
37
|
+
if (!existsSync(filePath))
|
|
38
|
+
continue;
|
|
39
|
+
const cacheKey = `${jobKey}-f${i}`;
|
|
40
|
+
const content = readFileSync(filePath);
|
|
41
|
+
addToCache(cacheKey, content);
|
|
42
|
+
entries.push({
|
|
43
|
+
fileName: path.basename(filePath),
|
|
44
|
+
cacheKey,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (entries.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
const manifest = { entries };
|
|
50
|
+
addMetaToCache(jobKey, manifest);
|
|
51
|
+
logger.debug(`[Job Cache] Saved ${entries.length} file(s) for job ${jobKey}`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Try to restore a previously cached job result.
|
|
55
|
+
* Loads all cached files into memory first, then writes them only if all are available.
|
|
56
|
+
* Returns the list of restored file paths, or null if the cache is incomplete.
|
|
57
|
+
*/
|
|
58
|
+
export function tryRestoreJobResult(jobKey, outputDir, logger) {
|
|
59
|
+
const manifest = tryGetMetaFromCache(jobKey);
|
|
60
|
+
if (!manifest?.entries?.length)
|
|
61
|
+
return null;
|
|
62
|
+
// Load all entries into memory — fail fast if any are missing
|
|
63
|
+
const loaded = [];
|
|
64
|
+
for (const entry of manifest.entries) {
|
|
65
|
+
const cached = tryGetFromCache(entry.cacheKey);
|
|
66
|
+
if (!cached) {
|
|
67
|
+
logger.debug(`[Job Cache] Cache miss: missing entry ${entry.cacheKey}`);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
loaded.push({ entry, data: cached });
|
|
71
|
+
}
|
|
72
|
+
// All entries present — write to output directory
|
|
73
|
+
if (!existsSync(outputDir)) {
|
|
74
|
+
mkdirSync(outputDir, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
const restoredPaths = [];
|
|
77
|
+
for (const { entry, data } of loaded) {
|
|
78
|
+
const outPath = path.join(outputDir, entry.fileName);
|
|
79
|
+
writeFileSync(outPath, data);
|
|
80
|
+
restoredPaths.push(outPath);
|
|
81
|
+
}
|
|
82
|
+
logger.info(`[Job Cache] Restored ${restoredPaths.length} file(s) from cache`);
|
|
83
|
+
return restoredPaths;
|
|
84
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -6,9 +6,10 @@ import { getVersion } from '../utils/version.js';
|
|
|
6
6
|
import { UsecaseOptions, createConfig, getConfig } from '../config/index.js';
|
|
7
7
|
import { isLOD, make_progressive } from '../transforms/needle_progressive.js';
|
|
8
8
|
import { cacheSizeLimit, clearCache, limitCacheSize } from '../cache/cache.js';
|
|
9
|
-
import {
|
|
9
|
+
import { computeJobKey, saveJobResult, tryRestoreJobResult } from '../cache/job-cache.js';
|
|
10
|
+
import { existsSync, statSync } from 'fs';
|
|
10
11
|
import { ensureIsDirectory, foreachGLTF, isDirectory } from '../utils/fileutils.js';
|
|
11
|
-
import path, { resolve } from 'path';
|
|
12
|
+
import path, { dirname, resolve } from 'path';
|
|
12
13
|
import { ERROR_CODES } from '../constants.js';
|
|
13
14
|
import { trackPipelineStart, trackPipelineEnd, trackError } from '../utils/analytics.js';
|
|
14
15
|
// For testing / dev you can run `npm link` in the package directory
|
|
@@ -140,6 +141,32 @@ Each version will be compressed and written to the output directory.
|
|
|
140
141
|
logger.debug(`→ Ignore existing LOD file at ${file}`);
|
|
141
142
|
return;
|
|
142
143
|
}
|
|
144
|
+
// Job-level cache: skip all work if the same input + options were already processed
|
|
145
|
+
const jobKey = useCache ? computeJobKey({
|
|
146
|
+
compress: options.compress === true,
|
|
147
|
+
progressive: options.progressive === true,
|
|
148
|
+
config,
|
|
149
|
+
}, file) : undefined;
|
|
150
|
+
if (jobKey) {
|
|
151
|
+
const outputDir = output
|
|
152
|
+
? (existsSync(output) && isDirectory(output) ? output : dirname(output))
|
|
153
|
+
: dirname(file);
|
|
154
|
+
const restored = tryRestoreJobResult(jobKey, outputDir, logger);
|
|
155
|
+
if (restored) {
|
|
156
|
+
const inputFileSize = statSync(file).size / 1024 / 1024;
|
|
157
|
+
for (const f of restored) {
|
|
158
|
+
set.add(f);
|
|
159
|
+
if (stats) {
|
|
160
|
+
const sizeInMB = statSync(f).size / 1024 / 1024;
|
|
161
|
+
stats.totalFilesProcessed++;
|
|
162
|
+
stats.totalFileSizeInMB += sizeInMB;
|
|
163
|
+
stats.totalFileSizeInMBBefore += inputFileSize;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
logger.info(`→ [CACHE] Restored ${restored.length} file(s)for job ${jobKey}`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
143
170
|
logger.info(`→ Transform ${file}`);
|
|
144
171
|
const progressive_results = new Array();
|
|
145
172
|
if (options.progressive === true) {
|
|
@@ -195,6 +222,13 @@ Each version will be compressed and written to the output directory.
|
|
|
195
222
|
}
|
|
196
223
|
}
|
|
197
224
|
}
|
|
225
|
+
// Save job result to cache for future runs
|
|
226
|
+
if (jobKey) {
|
|
227
|
+
const allOutputFiles = progressive_results.length > 0
|
|
228
|
+
? progressive_results
|
|
229
|
+
: [output || file];
|
|
230
|
+
saveJobResult(jobKey, allOutputFiles, logger);
|
|
231
|
+
}
|
|
198
232
|
});
|
|
199
233
|
printStats(stats, logger, config);
|
|
200
234
|
trackPipelineEnd({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.
|
|
1
|
+
export declare const version = "2.16.0-next.fc19d36";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = "2.
|
|
1
|
+
export const version = "2.16.0-next.fc19d36";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/gltf-build-pipeline",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0-next.fc19d36",
|
|
4
4
|
"description": "Pipeline and tools for optimizing gltf files using gltf-transform and compression settings within glTF extensions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|