@driftengine/assets 4.8.6 → 4.9.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Model readers for nine formats — `.blend` among them, read from the file's own DNA — and the streaming loader that uploads them.
4
4
 
5
- **Cost: 26.3 KB gzipped on top of core.** Measured by `scripts/size-gate.test.mjs`, which fails if it drifts more than
5
+ **Cost: 25.9 KB gzipped on top of core.** Measured by `scripts/size-gate.test.mjs`, which fails if it drifts more than
6
6
  3% — the number is a fact about the build rather than a claim in a document.
7
7
 
8
8
  Part of [DriftEngine](../../README.md). See [`docs/ARCHITECTURE.md`](../../docs/ARCHITECTURE.md)
package/dist/bcLoad.d.ts CHANGED
@@ -23,4 +23,9 @@ export interface BcDecoder {
23
23
  readonly reason: string;
24
24
  dispose(): void;
25
25
  }
26
+ /**
27
+ * A decoder over a worker `spawn` starts, or on the main thread where it is null — the default, since
28
+ * the worker's factory lives behind its own specifier (`@driftengine/assets/bcWorkers`) and a caller
29
+ * names it.
30
+ */
26
31
  export declare function createBcDecoder(spawn?: (() => BcWorker) | null): BcDecoder;
package/dist/bcLoad.js CHANGED
@@ -8,10 +8,11 @@
8
8
  * otherwise**, which on a phone is every BC texture: in a worker, because a 2048² BC7 image is
9
9
  * 262,144 blocks and a phone's main thread spends the better part of a second on them.
10
10
  *
11
- * **Never silently slower.** Where there is no `Worker`, or the one this module starts fails to
12
- * load — a bundler that does not understand `new Worker(new URL(…))` — the decode moves to the main
13
- * thread and `reason` says so, and the loader prints it once. A request in flight when the worker
14
- * fails is decoded again there rather than left waiting.
11
+ * **Never silently slower.** Where no worker is named (`spawnBcWorker`, behind a specifier of its
12
+ * own so the barrel names none), where there is no `Worker`, or where the one started fails to load —
13
+ * a bundler that does not understand `new Worker(new URL(…))` — the decode moves to the main thread
14
+ * and `reason` says so, and the loader prints it once. A request in flight when the worker fails is
15
+ * decoded again there rather than left waiting.
15
16
  */
16
17
  import { uploadsCompressed } from '@driftengine/core';
17
18
  import { answerBcRequest } from './bcAnswer.js';
@@ -36,19 +37,11 @@ function settle(reply, pending) {
36
37
  pending.reject(new Error(reply.error ?? 'bc: the decode answered nothing'));
37
38
  }
38
39
  /**
39
- * The worker this package ships, built the way a bundler understands, or null where there is no
40
- * `Worker` at all. `createBcDecoder`'s `spawn` is the way past either.
40
+ * A decoder over a worker `spawn` starts, or on the main thread where it is null — the default, since
41
+ * the worker's factory lives behind its own specifier (`@driftengine/assets/bcWorkers`) and a caller
42
+ * names it.
41
43
  */
42
- const defaultSpawn =
43
- // platform: feature probe — no `Worker` means BC decodes on the main thread, with a reason
44
- typeof Worker === 'undefined'
45
- ? null
46
- : () =>
47
- // platform: browser default — `createBcDecoder(spawn)` is the seam a host supplies
48
- new Worker(new URL('./bcWorker.js', import.meta.url), {
49
- type: 'module',
50
- });
51
- export function createBcDecoder(spawn = defaultSpawn) {
44
+ export function createBcDecoder(spawn = null) {
52
45
  let worker = null;
53
46
  let reason = '';
54
47
  const pending = new Map();
@@ -62,7 +55,9 @@ export function createBcDecoder(spawn = defaultSpawn) {
62
55
  pending.clear();
63
56
  };
64
57
  if (spawn === null) {
65
- reason = 'BC textures decode on the main thread: this runtime has no Worker';
58
+ reason =
59
+ 'BC textures decode on the main thread: no worker was named. Pass `bcWorker: spawnBcWorker`, ' +
60
+ "exported by '@driftengine/assets/bcWorkers', to decode them off it";
66
61
  }
67
62
  else {
68
63
  try {
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The BC decode worker, behind its own specifier, for the reason `physics/src/workers.ts` gives.
3
+ *
4
+ * **A bundler emits a worker before it decides the code reaching it is unreachable.** The loader's
5
+ * decoder built its worker with `new Worker(new URL('./bcWorker.ts', import.meta.url))` inside the
6
+ * barrel's graph, and Vite rewrites that at transform time, before tree-shaking, so every consumer
7
+ * importing anything from `@driftengine/assets` had the BC decoder's chunk written into its build,
8
+ * whether or not it ever loaded a BC texture. It did so from 4.8.4 to 4.8.6;
9
+ * `scripts/worker-entry.test.mjs` asserts that no barrel names a worker, and it skipped this barrel
10
+ * whenever the packages were not built, which is how the chunk shipped past it.
11
+ *
12
+ * So a loader that should decode BC off the main thread names the factory:
13
+ *
14
+ * ```ts
15
+ * import { DrftLoader } from '@driftengine/assets';
16
+ * import { spawnBcWorker } from '@driftengine/assets/bcWorkers';
17
+ *
18
+ * const loader = new DrftLoader(renderer, { bcWorker: spawnBcWorker });
19
+ * ```
20
+ *
21
+ * **A declared subpath, so it resolves the way the barrel does**: to the build by default and to
22
+ * this file under `drift-source`. The path into `src/` that 4.8.7 documented still resolves, through
23
+ * the manifest's `./*` passthrough, and hands every consumer TypeScript inside `node_modules`, which
24
+ * is the default the 2026-09-13 flip took away from the barrels; a resolver that maps a package's
25
+ * subpaths into `src/` also doubled it, to `src/src/bcWorkers.ts`.
26
+ *
27
+ * Without it BC textures decode on the main thread, and the loader says so once. **What that costs**
28
+ * is the decode of a texture the device cannot take as blocks, which on a phone is every BC texture:
29
+ * a 2048² BC7 image is 262,144 blocks.
30
+ */
31
+ import type { BcWorker } from './bcLoad.ts';
32
+ /** A BC decode worker, as a bundler understands one. Throws where the runtime has no `Worker`. */
33
+ export declare function spawnBcWorker(): BcWorker;
@@ -0,0 +1,7 @@
1
+ /** A BC decode worker, as a bundler understands one. Throws where the runtime has no `Worker`. */
2
+ export function spawnBcWorker() {
3
+ // platform: browser default — `DrftLoaderOptions.bcWorker` is the seam a host supplies
4
+ return new Worker(new URL('./bcWorker.js', import.meta.url), {
5
+ type: 'module',
6
+ });
7
+ }
@@ -5,6 +5,7 @@ import type { SurfaceTextureHandle } from '@driftengine/core';
5
5
  import type { DrftMaterial } from '@driftengine/drft';
6
6
  import type { DrftFieldPlacement } from './fieldPlacement.ts';
7
7
  import { TextureSet } from './drftTextures.ts';
8
+ import type { BcWorker } from './bcLoad.ts';
8
9
  import type { DrawSurfaceOverride } from './drawKey.ts';
9
10
  import type { AnimationClip, DrftLight, DrftMorph, DrftNode, DrftSkin } from '@driftengine/drft';
10
11
  import type { DrftLightVolume, EntsScene, NavPolyMesh } from '@driftengine/drft';
@@ -48,6 +49,13 @@ export interface DrftLoaderOptions {
48
49
  * the default.
49
50
  */
50
51
  readonly fetchImpl?: FetchLike;
52
+ /**
53
+ * What starts the worker a BC texture the device cannot take as blocks is decoded in:
54
+ * `spawnBcWorker`, from `@driftengine/assets/bcWorkers`. Absent, they decode on the main
55
+ * thread and the loader says so once — the factory is behind its own specifier so a consumer that
56
+ * never names it has no worker in its build. See `bcWorkers.ts`.
57
+ */
58
+ readonly bcWorker?: () => BcWorker;
51
59
  /**
52
60
  * The most parts one `update` may take, however cheap they turn out to be.
53
61
  *
@@ -1251,7 +1251,7 @@ export class DrftLoader {
1251
1251
  return;
1252
1252
  }
1253
1253
  if (this.bcDecoder === null)
1254
- this.bcDecoder = createBcDecoder();
1254
+ this.bcDecoder = createBcDecoder(this.options.bcWorker ?? null);
1255
1255
  const decoder = this.bcDecoder;
1256
1256
  decoder
1257
1257
  .decode(image, asImage)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftengine/assets",
3
- "version": "4.8.6",
3
+ "version": "4.9.0",
4
4
  "description": "Model readers and the streaming loader for the .drft container",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -12,6 +12,11 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "default": "./dist/index.js"
14
14
  },
15
+ "./bcWorkers": {
16
+ "drift-source": "./src/bcWorkers.ts",
17
+ "types": "./dist/bcWorkers.d.ts",
18
+ "default": "./dist/bcWorkers.js"
19
+ },
15
20
  "./package.json": "./package.json",
16
21
  "./*": "./*"
17
22
  },
@@ -27,11 +32,11 @@
27
32
  ],
28
33
  "sideEffects": false,
29
34
  "dependencies": {
30
- "@driftengine/drft": "4.8.6",
31
- "@driftengine/texture": "4.8.6"
35
+ "@driftengine/drft": "4.9.0",
36
+ "@driftengine/texture": "4.9.0"
32
37
  },
33
38
  "peerDependencies": {
34
- "@driftengine/core": "4.8.6"
39
+ "@driftengine/core": "4.9.0"
35
40
  },
36
41
  "author": "Drift Technologies",
37
42
  "repository": {
package/src/bcLoad.ts CHANGED
@@ -8,10 +8,11 @@
8
8
  * otherwise**, which on a phone is every BC texture: in a worker, because a 2048² BC7 image is
9
9
  * 262,144 blocks and a phone's main thread spends the better part of a second on them.
10
10
  *
11
- * **Never silently slower.** Where there is no `Worker`, or the one this module starts fails to
12
- * load — a bundler that does not understand `new Worker(new URL(…))` — the decode moves to the main
13
- * thread and `reason` says so, and the loader prints it once. A request in flight when the worker
14
- * fails is decoded again there rather than left waiting.
11
+ * **Never silently slower.** Where no worker is named (`spawnBcWorker`, behind a specifier of its
12
+ * own so the barrel names none), where there is no `Worker`, or where the one started fails to load —
13
+ * a bundler that does not understand `new Worker(new URL(…))` — the decode moves to the main thread
14
+ * and `reason` says so, and the loader prints it once. A request in flight when the worker fails is
15
+ * decoded again there rather than left waiting.
15
16
  */
16
17
  import { uploadsCompressed } from '@driftengine/core';
17
18
  import type { CompressedTextureFormat } from '@driftengine/core';
@@ -74,20 +75,11 @@ function settle(reply: BcReply, pending: Pending): void {
74
75
  }
75
76
 
76
77
  /**
77
- * The worker this package ships, built the way a bundler understands, or null where there is no
78
- * `Worker` at all. `createBcDecoder`'s `spawn` is the way past either.
78
+ * A decoder over a worker `spawn` starts, or on the main thread where it is null — the default, since
79
+ * the worker's factory lives behind its own specifier (`@driftengine/assets/bcWorkers`) and a caller
80
+ * names it.
79
81
  */
80
- const defaultSpawn: (() => BcWorker) | null =
81
- // platform: feature probe — no `Worker` means BC decodes on the main thread, with a reason
82
- typeof Worker === 'undefined'
83
- ? null
84
- : (): BcWorker =>
85
- // platform: browser default — `createBcDecoder(spawn)` is the seam a host supplies
86
- new Worker(new URL('./bcWorker.ts', import.meta.url), {
87
- type: 'module',
88
- }) as unknown as BcWorker;
89
-
90
- export function createBcDecoder(spawn: (() => BcWorker) | null = defaultSpawn): BcDecoder {
82
+ export function createBcDecoder(spawn: (() => BcWorker) | null = null): BcDecoder {
91
83
  let worker: BcWorker | null = null;
92
84
  let reason = '';
93
85
  const pending = new Map<number, Pending>();
@@ -103,7 +95,9 @@ export function createBcDecoder(spawn: (() => BcWorker) | null = defaultSpawn):
103
95
  };
104
96
 
105
97
  if (spawn === null) {
106
- reason = 'BC textures decode on the main thread: this runtime has no Worker';
98
+ reason =
99
+ 'BC textures decode on the main thread: no worker was named. Pass `bcWorker: spawnBcWorker`, ' +
100
+ "exported by '@driftengine/assets/bcWorkers', to decode them off it";
107
101
  } else {
108
102
  try {
109
103
  worker = spawn();
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The BC decode worker, behind its own specifier, for the reason `physics/src/workers.ts` gives.
3
+ *
4
+ * **A bundler emits a worker before it decides the code reaching it is unreachable.** The loader's
5
+ * decoder built its worker with `new Worker(new URL('./bcWorker.ts', import.meta.url))` inside the
6
+ * barrel's graph, and Vite rewrites that at transform time, before tree-shaking, so every consumer
7
+ * importing anything from `@driftengine/assets` had the BC decoder's chunk written into its build,
8
+ * whether or not it ever loaded a BC texture. It did so from 4.8.4 to 4.8.6;
9
+ * `scripts/worker-entry.test.mjs` asserts that no barrel names a worker, and it skipped this barrel
10
+ * whenever the packages were not built, which is how the chunk shipped past it.
11
+ *
12
+ * So a loader that should decode BC off the main thread names the factory:
13
+ *
14
+ * ```ts
15
+ * import { DrftLoader } from '@driftengine/assets';
16
+ * import { spawnBcWorker } from '@driftengine/assets/bcWorkers';
17
+ *
18
+ * const loader = new DrftLoader(renderer, { bcWorker: spawnBcWorker });
19
+ * ```
20
+ *
21
+ * **A declared subpath, so it resolves the way the barrel does**: to the build by default and to
22
+ * this file under `drift-source`. The path into `src/` that 4.8.7 documented still resolves, through
23
+ * the manifest's `./*` passthrough, and hands every consumer TypeScript inside `node_modules`, which
24
+ * is the default the 2026-09-13 flip took away from the barrels; a resolver that maps a package's
25
+ * subpaths into `src/` also doubled it, to `src/src/bcWorkers.ts`.
26
+ *
27
+ * Without it BC textures decode on the main thread, and the loader says so once. **What that costs**
28
+ * is the decode of a texture the device cannot take as blocks, which on a phone is every BC texture:
29
+ * a 2048² BC7 image is 262,144 blocks.
30
+ */
31
+ import type { BcWorker } from './bcLoad.ts';
32
+
33
+ /** A BC decode worker, as a bundler understands one. Throws where the runtime has no `Worker`. */
34
+ export function spawnBcWorker(): BcWorker {
35
+ // platform: browser default — `DrftLoaderOptions.bcWorker` is the seam a host supplies
36
+ return new Worker(new URL('./bcWorker.ts', import.meta.url), {
37
+ type: 'module',
38
+ }) as unknown as BcWorker;
39
+ }
package/src/drftLoader.ts CHANGED
@@ -11,7 +11,7 @@ import type { DrftFieldPlacement } from './fieldPlacement.ts';
11
11
  import type { DrftTexture } from '@driftengine/drft';
12
12
  import { TextureSet, textureColorSpaces } from './drftTextures.ts';
13
13
  import { bcPlan, createBcDecoder } from './bcLoad.ts';
14
- import type { BcDecoder } from './bcLoad.ts';
14
+ import type { BcDecoder, BcWorker } from './bcLoad.ts';
15
15
  import { decodeBc } from './bcDecode.ts';
16
16
  import { isCompressedSource, uploadsCompressed } from '@driftengine/core';
17
17
  import { CODEC_BC, codecName, readBcPayload } from '@driftengine/drft';
@@ -82,6 +82,13 @@ export interface DrftLoaderOptions {
82
82
  * the default.
83
83
  */
84
84
  readonly fetchImpl?: FetchLike;
85
+ /**
86
+ * What starts the worker a BC texture the device cannot take as blocks is decoded in:
87
+ * `spawnBcWorker`, from `@driftengine/assets/bcWorkers`. Absent, they decode on the main
88
+ * thread and the loader says so once — the factory is behind its own specifier so a consumer that
89
+ * never names it has no worker in its build. See `bcWorkers.ts`.
90
+ */
91
+ readonly bcWorker?: () => BcWorker;
85
92
  /**
86
93
  * The most parts one `update` may take, however cheap they turn out to be.
87
94
  *
@@ -1523,7 +1530,7 @@ export class DrftLoader {
1523
1530
  done(image);
1524
1531
  return;
1525
1532
  }
1526
- if (this.bcDecoder === null) this.bcDecoder = createBcDecoder();
1533
+ if (this.bcDecoder === null) this.bcDecoder = createBcDecoder(this.options.bcWorker ?? null);
1527
1534
  const decoder = this.bcDecoder;
1528
1535
  decoder
1529
1536
  .decode(image, asImage)