@driftengine/physics 3.61.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/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +503 -0
- package/dist/bodies.d.ts +121 -0
- package/dist/bodies.js +298 -0
- package/dist/buoyancy.d.ts +46 -0
- package/dist/buoyancy.js +45 -0
- package/dist/cloth.d.ts +226 -0
- package/dist/cloth.js +587 -0
- package/dist/collide/aabb.d.ts +10 -0
- package/dist/collide/aabb.js +11 -0
- package/dist/collide/body.d.ts +202 -0
- package/dist/collide/body.js +142 -0
- package/dist/collide/eject.d.ts +39 -0
- package/dist/collide/eject.js +142 -0
- package/dist/collide/index.d.ts +18 -0
- package/dist/collide/index.js +5 -0
- package/dist/collide/scratch.d.ts +8 -0
- package/dist/collide/scratch.js +9 -0
- package/dist/collide/segment.d.ts +12 -0
- package/dist/collide/segment.js +109 -0
- package/dist/collide/sweep.d.ts +3 -0
- package/dist/collide/sweep.js +1036 -0
- package/dist/colliderSet.d.ts +239 -0
- package/dist/colliderSet.js +546 -0
- package/dist/compoundContact.d.ts +70 -0
- package/dist/compoundContact.js +221 -0
- package/dist/controller.d.ts +351 -0
- package/dist/controller.js +641 -0
- package/dist/cylinder.d.ts +78 -0
- package/dist/cylinder.js +294 -0
- package/dist/cylinderContact.d.ts +7 -0
- package/dist/cylinderContact.js +704 -0
- package/dist/decompose.d.ts +105 -0
- package/dist/decompose.js +979 -0
- package/dist/events.d.ts +43 -0
- package/dist/events.js +95 -0
- package/dist/executor.d.ts +147 -0
- package/dist/executor.js +173 -0
- package/dist/faces.d.ts +26 -0
- package/dist/faces.js +34 -0
- package/dist/fingerprint.d.ts +61 -0
- package/dist/fingerprint.js +139 -0
- package/dist/groundMotor.d.ts +34 -0
- package/dist/groundMotor.js +41 -0
- package/dist/heightfieldShape.d.ts +128 -0
- package/dist/heightfieldShape.js +328 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +47 -0
- package/dist/island.d.ts +55 -0
- package/dist/island.js +227 -0
- package/dist/islandSolve.d.ts +70 -0
- package/dist/islandSolve.js +171 -0
- package/dist/islandWorker.d.ts +18 -0
- package/dist/islandWorker.js +125 -0
- package/dist/joints.d.ts +180 -0
- package/dist/joints.js +1094 -0
- package/dist/manifold.d.ts +89 -0
- package/dist/manifold.js +608 -0
- package/dist/mass.d.ts +53 -0
- package/dist/mass.js +353 -0
- package/dist/meshContact.d.ts +57 -0
- package/dist/meshContact.js +393 -0
- package/dist/meshShape.d.ts +76 -0
- package/dist/meshShape.js +175 -0
- package/dist/pairs.d.ts +62 -0
- package/dist/pairs.js +154 -0
- package/dist/query.d.ts +71 -0
- package/dist/query.js +683 -0
- package/dist/ragdoll.d.ts +135 -0
- package/dist/ragdoll.js +489 -0
- package/dist/shape.d.ts +177 -0
- package/dist/shape.js +533 -0
- package/dist/solver.d.ts +136 -0
- package/dist/solver.js +601 -0
- package/dist/stage.d.ts +167 -0
- package/dist/stage.js +350 -0
- package/dist/stallEscape.d.ts +141 -0
- package/dist/stallEscape.js +254 -0
- package/dist/sweptStep.d.ts +173 -0
- package/dist/sweptStep.js +472 -0
- package/dist/tree.d.ts +61 -0
- package/dist/tree.js +348 -0
- package/dist/vehicle.d.ts +154 -0
- package/dist/vehicle.js +343 -0
- package/dist/workerPool.d.ts +107 -0
- package/dist/workerPool.js +239 -0
- package/dist/workers.d.ts +37 -0
- package/dist/workers.js +35 -0
- package/dist/world.d.ts +298 -0
- package/dist/world.js +712 -0
- package/package.json +55 -0
- package/src/bodies.ts +369 -0
- package/src/buoyancy.ts +78 -0
- package/src/cloth.ts +734 -0
- package/src/collide/aabb.ts +28 -0
- package/src/collide/body.ts +336 -0
- package/src/collide/eject.ts +156 -0
- package/src/collide/index.ts +26 -0
- package/src/collide/scratch.ts +10 -0
- package/src/collide/segment.ts +127 -0
- package/src/collide/sweep.ts +1132 -0
- package/src/colliderSet.ts +637 -0
- package/src/compoundContact.ts +284 -0
- package/src/controller.ts +817 -0
- package/src/cylinder.ts +340 -0
- package/src/cylinderContact.ts +825 -0
- package/src/decompose.ts +1198 -0
- package/src/events.ts +108 -0
- package/src/executor.ts +224 -0
- package/src/faces.ts +37 -0
- package/src/fingerprint.ts +146 -0
- package/src/groundMotor.ts +53 -0
- package/src/heightfieldShape.ts +395 -0
- package/src/index.ts +164 -0
- package/src/island.ts +219 -0
- package/src/islandSolve.ts +218 -0
- package/src/islandWorker.ts +141 -0
- package/src/joints.ts +1367 -0
- package/src/manifold.ts +798 -0
- package/src/mass.ts +424 -0
- package/src/meshContact.ts +443 -0
- package/src/meshShape.ts +261 -0
- package/src/pairs.ts +169 -0
- package/src/query.ts +844 -0
- package/src/ragdoll.ts +662 -0
- package/src/shape.ts +656 -0
- package/src/solver.ts +873 -0
- package/src/stage.ts +406 -0
- package/src/stallEscape.ts +362 -0
- package/src/sweptStep.ts +609 -0
- package/src/tree.ts +366 -0
- package/src/vehicle.ts +489 -0
- package/src/workerPool.ts +283 -0
- package/src/workers.ts +38 -0
- package/src/world.ts +961 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Islands solved on other threads, and every way that can decline to happen.
|
|
3
|
+
*
|
|
4
|
+
* **The handshake was never the problem.** `executor.ts` measured a fork-join over a
|
|
5
|
+
* `SharedArrayBuffer` at 11.8 microseconds for four workers against 2.6 milliseconds of solve. What
|
|
6
|
+
* stops a pool is that `SharedArrayBuffer` does not exist on a page that is not cross-origin
|
|
7
|
+
* isolated, and that is a header on the consumer's site rather than anything the engine can arrange.
|
|
8
|
+
* So this module's real subject is the four ways it can fail to start and the one way it must never
|
|
9
|
+
* fail: quietly.
|
|
10
|
+
*
|
|
11
|
+
* **Islands are claimed with `Atomics.add` on a cursor, which balances load and costs nothing.**
|
|
12
|
+
* Handing work out in arrival order is normally a determinism hazard. It is not one here, because
|
|
13
|
+
* two islands never write the same body — the property `DisjointExecutor` asserts — so the result
|
|
14
|
+
* does not depend on who solved what or when. A pool of eight and a pool of one produce the same
|
|
15
|
+
* bits, and so does the serial runner.
|
|
16
|
+
*
|
|
17
|
+
* **The caller solves its own share.** That is what keeps the join short on a browser's main thread,
|
|
18
|
+
* where `Atomics.wait` throws and the wait has to be a spin: by the time the caller runs out of
|
|
19
|
+
* islands to claim, the only thing left to wait for is the imbalance.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { solveIslandInto } from './islandSolve.ts';
|
|
23
|
+
import { SolveStage, StagedExecutor } from './stage.ts';
|
|
24
|
+
import type { Executor } from './executor.ts';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The part of a worker this pool uses.
|
|
28
|
+
*
|
|
29
|
+
* A browser `Worker` satisfies it and so does `node:worker_threads`, which is what lets the gate run
|
|
30
|
+
* the real entry point in Node instead of a stand-in that would prove nothing about threads.
|
|
31
|
+
*/
|
|
32
|
+
export interface PoolWorker {
|
|
33
|
+
postMessage(value: unknown): void;
|
|
34
|
+
terminate(): void | Promise<number>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Slots in the control block, which is its own buffer and outlives every re-plan of the stage. */
|
|
38
|
+
export const CTL_EPOCH = 0;
|
|
39
|
+
export const CTL_CURSOR = 1;
|
|
40
|
+
export const CTL_ISLANDS = 2;
|
|
41
|
+
export const CTL_DONE = 3;
|
|
42
|
+
export const CTL_ACK = 4;
|
|
43
|
+
export const CTL_RELOAD = 5;
|
|
44
|
+
export const CTL_STOP = 6;
|
|
45
|
+
/** One slot per worker from here, counting the islands it has solved. */
|
|
46
|
+
export const CTL_SOLVED = 7;
|
|
47
|
+
|
|
48
|
+
/** How long a join waits before it gives up on a worker and solves the tick itself. */
|
|
49
|
+
const JOIN_DEADLINE_MS = 250;
|
|
50
|
+
|
|
51
|
+
/** What a consumer asked for. */
|
|
52
|
+
export interface PoolRequest {
|
|
53
|
+
workers: number;
|
|
54
|
+
spawn?: () => PoolWorker;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What they got, and why, in a sentence fit to put on a debug overlay. */
|
|
58
|
+
export interface PoolOutcome {
|
|
59
|
+
executor: Executor | null;
|
|
60
|
+
running: number;
|
|
61
|
+
reason: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sharedAlloc(bytes: number): ArrayBufferLike {
|
|
65
|
+
return new SharedArrayBuffer(bytes);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether this thread may block.
|
|
70
|
+
*
|
|
71
|
+
* `Atomics.wait` is refused on a browser's main thread and permitted everywhere else, including a
|
|
72
|
+
* worker and Node. Asked by trying it, because the alternative is sniffing for `window` and being
|
|
73
|
+
* wrong in every runtime nobody thought of. The probe waits zero milliseconds on a value that does
|
|
74
|
+
* not match, so it returns `not-equal` immediately and blocks nothing.
|
|
75
|
+
*/
|
|
76
|
+
function canBlock(): boolean {
|
|
77
|
+
try {
|
|
78
|
+
const probe = new Int32Array(new SharedArrayBuffer(4));
|
|
79
|
+
Atomics.wait(probe, 0, 1, 0);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Start a pool, or say why there is none.
|
|
88
|
+
*
|
|
89
|
+
* Never throws. Every path out sets a reason, because a consumer who asked for four workers and got
|
|
90
|
+
* none is owed the difference between "your browser will not let me" and "your bundler could not
|
|
91
|
+
* find the worker". A silent fallback here is a frame rate nobody can attribute, which is the exact
|
|
92
|
+
* complaint `capabilityClamp` exists to answer elsewhere in this engine.
|
|
93
|
+
*/
|
|
94
|
+
export function createIslandPool(request: PoolRequest): PoolOutcome {
|
|
95
|
+
const wanted = Math.max(0, Math.floor(request.workers));
|
|
96
|
+
if (wanted === 0) return { executor: null, running: 0, reason: '' };
|
|
97
|
+
|
|
98
|
+
if (typeof SharedArrayBuffer === 'undefined') {
|
|
99
|
+
return {
|
|
100
|
+
executor: null,
|
|
101
|
+
running: 0,
|
|
102
|
+
reason:
|
|
103
|
+
'this page is not cross-origin isolated, so SharedArrayBuffer is undefined. Serve it with ' +
|
|
104
|
+
'Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp ' +
|
|
105
|
+
'(or credentialless) to enable the pool.',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const spawn = request.spawn ?? defaultSpawn;
|
|
110
|
+
if (spawn === null) {
|
|
111
|
+
return {
|
|
112
|
+
executor: null,
|
|
113
|
+
running: 0,
|
|
114
|
+
reason: 'this runtime has no Worker, and no spawn was given.',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const workers: PoolWorker[] = [];
|
|
119
|
+
try {
|
|
120
|
+
for (let i = 0; i < wanted; i++) workers.push(spawn());
|
|
121
|
+
} catch (error) {
|
|
122
|
+
for (const worker of workers) worker.terminate();
|
|
123
|
+
const message = String((error as Error)?.message ?? error);
|
|
124
|
+
return {
|
|
125
|
+
executor: null,
|
|
126
|
+
running: 0,
|
|
127
|
+
reason:
|
|
128
|
+
`the worker entry failed to load: ${message}. Pass a spawn option building the worker ` +
|
|
129
|
+
'with your own bundler if it does not understand a worker entry point.',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { executor: new WorkerPoolExecutor(workers), running: workers.length, reason: '' };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The worker this package ships, built the way a bundler understands.
|
|
138
|
+
*
|
|
139
|
+
* Vite, webpack 5 and Parcel all resolve `new URL('./x.ts', import.meta.url)` inside a `new Worker`.
|
|
140
|
+
* A build that does not throws, which becomes a reason and serial physics — and `spawn` is the way
|
|
141
|
+
* out, which is why this is a default and not the only path. Null where there is no `Worker` at all.
|
|
142
|
+
*/
|
|
143
|
+
const defaultSpawn: (() => PoolWorker) | null =
|
|
144
|
+
typeof Worker === 'undefined'
|
|
145
|
+
? null
|
|
146
|
+
: (): PoolWorker =>
|
|
147
|
+
new Worker(new URL('./islandWorker.ts', import.meta.url), { type: 'module' }) as PoolWorker;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The pool itself: `StagedExecutor` with the serial solve replaced by a fork and a join.
|
|
151
|
+
*
|
|
152
|
+
* Everything about mirroring the state is inherited, which is what lets `stage.test.ts` hold the
|
|
153
|
+
* mirror to bit-identical physics with no threads in the picture and leaves this class responsible
|
|
154
|
+
* for one question only.
|
|
155
|
+
*/
|
|
156
|
+
export class WorkerPoolExecutor extends StagedExecutor implements Executor {
|
|
157
|
+
override readonly name = 'workers';
|
|
158
|
+
|
|
159
|
+
/** Set when the pool has given up. Read by `PhysicsWorld` so `parallelism` stops claiming workers. */
|
|
160
|
+
failure: string | null = null;
|
|
161
|
+
|
|
162
|
+
private readonly control: Int32Array<ArrayBufferLike>;
|
|
163
|
+
private readonly blocking = canBlock();
|
|
164
|
+
private posted = -1;
|
|
165
|
+
private started = false;
|
|
166
|
+
|
|
167
|
+
private readonly workers: PoolWorker[];
|
|
168
|
+
|
|
169
|
+
constructor(workers: PoolWorker[]) {
|
|
170
|
+
super(sharedAlloc);
|
|
171
|
+
this.workers = workers;
|
|
172
|
+
this.control = new Int32Array(new SharedArrayBuffer((CTL_SOLVED + workers.length) * 4));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** How many islands each worker has solved since the pool started. The engagement evidence. */
|
|
176
|
+
get solvedPerWorker(): number[] {
|
|
177
|
+
return this.workers.map((_, i) => Atomics.load(this.control, CTL_SOLVED + i));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Stop every worker. Idempotent, because a consumer may dispose a world twice. */
|
|
181
|
+
dispose(): void {
|
|
182
|
+
if (this.workers.length === 0) return;
|
|
183
|
+
Atomics.store(this.control, CTL_STOP, 1);
|
|
184
|
+
Atomics.add(this.control, CTL_EPOCH, 1);
|
|
185
|
+
Atomics.notify(this.control, CTL_EPOCH);
|
|
186
|
+
for (const worker of this.workers) worker.terminate();
|
|
187
|
+
this.workers.length = 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Fork, take a share, join. Returns false when the pool failed, which sends the tick back to the
|
|
192
|
+
* live state and a serial solve.
|
|
193
|
+
*/
|
|
194
|
+
protected override solve(count: number, stage: SolveStage): boolean {
|
|
195
|
+
if (this.failure !== null || this.workers.length === 0) return false;
|
|
196
|
+
const ctl = this.control;
|
|
197
|
+
|
|
198
|
+
if (!this.started || stage.generation !== this.posted) {
|
|
199
|
+
if (!this.reload(stage)) return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Atomics.store(ctl, CTL_CURSOR, 0);
|
|
203
|
+
Atomics.store(ctl, CTL_ISLANDS, count);
|
|
204
|
+
Atomics.store(ctl, CTL_DONE, 0);
|
|
205
|
+
Atomics.add(ctl, CTL_EPOCH, 1);
|
|
206
|
+
Atomics.notify(ctl, CTL_EPOCH);
|
|
207
|
+
|
|
208
|
+
/* The caller's own share, from the same cursor, so what is left to wait for is the imbalance
|
|
209
|
+
and not the solve. */
|
|
210
|
+
for (;;) {
|
|
211
|
+
const island = Atomics.add(ctl, CTL_CURSOR, 1);
|
|
212
|
+
if (island >= count) break;
|
|
213
|
+
solveIslandInto(stage.state, island);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return this.join();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Wait for every worker to finish this epoch, and give up rather than freeze.
|
|
221
|
+
*
|
|
222
|
+
* **A worker that dies mid-island is the failure this deadline exists for.** Without it the join
|
|
223
|
+
* spins until the tab is closed, which is a frozen game and the worst outcome available. With it
|
|
224
|
+
* the tick is re-solved from the live state, which is untouched until `pull`, and the pool is
|
|
225
|
+
* marked dead so every later tick is serial with a reason attached.
|
|
226
|
+
*/
|
|
227
|
+
private join(): boolean {
|
|
228
|
+
const ctl = this.control;
|
|
229
|
+
const wanted = this.workers.length;
|
|
230
|
+
const deadline = Date.now() + JOIN_DEADLINE_MS;
|
|
231
|
+
for (;;) {
|
|
232
|
+
const done = Atomics.load(ctl, CTL_DONE);
|
|
233
|
+
if (done >= wanted) return true;
|
|
234
|
+
if (Date.now() > deadline) {
|
|
235
|
+
this.failure =
|
|
236
|
+
'a worker stopped answering, so island solving went back to this thread. The world is ' +
|
|
237
|
+
'correct and serial from here.';
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if (this.blocking) Atomics.wait(ctl, CTL_DONE, done, 50);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Hand the workers a new staging buffer and wait for them to adopt it.
|
|
246
|
+
*
|
|
247
|
+
* A worker parked in `Atomics.wait` cannot process a message, so it has to be let out first:
|
|
248
|
+
* `CTL_RELOAD` is set, the epoch is bumped, and a woken worker returns to its event loop instead
|
|
249
|
+
* of solving. It adopts the buffer in its message handler, acknowledges through `CTL_ACK`, and
|
|
250
|
+
* parks again. Rare by construction — a re-plan happens when a set doubles, which is about
|
|
251
|
+
* `log2(n)` times in a world's life and never inside a tick.
|
|
252
|
+
*/
|
|
253
|
+
private reload(stage: SolveStage): boolean {
|
|
254
|
+
const ctl = this.control;
|
|
255
|
+
Atomics.store(ctl, CTL_ACK, 0);
|
|
256
|
+
Atomics.store(ctl, CTL_RELOAD, 1);
|
|
257
|
+
Atomics.add(ctl, CTL_EPOCH, 1);
|
|
258
|
+
Atomics.notify(ctl, CTL_EPOCH);
|
|
259
|
+
|
|
260
|
+
for (let i = 0; i < this.workers.length; i++) {
|
|
261
|
+
(this.workers[i] as PoolWorker).postMessage({
|
|
262
|
+
kind: 'stage',
|
|
263
|
+
index: i,
|
|
264
|
+
control: ctl.buffer,
|
|
265
|
+
buffer: stage.buffer,
|
|
266
|
+
plan: stage.plan,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const deadline = Date.now() + JOIN_DEADLINE_MS;
|
|
271
|
+
while (Atomics.load(ctl, CTL_ACK) < this.workers.length) {
|
|
272
|
+
if (Date.now() > deadline) {
|
|
273
|
+
this.failure =
|
|
274
|
+
'the workers did not take the shared buffer, so island solving stayed on this thread.';
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
Atomics.store(ctl, CTL_RELOAD, 0);
|
|
279
|
+
this.posted = stage.generation;
|
|
280
|
+
this.started = true;
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
}
|
package/src/workers.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The island worker pool, behind its own specifier.
|
|
3
|
+
*
|
|
4
|
+
* **Because a bundler emits the worker before it decides the pool is unreachable.** `workerPool.ts`
|
|
5
|
+
* builds its worker with `new Worker(new URL('./islandWorker.ts', import.meta.url))`, and Vite's
|
|
6
|
+
* `vite:worker-import-meta-url` rewrites that at *transform* time, which happens before
|
|
7
|
+
* tree-shaking. So a barrel that merely re-exported `createIslandPool` put the worker in the module
|
|
8
|
+
* graph, and the chunk was written to disk whether or not anything could ever fetch it.
|
|
9
|
+
*
|
|
10
|
+
* Reported by a consumer on 2026-09-05 and reproduced on a second the same day: **37,913 bytes raw
|
|
11
|
+
* and 12,259 gzipped**, in a build where `WorkerPoolExecutor`, `StagedExecutor`, `createIslandPool`
|
|
12
|
+
* and `SolveStage` were all absent from every other chunk and nothing under `dist/` named the
|
|
13
|
+
* worker. Tree-shaking had done its half and the asset was already on disk.
|
|
14
|
+
*
|
|
15
|
+
* **This is the same decision 3.54.0 made and half of it was missed.** That release moved the pool
|
|
16
|
+
* factory to the call site rather than have `PhysicsWorld` reach it, to avoid *"2,629 bytes gzipped
|
|
17
|
+
* into every consumer's bundle, including everyone who never asks for a worker"*. The measured
|
|
18
|
+
* weight of the half that was missed is 4.7 times the one that was caught, landing on exactly the
|
|
19
|
+
* consumer that reasoning was written to protect.
|
|
20
|
+
*
|
|
21
|
+
* A caller already had to name the factory for that reason, so the cost of the fix is the specifier
|
|
22
|
+
* and nothing else:
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { PhysicsWorld } from '@driftengine/physics';
|
|
26
|
+
* import { createIslandPool } from '@driftengine/physics/src/workers.ts';
|
|
27
|
+
*
|
|
28
|
+
* const world = new PhysicsWorld({ workers: 4, pool: createIslandPool });
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* `scripts/worker-entry.test.mjs` bundles the barrel and asserts it names no worker, because every
|
|
32
|
+
* gate in this repository passed while the chunk was shipping.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export type { PoolOutcome, PoolRequest, PoolWorker } from './workerPool.ts';
|
|
36
|
+
export { WorkerPoolExecutor, createIslandPool } from './workerPool.ts';
|
|
37
|
+
export type { LanePlan, StagePlan } from './stage.ts';
|
|
38
|
+
export { SolveStage, StagedExecutor } from './stage.ts';
|