@forgeax/engine-import 0.1.7 → 0.1.20
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/.tsbuildinfo +1 -1
- package/dist/__tests__/scriptable-pack-texture.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-texture.unit.test.d.ts.map +1 -0
- package/dist/__tests__/source-package-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/source-package-errors.unit.test.d.ts.map +1 -0
- package/dist/browser.mjs +5 -2
- package/dist/browser.mjs.map +1 -1
- package/dist/build-production.d.ts.map +1 -1
- package/dist/catalog-inventory.d.ts +1 -1
- package/dist/catalog-inventory.d.ts.map +1 -1
- package/dist/import-runner.d.ts +9 -0
- package/dist/import-runner.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +140 -45
- package/dist/index.mjs.map +1 -1
- package/dist/scriptable-pack-host.d.ts +1 -1
- package/dist/scriptable-pack-host.d.ts.map +1 -1
- package/dist/scriptable-pack-output-producers.d.ts +1 -0
- package/dist/scriptable-pack-output-producers.d.ts.map +1 -1
- package/dist/source-package-errors.d.ts +2 -0
- package/dist/source-package-errors.d.ts.map +1 -1
- package/dist/source-path.d.ts +12 -0
- package/dist/source-path.d.ts.map +1 -0
- package/package.json +7 -7
- package/src/__tests__/import-dependencies.test.ts +34 -0
- package/src/__tests__/scriptable-pack-product.contract.test.ts +4 -5
- package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +4 -5
- package/src/__tests__/scriptable-pack-texture.unit.test.ts +96 -0
- package/src/__tests__/source-package-errors.unit.test.ts +62 -0
- package/src/build-production.ts +38 -14
- package/src/catalog-inventory.ts +2 -0
- package/src/import-runner.ts +15 -2
- package/src/index.ts +6 -0
- package/src/scriptable-pack-host.ts +6 -2
- package/src/scriptable-pack-output-producers.ts +54 -16
- package/src/source-package-errors.ts +37 -0
- package/src/source-path.ts +36 -0
|
@@ -23,7 +23,13 @@ import type {
|
|
|
23
23
|
TilesetAsset,
|
|
24
24
|
VideoAsset,
|
|
25
25
|
} from '@forgeax/engine-types';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
deriveTextureLayout,
|
|
28
|
+
err,
|
|
29
|
+
ImportError,
|
|
30
|
+
MATERIAL_TEXTURE_SLOTS,
|
|
31
|
+
ok,
|
|
32
|
+
} from '@forgeax/engine-types';
|
|
27
33
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
28
34
|
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
29
35
|
import { packMeshBinV4 } from './mesh-bin.js';
|
|
@@ -246,23 +252,50 @@ function bytes(value: Uint8Array | Uint8ClampedArray): Uint8Array {
|
|
|
246
252
|
return Uint8Array.from(value);
|
|
247
253
|
}
|
|
248
254
|
|
|
255
|
+
function textureProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
256
|
+
if (input.asset.kind !== 'texture') throw new TypeError('expected TextureAsset');
|
|
257
|
+
const texture = input.asset as TextureAsset;
|
|
258
|
+
const layout = deriveTextureLayout({
|
|
259
|
+
shape: texture.shape,
|
|
260
|
+
format: texture.format,
|
|
261
|
+
mips: texture.mips,
|
|
262
|
+
actualByteLength: texture.data.byteLength,
|
|
263
|
+
order: 'mip-major,image-major,row-major',
|
|
264
|
+
});
|
|
265
|
+
if (!layout.ok) {
|
|
266
|
+
const expectedBytes =
|
|
267
|
+
layout.error.code === 'texture-packing-invalid'
|
|
268
|
+
? layout.error.detail.expectedBytes
|
|
269
|
+
: texture.data.byteLength;
|
|
270
|
+
const actualBytes =
|
|
271
|
+
layout.error.code === 'texture-packing-invalid'
|
|
272
|
+
? layout.error.detail.actualBytes
|
|
273
|
+
: texture.data.byteLength;
|
|
274
|
+
throw new TypeError(
|
|
275
|
+
`texture data is not canonical: expected ${expectedBytes} bytes, got ${actualBytes}`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
payload: texture,
|
|
280
|
+
refs: [],
|
|
281
|
+
artifacts: {
|
|
282
|
+
body: {
|
|
283
|
+
mediaType:
|
|
284
|
+
texture.format === 'r8unorm'
|
|
285
|
+
? 'application/x-forgeax-r8'
|
|
286
|
+
: `application/x-forgeax-${texture.format}`,
|
|
287
|
+
assetCodec: { name: texture.format, version: '1' },
|
|
288
|
+
bytes: bytes(texture.data),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
249
294
|
function ordinaryPodProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
250
295
|
const asset = input.asset;
|
|
251
296
|
switch (asset.kind) {
|
|
252
|
-
case 'texture':
|
|
253
|
-
|
|
254
|
-
return {
|
|
255
|
-
payload: texture,
|
|
256
|
-
refs: [],
|
|
257
|
-
artifacts: {
|
|
258
|
-
body: {
|
|
259
|
-
mediaType: 'image/raw',
|
|
260
|
-
assetCodec: { name: 'raw-image', version: '1' },
|
|
261
|
-
bytes: bytes(texture.data),
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
};
|
|
265
|
-
}
|
|
297
|
+
case 'texture':
|
|
298
|
+
return textureProduct(input);
|
|
266
299
|
case 'equirect': {
|
|
267
300
|
const equirect = asset as EquirectAsset;
|
|
268
301
|
return {
|
|
@@ -418,6 +451,11 @@ export const materialAssetOutputProducer = createSafeProducer(
|
|
|
418
451
|
materialProduct,
|
|
419
452
|
);
|
|
420
453
|
export const meshAssetOutputProducer = createSafeProducer('mesh', 'mesh-binary/4', meshProduct);
|
|
454
|
+
export const textureAssetOutputProducer = createSafeProducer(
|
|
455
|
+
'texture',
|
|
456
|
+
'texture-pack/1',
|
|
457
|
+
textureProduct,
|
|
458
|
+
);
|
|
421
459
|
|
|
422
460
|
export function createSceneAssetOutputProducer(
|
|
423
461
|
sceneComponents: readonly ScriptablePackSceneComponent[] = [],
|
|
@@ -434,9 +472,9 @@ export function createStandardAssetOutputProducerRegistry(
|
|
|
434
472
|
const registry = new AssetOutputProducerRegistry();
|
|
435
473
|
registry.register(materialAssetOutputProducer);
|
|
436
474
|
registry.register(meshAssetOutputProducer);
|
|
475
|
+
registry.register(textureAssetOutputProducer);
|
|
437
476
|
registry.register(createSceneAssetOutputProducer(sceneComponents));
|
|
438
477
|
for (const kind of [
|
|
439
|
-
'texture',
|
|
440
478
|
'equirect',
|
|
441
479
|
'sampler',
|
|
442
480
|
'font',
|
|
@@ -118,10 +118,45 @@ function importFailureCode(error: ImportError): SourcePackageErrorCode {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
const IMPORT_ERROR_CODES = new Set([
|
|
122
|
+
'importer-not-registered',
|
|
123
|
+
'source-read-failed',
|
|
124
|
+
'import-produced-no-assets',
|
|
125
|
+
'guid-mismatch',
|
|
126
|
+
'mesh-material-slot-topology-change',
|
|
127
|
+
'import-internal-error',
|
|
128
|
+
'source-validation-failed',
|
|
129
|
+
'unknown-source-key',
|
|
130
|
+
'duplicate-source-key',
|
|
131
|
+
'invalid-source-overrides',
|
|
132
|
+
'invalid-source-override-payload',
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
function findNormalizableCause(error: unknown, seen = new Set<unknown>()): unknown {
|
|
136
|
+
if (error === null || typeof error !== 'object' || seen.has(error)) return undefined;
|
|
137
|
+
seen.add(error);
|
|
138
|
+
if (isSourcePackageError(error) || isScriptablePackFailure(error) || isImportError(error)) {
|
|
139
|
+
return error;
|
|
140
|
+
}
|
|
141
|
+
const cause = (error as { readonly cause?: unknown }).cause;
|
|
142
|
+
return cause === undefined ? undefined : findNormalizableCause(cause, seen);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Return whether an error chain contains a source-package/import contract error. */
|
|
146
|
+
export function containsSourcePackageError(error: unknown, seen = new Set<unknown>()): boolean {
|
|
147
|
+
if (error === null || typeof error !== 'object' || seen.has(error)) return false;
|
|
148
|
+
seen.add(error);
|
|
149
|
+
if (isSourcePackageError(error) || isImportError(error)) return true;
|
|
150
|
+
const cause = (error as { readonly cause?: unknown }).cause;
|
|
151
|
+
return cause !== undefined && containsSourcePackageError(cause, seen);
|
|
152
|
+
}
|
|
153
|
+
|
|
121
154
|
export function normalizeSourcePackageError(
|
|
122
155
|
error: unknown,
|
|
123
156
|
context: SourcePackageErrorContext,
|
|
124
157
|
): SourcePackageError {
|
|
158
|
+
const cause = findNormalizableCause(error);
|
|
159
|
+
if (cause !== undefined && cause !== error) return normalizeSourcePackageError(cause, context);
|
|
125
160
|
if (isSourcePackageError(error)) return error;
|
|
126
161
|
if (isScriptablePackFailure(error)) {
|
|
127
162
|
return sourcePackageError('source-package-conversion-failed', context, {
|
|
@@ -174,6 +209,8 @@ function isImportError(error: unknown): error is ImportError {
|
|
|
174
209
|
error !== null &&
|
|
175
210
|
typeof error === 'object' &&
|
|
176
211
|
'code' in error &&
|
|
212
|
+
typeof error.code === 'string' &&
|
|
213
|
+
IMPORT_ERROR_CODES.has(error.code) &&
|
|
177
214
|
'expected' in error &&
|
|
178
215
|
'hint' in error &&
|
|
179
216
|
'detail' in error
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { catalogSourcePathFor } from '@forgeax/engine-pack/build';
|
|
3
|
+
import type { ScanSourceDeclaration } from '@forgeax/engine-pack/scanner';
|
|
4
|
+
|
|
5
|
+
function normalizeCatalogPath(path: string): string {
|
|
6
|
+
return path.replaceAll('\\', '/').replace(/^\.\//, '');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CatalogSourceDeclaration {
|
|
10
|
+
readonly sourcePath: string;
|
|
11
|
+
readonly declaration: ScanSourceDeclaration;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a Catalog row's logical source locator back to its physical scanner
|
|
16
|
+
* declaration. Catalog rows are host identities; declaration maps remain
|
|
17
|
+
* physical because production still has to read those files from disk.
|
|
18
|
+
*/
|
|
19
|
+
export function sourceDeclarationForCatalogPath(
|
|
20
|
+
sourcePath: string,
|
|
21
|
+
declarations: ReadonlyMap<string, ScanSourceDeclaration>,
|
|
22
|
+
sourceIdentityFor?: (sourcePath: string) => string,
|
|
23
|
+
cwd = process.cwd(),
|
|
24
|
+
): CatalogSourceDeclaration | undefined {
|
|
25
|
+
const normalized = normalizeCatalogPath(sourcePath);
|
|
26
|
+
const direct = declarations.get(resolve(cwd, sourcePath));
|
|
27
|
+
if (direct !== undefined) {
|
|
28
|
+
return { sourcePath: resolve(cwd, sourcePath), declaration: direct };
|
|
29
|
+
}
|
|
30
|
+
for (const [physicalPath, declaration] of declarations) {
|
|
31
|
+
if (catalogSourcePathFor(cwd, physicalPath, sourceIdentityFor) === normalized) {
|
|
32
|
+
return { sourcePath: physicalPath, declaration };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|