@ecsia/scheduler 0.1.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 +21 -0
- package/README.md +32 -0
- package/dist/commands/apply.d.ts +49 -0
- package/dist/commands/apply.d.ts.map +1 -0
- package/dist/commands/apply.js +211 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/buffer.d.ts +53 -0
- package/dist/commands/buffer.d.ts.map +1 -0
- package/dist/commands/buffer.js +66 -0
- package/dist/commands/buffer.js.map +1 -0
- package/dist/commands/encode.d.ts +30 -0
- package/dist/commands/encode.d.ts.map +1 -0
- package/dist/commands/encode.js +108 -0
- package/dist/commands/encode.js.map +1 -0
- package/dist/commands/fields.d.ts +22 -0
- package/dist/commands/fields.d.ts.map +1 -0
- package/dist/commands/fields.js +123 -0
- package/dist/commands/fields.js.map +1 -0
- package/dist/commands/index.d.ts +10 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +6 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/op.d.ts +16 -0
- package/dist/commands/op.d.ts.map +1 -0
- package/dist/commands/op.js +43 -0
- package/dist/commands/op.js.map +1 -0
- package/dist/executor/guards.d.ts +8 -0
- package/dist/executor/guards.d.ts.map +1 -0
- package/dist/executor/guards.js +61 -0
- package/dist/executor/guards.js.map +1 -0
- package/dist/executor/index.d.ts +9 -0
- package/dist/executor/index.d.ts.map +1 -0
- package/dist/executor/index.js +6 -0
- package/dist/executor/index.js.map +1 -0
- package/dist/executor/run-wave.d.ts +19 -0
- package/dist/executor/run-wave.d.ts.map +1 -0
- package/dist/executor/run-wave.js +38 -0
- package/dist/executor/run-wave.js.map +1 -0
- package/dist/executor/scheduler.d.ts +41 -0
- package/dist/executor/scheduler.d.ts.map +1 -0
- package/dist/executor/scheduler.js +71 -0
- package/dist/executor/scheduler.js.map +1 -0
- package/dist/executor/seams.d.ts +38 -0
- package/dist/executor/seams.d.ts.map +1 -0
- package/dist/executor/seams.js +16 -0
- package/dist/executor/seams.js.map +1 -0
- package/dist/executor/update-threaded.d.ts +17 -0
- package/dist/executor/update-threaded.d.ts.map +1 -0
- package/dist/executor/update-threaded.js +67 -0
- package/dist/executor/update-threaded.js.map +1 -0
- package/dist/executor/update.d.ts +4 -0
- package/dist/executor/update.d.ts.map +1 -0
- package/dist/executor/update.js +23 -0
- package/dist/executor/update.js.map +1 -0
- package/dist/graph/dag.d.ts +16 -0
- package/dist/graph/dag.d.ts.map +1 -0
- package/dist/graph/dag.js +118 -0
- package/dist/graph/dag.js.map +1 -0
- package/dist/graph/edges.d.ts +20 -0
- package/dist/graph/edges.d.ts.map +1 -0
- package/dist/graph/edges.js +181 -0
- package/dist/graph/edges.js.map +1 -0
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +5 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/waves.d.ts +30 -0
- package/dist/graph/waves.d.ts.map +1 -0
- package/dist/graph/waves.js +133 -0
- package/dist/graph/waves.js.map +1 -0
- package/dist/graph/weights.d.ts +7 -0
- package/dist/graph/weights.d.ts.map +1 -0
- package/dist/graph/weights.js +10 -0
- package/dist/graph/weights.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +13 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +12 -0
- package/dist/internal.js.map +1 -0
- package/dist/planner/access.d.ts +18 -0
- package/dist/planner/access.d.ts.map +1 -0
- package/dist/planner/access.js +92 -0
- package/dist/planner/access.js.map +1 -0
- package/dist/planner/define-system.d.ts +14 -0
- package/dist/planner/define-system.d.ts.map +1 -0
- package/dist/planner/define-system.js +30 -0
- package/dist/planner/define-system.js.map +1 -0
- package/dist/planner/index.d.ts +6 -0
- package/dist/planner/index.d.ts.map +1 -0
- package/dist/planner/index.js +4 -0
- package/dist/planner/index.js.map +1 -0
- package/dist/planner/types.d.ts +74 -0
- package/dist/planner/types.d.ts.map +1 -0
- package/dist/planner/types.js +6 -0
- package/dist/planner/types.js.map +1 -0
- package/dist/workers/atomics-shim.d.ts +8 -0
- package/dist/workers/atomics-shim.d.ts.map +1 -0
- package/dist/workers/atomics-shim.js +14 -0
- package/dist/workers/atomics-shim.js.map +1 -0
- package/dist/workers/index.d.ts +13 -0
- package/dist/workers/index.d.ts.map +1 -0
- package/dist/workers/index.js +10 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/manifest.d.ts +67 -0
- package/dist/workers/manifest.d.ts.map +1 -0
- package/dist/workers/manifest.js +5 -0
- package/dist/workers/manifest.js.map +1 -0
- package/dist/workers/pool.d.ts +60 -0
- package/dist/workers/pool.d.ts.map +1 -0
- package/dist/workers/pool.js +313 -0
- package/dist/workers/pool.js.map +1 -0
- package/dist/workers/reservation.d.ts +18 -0
- package/dist/workers/reservation.d.ts.map +1 -0
- package/dist/workers/reservation.js +41 -0
- package/dist/workers/reservation.js.map +1 -0
- package/dist/workers/wave-sync.d.ts +18 -0
- package/dist/workers/wave-sync.d.ts.map +1 -0
- package/dist/workers/wave-sync.js +88 -0
- package/dist/workers/wave-sync.js.map +1 -0
- package/dist/workers/worker-entry.d.ts +2 -0
- package/dist/workers/worker-entry.d.ts.map +1 -0
- package/dist/workers/worker-entry.js +187 -0
- package/dist/workers/worker-entry.js.map +1 -0
- package/dist/workers/worker-system.d.ts +31 -0
- package/dist/workers/worker-system.d.ts.map +1 -0
- package/dist/workers/worker-system.js +20 -0
- package/dist/workers/worker-system.js.map +1 -0
- package/dist/workers/world-view.d.ts +39 -0
- package/dist/workers/world-view.d.ts.map +1 -0
- package/dist/workers/world-view.js +85 -0
- package/dist/workers/world-view.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { SharedHandleManifest } from '@ecsia/core';
|
|
2
|
+
/** Dense component-id assignment so the worker aligns ids identically. */
|
|
3
|
+
export interface ComponentManifestEntry {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly id: number;
|
|
6
|
+
/** Per-field word counts (declaration order) so the worker rebuilds the payload codec. */
|
|
7
|
+
readonly fieldWords: readonly number[];
|
|
8
|
+
}
|
|
9
|
+
export interface WorkerBootstrap {
|
|
10
|
+
readonly workerIndex: number;
|
|
11
|
+
/** Module URL the worker imports to obtain its system kernels by name (the dispatch mechanism). */
|
|
12
|
+
readonly kernelModule: string;
|
|
13
|
+
/** System names indexed by SystemId (the POOL's registration order) — the kernel lookup key. */
|
|
14
|
+
readonly systemNames: readonly string[];
|
|
15
|
+
/** The shared buffer set (SAB columns + regions), by reference. */
|
|
16
|
+
readonly buffers: SharedHandleManifest;
|
|
17
|
+
readonly indexBitsMask: number;
|
|
18
|
+
/** Dense component-id registry the worker aligns to. */
|
|
19
|
+
readonly components: readonly ComponentManifestEntry[];
|
|
20
|
+
/** Per-worker control SABs. */
|
|
21
|
+
readonly commandSab: SharedArrayBuffer;
|
|
22
|
+
readonly reservationSab: SharedArrayBuffer;
|
|
23
|
+
readonly reservationCapacity: number;
|
|
24
|
+
readonly waveSab: SharedArrayBuffer;
|
|
25
|
+
/** Work descriptor SAB: [0]=systemId [1]=count [2]=dtBits(f32) [3..]=entity indices. */
|
|
26
|
+
readonly workSab: SharedArrayBuffer;
|
|
27
|
+
/** Wake SAB: the worker Atomics.waits on [0]; the main thread bumps it + notifies to dispatch. */
|
|
28
|
+
readonly wakeSab: SharedArrayBuffer;
|
|
29
|
+
/**
|
|
30
|
+
* Re-backing signal SAB. Word [0] = the column
|
|
31
|
+
* re-backing generation the main thread last published. When the worker is woken and finds [0] !=
|
|
32
|
+
* its last-applied generation, the wake is a NOTICE round: it `await`s the queued `columns-added`
|
|
33
|
+
* postMessage (the only transport that can carry a new SharedArrayBuffer reference), re-wraps the
|
|
34
|
+
* named columns, then completes the wave fence as its ACK — all BEFORE the next dispatch. Steady
|
|
35
|
+
* state ([0] unchanged) costs one extra Atomics.load per wave.
|
|
36
|
+
*/
|
|
37
|
+
readonly noticeSab: SharedArrayBuffer;
|
|
38
|
+
/**
|
|
39
|
+
* Per-worker write-corral SAB: the worker stages value writes here as
|
|
40
|
+
* `[count, index0, componentId0, index1, componentId1, …]`. Word [0] is the entry count; the main
|
|
41
|
+
* thread reads it after the fence and merges into the shared write log in worker-index order. No
|
|
42
|
+
* atomics on the worker push path — it is single-writer, drained only after the wave fence.
|
|
43
|
+
*/
|
|
44
|
+
readonly writeCorralSab: SharedArrayBuffer;
|
|
45
|
+
}
|
|
46
|
+
/** A message the main thread posts to ask a worker to run one batch (used in the postMessage tier). */
|
|
47
|
+
export interface DispatchMessage {
|
|
48
|
+
readonly kind: 'dispatch';
|
|
49
|
+
readonly systemId: number;
|
|
50
|
+
readonly dt: number;
|
|
51
|
+
readonly indices: Int32Array;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The re-backing broadcast. Posted to each worker at the wave fence carrying
|
|
55
|
+
* the NEW SharedArrayBuffer backings (by reference — SABs cannot ride a SAB) so the worker re-wraps
|
|
56
|
+
* its stale column views. `generation` matches `noticeSab[0]`; the worker ACKs via the wave fence.
|
|
57
|
+
*/
|
|
58
|
+
export interface ColumnsAddedMessage {
|
|
59
|
+
readonly kind: 'columns-added';
|
|
60
|
+
readonly generation: number;
|
|
61
|
+
readonly columns: ReadonlyArray<{
|
|
62
|
+
key: string;
|
|
63
|
+
backing: SharedArrayBuffer;
|
|
64
|
+
layout: import('@ecsia/core').ColumnLayout;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/workers/manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,0FAA0F;IAC1F,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,mGAAmG;IACnG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAA;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,SAAS,sBAAsB,EAAE,CAAA;IACtD,+BAA+B;IAC/B,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAA;IACtC,QAAQ,CAAC,cAAc,EAAE,iBAAiB,CAAA;IAC1C,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,wFAAwF;IACxF,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAA;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,iBAAiB,CAAA;CAC3C;AAED,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAC7B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAA;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,OAAO,aAAa,EAAE,YAAY,CAAA;KAAE,CAAC,CAAA;CACzH"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The workerData payload posted ONCE per worker at pool startup (). Carries the shared buffer set (by reference — SABs are sharable, NOT transferred)
|
|
2
|
+
// plus the per-worker control SABs the dispatch loop uses. No component VALUES are ever serialized
|
|
3
|
+
// (zero-copy): the worker reads live SAB columns.
|
|
4
|
+
export {};
|
|
5
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/workers/manifest.ts"],"names":[],"mappings":"AAAA,sJAAsJ;AACtJ,mGAAmG;AACnG,kDAAkD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { World } from '@ecsia/core';
|
|
2
|
+
import type { ComponentDef, Schema, SystemId } from '@ecsia/schema';
|
|
3
|
+
import type { ComponentFieldCodec } from '../commands/index.js';
|
|
4
|
+
import type { WorkerSystemKernel } from './worker-system.js';
|
|
5
|
+
/** A worker-eligible system registered with the pool, indexed by SystemId (registration order). */
|
|
6
|
+
export interface PoolSystem {
|
|
7
|
+
readonly id: SystemId;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly matchComponents: readonly ComponentDef<Schema>[];
|
|
10
|
+
readonly kernel: WorkerSystemKernel;
|
|
11
|
+
readonly maxSpawnsPerWave: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PoolConfig {
|
|
14
|
+
readonly world: World;
|
|
15
|
+
readonly workers: number;
|
|
16
|
+
/** Module URL the workers import for their kernels (the dispatch mechanism). */
|
|
17
|
+
readonly kernelModule: string;
|
|
18
|
+
readonly systems: readonly PoolSystem[];
|
|
19
|
+
/**
|
|
20
|
+
* Every component a worker may touch — read, written, OR named as an add/set-payload target. Their
|
|
21
|
+
* dense ids are aligned on the worker side. Defaults to the union of all
|
|
22
|
+
* systems' matchComponents; pass the full registered set if a kernel adds a component it does not
|
|
23
|
+
* also match (e.g. a spawner adding a component to a fresh entity).
|
|
24
|
+
*/
|
|
25
|
+
readonly components?: readonly ComponentDef<Schema>[];
|
|
26
|
+
/** Max matched entities per batch dispatch (work SAB sizing). Default: maxEntities. */
|
|
27
|
+
readonly maxBatchEntities?: number;
|
|
28
|
+
readonly commandWords?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Max `(index, componentId)` value-write entries one worker may stage per wave (write-corral SAB
|
|
31
|
+
* sizing). Default: maxBatchEntities × 4 (a kernel may write a few components
|
|
32
|
+
* per matched entity). Excess writes are capped (diagnosed at merge), never dropped silently.
|
|
33
|
+
*/
|
|
34
|
+
readonly writeCorralEntries?: number;
|
|
35
|
+
readonly diagnostic?: (message: string) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Override the worker-entry module URL. Defaults to the dist sibling of this module. Tests running
|
|
38
|
+
* over TS source (vitest aliases) MUST pass the BUILT `dist/workers/worker-entry.js` URL, because a
|
|
39
|
+
* raw Node worker_threads Worker has no TS transform and no path aliasing.
|
|
40
|
+
*/
|
|
41
|
+
readonly workerEntryUrl?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare class WorkerPool {
|
|
44
|
+
#private;
|
|
45
|
+
constructor(cfg: PoolConfig);
|
|
46
|
+
/** Wait until every spawned worker has posted 'ready' (bootstrap complete). */
|
|
47
|
+
ready(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Run one round: dispatch each (systemId, workerIndex) batch to its worker, wait on the fence, then
|
|
50
|
+
* merge the per-worker command buffers in fixed worker-index order. `world.phase` is 'wave' across
|
|
51
|
+
* the dispatch (PHASE-2) and flipped back to 'serial' for the merge/apply flush slot.
|
|
52
|
+
*/
|
|
53
|
+
runRound(batches: readonly {
|
|
54
|
+
systemId: SystemId;
|
|
55
|
+
workerIndex: number;
|
|
56
|
+
}[], dt: number): Promise<void>;
|
|
57
|
+
dispose(): Promise<void>;
|
|
58
|
+
get codecById(): ReadonlyMap<number, ComponentFieldCodec>;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/workers/pool.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,KAAK,EAAe,MAAM,aAAa,CAAA;AAErD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGnE,OAAO,KAAK,EAA6B,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAO1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE5D,mGAAmG;AACnG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,eAAe,EAAE,SAAS,YAAY,CAAC,MAAM,CAAC,EAAE,CAAA;IACzD,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAA;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAClC;AAiBD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,gFAAgF;IAChF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAA;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,YAAY,CAAC,MAAM,CAAC,EAAE,CAAA;IACrD,uFAAuF;IACvF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAKD,qBAAa,UAAU;;gBAeT,GAAG,EAAE,UAAU;IAuF3B,+EAA+E;IACzE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;;;OAIG;IACG,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,EAAE,QAAQ,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2KpG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B,IAAI,SAAS,IAAI,WAAW,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAExD;CACF"}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// The worker pool + wave dispatch loop. At startup it allocates the per-worker
|
|
2
|
+
// control SABs, exports the world's shared buffer set ONCE (by reference, never per frame), and
|
|
3
|
+
// spawns a fixed worker pool. Per round it: tops up each worker's
|
|
4
|
+
// reservation (reserveEntityBlock — the Atomics.sub take path is now exercised on the worker side),
|
|
5
|
+
// flips world.phase to 'wave', writes each batch's matched entity indices + dt into the worker's work
|
|
6
|
+
// SAB, bumps the wake word, and waits on the Atomics wave fence; then flips back to 'serial' and merges
|
|
7
|
+
// the per-worker command buffers DETERMINISTICALLY (ascending worker index — flushAll). That fixed
|
|
8
|
+
// merge order is what makes the multi-worker run SERIAL-EQUIVALENT despite nondeterministic completion.
|
|
9
|
+
//
|
|
10
|
+
// Tier: Node main thread blocks on the wave fence directly (selectWaitTier → 'coordinator-block');
|
|
11
|
+
// workers block on their wake word OFF the main thread, so there is no deadlock. NO-SAB contexts take
|
|
12
|
+
// the postMessage fallback (transfer columns per wave) — emitted with a diagnostic, never silent.
|
|
13
|
+
import { Worker } from 'node:worker_threads';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { handleIndex } from '@ecsia/core';
|
|
16
|
+
import { has } from '@ecsia/schema';
|
|
17
|
+
import { makeCommandBuffer, resetBuffer, flushAll, buildFieldCodec } from '../commands/index.js';
|
|
18
|
+
import { makeWaveCounter, makeWaveSync, workerHead, waveErrored } from './wave-sync.js';
|
|
19
|
+
import { selectWaitTier } from '../executor/seams.js';
|
|
20
|
+
import { makeReservationSab, fillReservation, consumedCount } from './reservation.js';
|
|
21
|
+
const WAKE = 0;
|
|
22
|
+
const WORK_HEADER_WORDS = 3; // [systemId, count, dtBits]
|
|
23
|
+
export class WorkerPool {
|
|
24
|
+
#world;
|
|
25
|
+
#systems;
|
|
26
|
+
#slots = [];
|
|
27
|
+
#waveCounter;
|
|
28
|
+
#waveSync;
|
|
29
|
+
#diag;
|
|
30
|
+
#defById = new Map();
|
|
31
|
+
#codecById = new Map();
|
|
32
|
+
#wakeGen = 0;
|
|
33
|
+
// The last column-growth generation we broadcast to the workers. A cheap `!==` against the
|
|
34
|
+
// world's live generation gates the whole re-backing fence — zero work when nothing re-backed.
|
|
35
|
+
#appliedGrowthGen = 0;
|
|
36
|
+
#disposed = false;
|
|
37
|
+
constructor(cfg) {
|
|
38
|
+
this.#world = cfg.world;
|
|
39
|
+
this.#systems = cfg.systems;
|
|
40
|
+
this.#diag = cfg.diagnostic ?? ((m) => console.warn(`[ecsia] ${m}`));
|
|
41
|
+
const caps = cfg.world.options; // (capabilities live on the world; tier from the probe below)
|
|
42
|
+
void caps;
|
|
43
|
+
const manifest = cfg.world.__exportShared();
|
|
44
|
+
if (manifest.regions.length === 0 && manifest.columns.length === 0) {
|
|
45
|
+
this.#diag('threaded:true requested but no SAB-backed buffers are available (cross-origin isolation absent or SAB unavailable); ' +
|
|
46
|
+
'the pool cannot share columns by reference. Run single-threaded instead.');
|
|
47
|
+
}
|
|
48
|
+
const components = [];
|
|
49
|
+
const allDefs = cfg.components ?? [...new Set(cfg.systems.flatMap((s) => [...s.matchComponents]))];
|
|
50
|
+
for (const def of allDefs) {
|
|
51
|
+
const id = def.id;
|
|
52
|
+
if (!this.#defById.has(id)) {
|
|
53
|
+
const codec = buildFieldCodec(def);
|
|
54
|
+
this.#defById.set(id, def);
|
|
55
|
+
this.#codecById.set(id, codec);
|
|
56
|
+
components.push({ name: def.name, id, fieldWords: [codec.totalWords] });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this.#waveCounter = makeWaveCounter(cfg.workers);
|
|
60
|
+
const tier = selectWaitTier({
|
|
61
|
+
waitAsync: false, // Node main thread blocks directly (tier 2); browser-main would set this true
|
|
62
|
+
waitBlocking: typeof Atomics.wait === 'function',
|
|
63
|
+
sabAvailable: typeof SharedArrayBuffer === 'function',
|
|
64
|
+
});
|
|
65
|
+
this.#waveSync = makeWaveSync(tier);
|
|
66
|
+
const maxBatch = cfg.maxBatchEntities ?? cfg.world.options.maxEntities;
|
|
67
|
+
const corralEntries = cfg.writeCorralEntries ?? maxBatch * 4;
|
|
68
|
+
const reservationCap = Math.max(...cfg.systems.map((s) => s.maxSpawnsPerWave), 1);
|
|
69
|
+
const here = fileURLToPath(import.meta.url);
|
|
70
|
+
const entryUrl = cfg.workerEntryUrl ?? here.replace(/pool\.(js|ts)$/, 'worker-entry.$1');
|
|
71
|
+
for (let i = 0; i < cfg.workers; i++) {
|
|
72
|
+
const command = makeCommandBuffer(i, cfg.commandWords ?? 1 << 14, true);
|
|
73
|
+
const reservation = makeReservationSab(reservationCap);
|
|
74
|
+
const workSab = new SharedArrayBuffer((WORK_HEADER_WORDS + maxBatch) * 4);
|
|
75
|
+
const wakeSab = new SharedArrayBuffer(4);
|
|
76
|
+
const noticeSab = new SharedArrayBuffer(4); // [0] = published column-growth generation
|
|
77
|
+
// Write-corral SAB: 1 header word (count) + 2 words per staged entry.
|
|
78
|
+
const writeCorralSab = new SharedArrayBuffer((1 + corralEntries * 2) * 4);
|
|
79
|
+
const boot = {
|
|
80
|
+
workerIndex: i,
|
|
81
|
+
kernelModule: cfg.kernelModule,
|
|
82
|
+
systemNames: cfg.systems.map((s) => s.name),
|
|
83
|
+
buffers: manifest,
|
|
84
|
+
indexBitsMask: cfg.world.handleLayout.indexMask,
|
|
85
|
+
components,
|
|
86
|
+
commandSab: command.words.buffer,
|
|
87
|
+
reservationSab: reservation.sab,
|
|
88
|
+
reservationCapacity: reservation.capacity,
|
|
89
|
+
waveSab: this.#waveCounter.sab,
|
|
90
|
+
workSab,
|
|
91
|
+
wakeSab,
|
|
92
|
+
noticeSab,
|
|
93
|
+
writeCorralSab,
|
|
94
|
+
};
|
|
95
|
+
const worker = new Worker(entryUrl, { workerData: boot });
|
|
96
|
+
const slot = {
|
|
97
|
+
index: i,
|
|
98
|
+
worker,
|
|
99
|
+
command,
|
|
100
|
+
reservation,
|
|
101
|
+
work: { sab: workSab, i32: new Int32Array(workSab), f32: new Float32Array(workSab) },
|
|
102
|
+
wake: new Int32Array(wakeSab),
|
|
103
|
+
notice: new Int32Array(noticeSab),
|
|
104
|
+
writeCorral: new Uint32Array(writeCorralSab),
|
|
105
|
+
ready: false,
|
|
106
|
+
};
|
|
107
|
+
worker.on('message', (msg) => {
|
|
108
|
+
if (msg.kind === 'ready')
|
|
109
|
+
slot.ready = true;
|
|
110
|
+
else if (msg.kind === 'diagnostic' || msg.kind === 'error')
|
|
111
|
+
this.#diag(msg.message ?? 'worker error');
|
|
112
|
+
});
|
|
113
|
+
worker.on('error', (err) => this.#diag(`worker ${i} crashed: ${String(err)}`));
|
|
114
|
+
this.#slots.push(slot);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Wait until every spawned worker has posted 'ready' (bootstrap complete). */
|
|
118
|
+
async ready() {
|
|
119
|
+
const deadline = Date.now() + 5000;
|
|
120
|
+
while (this.#slots.some((s) => !s.ready)) {
|
|
121
|
+
if (Date.now() > deadline)
|
|
122
|
+
throw new Error('worker pool bootstrap timed out');
|
|
123
|
+
await new Promise((r) => setTimeout(r, 1));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Run one round: dispatch each (systemId, workerIndex) batch to its worker, wait on the fence, then
|
|
128
|
+
* merge the per-worker command buffers in fixed worker-index order. `world.phase` is 'wave' across
|
|
129
|
+
* the dispatch (PHASE-2) and flipped back to 'serial' for the merge/apply flush slot.
|
|
130
|
+
*/
|
|
131
|
+
async runRound(batches, dt) {
|
|
132
|
+
if (this.#disposed)
|
|
133
|
+
throw new Error('worker pool disposed');
|
|
134
|
+
const active = batches.filter((b) => b.workerIndex >= 0 && b.workerIndex < this.#slots.length);
|
|
135
|
+
if (active.length === 0)
|
|
136
|
+
return;
|
|
137
|
+
// ---- re-backing fence: re-wrap any column that moved to a NEW SAB since the last dispatch
|
|
138
|
+
// BEFORE this one proceeds. One generation `!==` per wave when nothing grew (zero steady-state cost).
|
|
139
|
+
await this.#drainColumnGrowth();
|
|
140
|
+
// ---- reservation top-up (serial) ----
|
|
141
|
+
for (const b of active) {
|
|
142
|
+
const slot = this.#slots[b.workerIndex];
|
|
143
|
+
const sys = this.#systems[b.systemId];
|
|
144
|
+
const block = this.#world.reserveEntityBlock(b.workerIndex, sys.maxSpawnsPerWave);
|
|
145
|
+
fillReservation(slot.reservation, block);
|
|
146
|
+
slot.command.lastReservation = block;
|
|
147
|
+
resetBuffer(slot.command);
|
|
148
|
+
}
|
|
149
|
+
// ---- compute matched entity indices on the MAIN thread (bitmask-free archetype matching) ----
|
|
150
|
+
for (const b of active) {
|
|
151
|
+
const slot = this.#slots[b.workerIndex];
|
|
152
|
+
const sys = this.#systems[b.systemId];
|
|
153
|
+
const indices = this.#matchedIndices(sys);
|
|
154
|
+
const work = slot.work;
|
|
155
|
+
Atomics.store(work.i32, 0, b.systemId);
|
|
156
|
+
Atomics.store(work.i32, 1, indices.length);
|
|
157
|
+
work.f32[2] = dt;
|
|
158
|
+
indices.forEach((idx, k) => {
|
|
159
|
+
work.i32[WORK_HEADER_WORDS + k] = idx;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// ---- dispatch: flip to 'wave', arm the fence, wake the workers ----
|
|
163
|
+
this.#world.__setPhase('wave');
|
|
164
|
+
this.#waveSync.begin(this.#waveCounter, active.length);
|
|
165
|
+
this.#wakeGen += 1;
|
|
166
|
+
for (const b of active) {
|
|
167
|
+
const slot = this.#slots[b.workerIndex];
|
|
168
|
+
Atomics.store(slot.wake, WAKE, this.#wakeGen);
|
|
169
|
+
Atomics.notify(slot.wake, WAKE);
|
|
170
|
+
}
|
|
171
|
+
// ---- wait on the wave fence (Node main blocks directly, tier 2) ----
|
|
172
|
+
const r = this.#waveSync.await(this.#waveCounter);
|
|
173
|
+
if (r !== undefined)
|
|
174
|
+
await r;
|
|
175
|
+
// ---- serial flush slot: flip back, merge command buffers deterministically ----
|
|
176
|
+
this.#world.__setPhase('serial');
|
|
177
|
+
if (waveErrored(this.#waveCounter))
|
|
178
|
+
this.#diag('a worker system threw; its command buffer is applied/dropped per CB-SAFE');
|
|
179
|
+
// Merge each worker's value-write corral into the shared write log in ASCENDING worker-index order
|
|
180
|
+
// BEFORE the command buffers apply — mirroring single-thread run-wave's
|
|
181
|
+
// mergeCorrals()→flushAll() order. This is what makes onChange observers + `.changed` filters fire
|
|
182
|
+
// for worker field writes (and stamp changeVersion) deterministically.
|
|
183
|
+
const writers = active.map((b) => b.workerIndex).sort((a, z) => a - z);
|
|
184
|
+
const corralHeader = 1;
|
|
185
|
+
for (const wi of writers) {
|
|
186
|
+
const slot = this.#slots[wi];
|
|
187
|
+
const corral = slot.writeCorral;
|
|
188
|
+
const count = corral[0] >>> 0;
|
|
189
|
+
const capPairs = (corral.length - corralHeader) >>> 1;
|
|
190
|
+
if (count > capPairs) {
|
|
191
|
+
this.#diag(`worker ${wi} write-corral overflow (${count} > ${capPairs} entries); merged ${capPairs} (raise writeCorralEntries)`);
|
|
192
|
+
}
|
|
193
|
+
const merged = Math.min(count, capPairs);
|
|
194
|
+
if (merged > 0) {
|
|
195
|
+
this.#world.__mergeWorkerWrites(corral.subarray(corralHeader, corralHeader + merged * 2), merged);
|
|
196
|
+
}
|
|
197
|
+
corral[0] = 0;
|
|
198
|
+
}
|
|
199
|
+
const bufs = [];
|
|
200
|
+
for (const b of active) {
|
|
201
|
+
const slot = this.#slots[b.workerIndex];
|
|
202
|
+
const reported = workerHead(this.#waveCounter, b.workerIndex);
|
|
203
|
+
const cap = slot.command.words.length;
|
|
204
|
+
// Hard clamp: the worker writes the SAB in place, so a `head` that exceeds the SAB length would
|
|
205
|
+
// make the apply decode read past the backing (undefined → NaN opcode → 'corrupt command buffer').
|
|
206
|
+
// The worker's overflow cap (ensureWords/fixed) should make this unreachable; clamp + diagnose so
|
|
207
|
+
// a corrupt/over-large head can never crash apply (review issue #3).
|
|
208
|
+
if (reported > cap) {
|
|
209
|
+
this.#diag(`worker ${b.workerIndex} reported head ${reported} > command SAB capacity ${cap}; clamped (records lost — raise commandWords)`);
|
|
210
|
+
slot.command.head = cap;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
slot.command.head = reported;
|
|
214
|
+
}
|
|
215
|
+
bufs.push(slot.command);
|
|
216
|
+
}
|
|
217
|
+
bufs.sort((a, z) => a.workerIndex - z.workerIndex);
|
|
218
|
+
flushAll(this.#worldApply(), bufs);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* (the world's
|
|
222
|
+
* growth generation advanced), broadcast the new backings to EVERY worker and block on the wave fence
|
|
223
|
+
* until all have re-wrapped + ACKed — so no dispatch ever reads a worker's stale (abandoned-SAB) view.
|
|
224
|
+
* Steady state is a single generation `!==`; the drain + broadcast happen only on an actual re-backing.
|
|
225
|
+
*/
|
|
226
|
+
async #drainColumnGrowth() {
|
|
227
|
+
const log = this.#world.__columnGrowth();
|
|
228
|
+
if (log.generation === this.#appliedGrowthGen)
|
|
229
|
+
return; // nothing re-backed — the steady-state path
|
|
230
|
+
const gen = log.generation;
|
|
231
|
+
const notices = log.drain();
|
|
232
|
+
if (notices.length > 0) {
|
|
233
|
+
const msg = {
|
|
234
|
+
kind: 'columns-added',
|
|
235
|
+
generation: gen,
|
|
236
|
+
columns: notices.map((n) => ({ key: n.key, backing: n.backing, layout: n.layout })),
|
|
237
|
+
};
|
|
238
|
+
// Arm the fence for every worker (each ACKs by completing the wave once it has re-wrapped).
|
|
239
|
+
this.#world.__setPhase('wave');
|
|
240
|
+
this.#waveSync.begin(this.#waveCounter, this.#slots.length);
|
|
241
|
+
this.#wakeGen += 1;
|
|
242
|
+
for (const slot of this.#slots) {
|
|
243
|
+
slot.worker.postMessage(msg); // SAB references can ONLY ride postMessage, not a SAB
|
|
244
|
+
Atomics.store(slot.notice, 0, gen);
|
|
245
|
+
Atomics.store(slot.wake, WAKE, this.#wakeGen);
|
|
246
|
+
Atomics.notify(slot.wake, WAKE);
|
|
247
|
+
}
|
|
248
|
+
const r = this.#waveSync.await(this.#waveCounter);
|
|
249
|
+
if (r !== undefined)
|
|
250
|
+
await r;
|
|
251
|
+
this.#world.__setPhase('serial');
|
|
252
|
+
}
|
|
253
|
+
this.#appliedGrowthGen = gen;
|
|
254
|
+
}
|
|
255
|
+
#matchedIndices(sys) {
|
|
256
|
+
if (sys.matchComponents.length === 0)
|
|
257
|
+
return new Int32Array(0);
|
|
258
|
+
const terms = sys.matchComponents.map((c) => has(c));
|
|
259
|
+
const q = this.#world.query(...terms);
|
|
260
|
+
const out = [];
|
|
261
|
+
for (const idx of q.current)
|
|
262
|
+
out.push(idx);
|
|
263
|
+
return Int32Array.from(out);
|
|
264
|
+
}
|
|
265
|
+
#worldApply() {
|
|
266
|
+
const world = this.#world;
|
|
267
|
+
const layout = world.handleLayout;
|
|
268
|
+
const apply = world.__apply;
|
|
269
|
+
const codecById = this.#codecById;
|
|
270
|
+
return {
|
|
271
|
+
isAlive: (h) => world.isAlive(h),
|
|
272
|
+
handleIndex: (h) => handleIndex(h, layout),
|
|
273
|
+
spawnReserved: (h) => world.__spawnReserved(h),
|
|
274
|
+
despawn: (h) => world.despawn(h),
|
|
275
|
+
defOf: (id) => apply.defOf(id),
|
|
276
|
+
codecOf: (id) => codecById.get(id),
|
|
277
|
+
addMany: (h, defs) => apply.addMany(h, defs),
|
|
278
|
+
removeMany: (h, defs) => apply.removeMany(h, defs),
|
|
279
|
+
has: (h, def) => world.has(h, def),
|
|
280
|
+
writePayload: (h, def, values) => apply.writePayload(h, def, values),
|
|
281
|
+
// Relation apply: forward to the core __apply surface that @ecsia/relations
|
|
282
|
+
// fills in via createRelations(world). The scheduler does NOT import @ecsia/relations.
|
|
283
|
+
addPair: (s, rid, t, payload) => apply.addPair?.(s, rid, t, payload),
|
|
284
|
+
removePair: (s, rid, t) => apply.removePair?.(s, rid, t),
|
|
285
|
+
returnUnused: (cb) => {
|
|
286
|
+
const slot = this.#slots[cb.workerIndex];
|
|
287
|
+
const block = slot?.command.lastReservation;
|
|
288
|
+
if (slot !== undefined && block !== undefined) {
|
|
289
|
+
const filled = Math.min(block.handles.length, slot.reservation.capacity);
|
|
290
|
+
const consumed = consumedCount(slot.reservation, filled);
|
|
291
|
+
// returnReservedIds reclaims the unconsumed tail [consumed..]; clamp to the block size.
|
|
292
|
+
world.returnReservedIds(block, Math.min(consumed, block.handles.length));
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
warn: (m) => this.#diag(m),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async dispose() {
|
|
299
|
+
if (this.#disposed)
|
|
300
|
+
return;
|
|
301
|
+
this.#disposed = true;
|
|
302
|
+
this.#wakeGen = -1;
|
|
303
|
+
for (const slot of this.#slots) {
|
|
304
|
+
Atomics.store(slot.wake, WAKE, -1);
|
|
305
|
+
Atomics.notify(slot.wake, WAKE);
|
|
306
|
+
}
|
|
307
|
+
await Promise.all(this.#slots.map((s) => s.worker.terminate()));
|
|
308
|
+
}
|
|
309
|
+
get codecById() {
|
|
310
|
+
return this.#codecById;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/workers/pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EntityHandle, EntityReservation } from '@ecsia/core';
|
|
2
|
+
export interface WorkerReservationSab {
|
|
3
|
+
readonly sab: SharedArrayBuffer;
|
|
4
|
+
readonly view: Int32Array;
|
|
5
|
+
readonly capacity: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function makeReservationSab(capacity: number): WorkerReservationSab;
|
|
8
|
+
/** Main thread, serial: fill the SAB with a freshly-reserved block and reset the take cursor. */
|
|
9
|
+
export declare function fillReservation(r: WorkerReservationSab, reservation: EntityReservation): void;
|
|
10
|
+
/** Worker, mid-wave: take the next reserved handle via Atomics.sub, or NO_ENTITY (-1) if exhausted. */
|
|
11
|
+
export declare function takeReserved(r: WorkerReservationSab): EntityHandle;
|
|
12
|
+
/**
|
|
13
|
+
* Count of reserved handles the worker actually took = filled - remaining. `filled` is the block size
|
|
14
|
+
* the main thread wrote (NOT the SAB capacity, which may exceed the block), so a 0-handle block reports
|
|
15
|
+
* 0 consumed even though the SAB reserves a larger cursor capacity.
|
|
16
|
+
*/
|
|
17
|
+
export declare function consumedCount(r: WorkerReservationSab, filled: number): number;
|
|
18
|
+
//# sourceMappingURL=reservation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reservation.d.ts","sourceRoot":"","sources":["../../src/workers/reservation.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAKlE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAA;IAC/B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAIzE;AAED,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,CAAC,EAAE,oBAAoB,EAAE,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAI7F;AAED,uGAAuG;AACvG,wBAAgB,YAAY,CAAC,CAAC,EAAE,oBAAoB,GAAG,YAAY,CAOlE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAG7E"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SAB-backed per-worker entity-ID reservation. The main
|
|
2
|
+
// thread pre-reserves a block of fully-formed (alive) handles per worker before each wave via the
|
|
3
|
+
// serial `world.reserveEntityBlock` ( layout), writes the handles into a SAB, and the worker TAKES
|
|
4
|
+
// them mid-wave with `Atomics.sub` on a cursor word — exercising the Atomics.sub take path (the
|
|
5
|
+
// v2 ) without ever touching the shared free-list.
|
|
6
|
+
//
|
|
7
|
+
// SAB layout per worker: word 0 = cursor (next free slot, counts DOWN via Atomics.sub),
|
|
8
|
+
// words [1 .. 1+capacity) = the reserved full handles. A take of cursor c yields handles[c-1].
|
|
9
|
+
const CURSOR = 0;
|
|
10
|
+
const HANDLES_BASE = 1;
|
|
11
|
+
export function makeReservationSab(capacity) {
|
|
12
|
+
const cap = Math.max(capacity, 0);
|
|
13
|
+
const sab = new SharedArrayBuffer((1 + cap) * 4);
|
|
14
|
+
return { sab, view: new Int32Array(sab), capacity: cap };
|
|
15
|
+
}
|
|
16
|
+
/** Main thread, serial: fill the SAB with a freshly-reserved block and reset the take cursor. */
|
|
17
|
+
export function fillReservation(r, reservation) {
|
|
18
|
+
const n = Math.min(reservation.handles.length, r.capacity);
|
|
19
|
+
for (let i = 0; i < n; i++)
|
|
20
|
+
r.view[HANDLES_BASE + i] = reservation.handles[i];
|
|
21
|
+
Atomics.store(r.view, CURSOR, n); // cursor counts down; n handles available
|
|
22
|
+
}
|
|
23
|
+
/** Worker, mid-wave: take the next reserved handle via Atomics.sub, or NO_ENTITY (-1) if exhausted. */
|
|
24
|
+
export function takeReserved(r) {
|
|
25
|
+
const prev = Atomics.sub(r.view, CURSOR, 1);
|
|
26
|
+
if (prev <= 0) {
|
|
27
|
+
Atomics.add(r.view, CURSOR, 1); // undo: do not let the cursor run negative
|
|
28
|
+
return 0xffffffff;
|
|
29
|
+
}
|
|
30
|
+
return (r.view[HANDLES_BASE + (prev - 1)] >>> 0);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Count of reserved handles the worker actually took = filled - remaining. `filled` is the block size
|
|
34
|
+
* the main thread wrote (NOT the SAB capacity, which may exceed the block), so a 0-handle block reports
|
|
35
|
+
* 0 consumed even though the SAB reserves a larger cursor capacity.
|
|
36
|
+
*/
|
|
37
|
+
export function consumedCount(r, filled) {
|
|
38
|
+
const remaining = Math.max(Atomics.load(r.view, CURSOR), 0);
|
|
39
|
+
return Math.max(filled - remaining, 0);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=reservation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reservation.js","sourceRoot":"","sources":["../../src/workers/reservation.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,kGAAkG;AAClG,mGAAmG;AACnG,gGAAgG;AAChG,mDAAmD;AACnD,EAAE;AACF,wFAAwF;AACxF,+FAA+F;AAI/F,MAAM,MAAM,GAAG,CAAC,CAAA;AAChB,MAAM,YAAY,GAAG,CAAC,CAAA;AAQtB,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;IACjC,MAAM,GAAG,GAAG,IAAI,iBAAiB,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;IAChD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAA;AAC1D,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,eAAe,CAAC,CAAuB,EAAE,WAA8B;IACrF,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAA;IAC1D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QAAE,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC,CAAW,CAAA;IACvF,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA,CAAC,0CAA0C;AAC7E,CAAC;AAED,uGAAuG;AACvG,MAAM,UAAU,YAAY,CAAC,CAAuB;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;IAC3C,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA,CAAC,2CAA2C;QAC1E,OAAO,UAA0B,CAAA;IACnC,CAAC;IACD,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAE,KAAK,CAAC,CAAiB,CAAA;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,CAAuB,EAAE,MAAc;IACnE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3D,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC,CAAC,CAAA;AACxC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { WaveCounter, WaveSync, WaveSyncTier } from '../executor/seams.js';
|
|
2
|
+
/**
|
|
3
|
+
* Words [0..3] are the control block (remaining, epoch, errorFlag, padding); words [4..4+workers)
|
|
4
|
+
* are the per-worker buffer `head` each worker stores before completing, so the main thread reads the
|
|
5
|
+
* record count after the fence without any postMessage on the hot path (tier-2 blocking-main path).
|
|
6
|
+
*/
|
|
7
|
+
export declare function makeWaveCounter(workers: number): WaveCounter;
|
|
8
|
+
export declare function workerHead(c: WaveCounter, workerIndex: number): number;
|
|
9
|
+
/** Worker-side completion: the last decrementer wakes the waiter. */
|
|
10
|
+
export declare function completeWave(c: WaveCounter): void;
|
|
11
|
+
export declare function setWaveError(c: WaveCounter): void;
|
|
12
|
+
export declare function waveErrored(c: WaveCounter): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Build a WaveSync for the chosen tier. `await` MUST loop on Atomics.load(remaining) even after a
|
|
15
|
+
* wake (spurious wakeups + the epoch guard), resolving only when remaining === 0.
|
|
16
|
+
*/
|
|
17
|
+
export declare function makeWaveSync(tier: WaveSyncTier): WaveSync;
|
|
18
|
+
//# sourceMappingURL=wave-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wave-sync.d.ts","sourceRoot":"","sources":["../../src/workers/wave-sync.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAO/E;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAG5D;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,CAEjD;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,GAAG,IAAI,CAEjD;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAEnD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAoDzD"}
|