@daneren2005/shared-memory-ecs 1.7.2 → 1.8.1

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
@@ -14,7 +14,8 @@ systems, and domain data.
14
14
  config to the raw block values) and `attach(entity, memory, index)` (builds the accessor over that block).
15
15
  Loading a component is `attach(entity, memory, memory.create(toBlock(config)))`; splitting it this way lets a
16
16
  worker build the block off-thread (it calls only `toBlock`) and the main thread wrap it (`attach`) when it
17
- adopts the entity. Plus an optional `save(component)`, `free(component)`, and `died(component, entity, world)`.
17
+ adopts the entity. `attach` also receives the load config as an optional fourth argument, for main-thread-only
18
+ state kept off the block (e.g. a display name); it is `undefined` when adopting a worker-built block. Plus an optional `save(component)`, `free(component)`, and `died(component, entity, world)`.
18
19
  A component's data splits into `Config` (defining props supplied up front, e.g. `maxHealth`) and
19
20
  `Serialization` (runtime-derived state, e.g. current `health`); `toBlock` sees `Config & Serialization` while
20
21
  `save` returns only the `Serialization` slice. `free` runs when the component is torn down (see
@@ -272,7 +273,7 @@ class DamageSystem extends EntityWorkerSystem<Components, { health: Int32Array }
272
273
  }
273
274
  ```
274
275
 
275
- ### Update-function hooks: `init` and `preRun`
276
+ ### Update-function hooks: `init`, `queryChanged` and `preRun`
276
277
 
277
278
  `addDataToWorld` runs on the main thread and re-sends its data every run. When the data instead needs to
278
279
  *live in the worker* — computed once, or too big to ship each frame — attach hooks to the update function
@@ -330,6 +331,21 @@ damageUpdate.preRun = (world, entities, queries, callbacks) => {
330
331
  };
331
332
  ```
332
333
 
334
+ **`queryChanged`** runs before `preRun`, once for each sub-query in `queries` whose membership changed this run,
335
+ with that query's name and its `{ added, removed }` delta. Use it to keep a worker-local structure in sync with a
336
+ set of entities that rarely changes - a navigation grid of static walls, a lookup of static targets - instead of
337
+ rescanning the whole query every run. `added` can repeat an entity already in the query (its components changed
338
+ and its blocks were re-sent), so treat it as an upsert. Keep that structure on the `init`-returned state so a
339
+ reloaded world rebuilds it from scratch; the reload re-sends every member as added:
340
+
341
+ ```ts
342
+ wallUpdate.init = () => ({ walls: new Map<number, Rect>() });
343
+ wallUpdate.queryChanged = (world, queryName, delta) => {
344
+ delta.removed.forEach(entityId => world.walls.delete(entityId));
345
+ delta.added.forEach(({ entityId, components }) => world.walls.set(entityId, rectOf(components.transform)));
346
+ };
347
+ ```
348
+
333
349
  (An `entityRemoved(world, entityId, callbacks)` hook completes the set — it fires once per entity that left
334
350
  the system this run, so a worker can release any per-entity state it was holding.)
335
351
 
@@ -374,7 +390,7 @@ import { createSystemWorker } from '@daneren2005/shared-memory-ecs/worker';
374
390
  createSystemWorker(self, spawnerUpdate, registry);
375
391
  ```
376
392
 
377
- The run function may carry the same `init` / `entityRemoved` hooks as an `updateFunction`. Everything else —
393
+ The run function may carry the same `init` / `queryChanged` / `entityRemoved` hooks as an `updateFunction`. Everything else —
378
394
  callbacks, worker fallback, off-thread creation — behaves exactly as an `EntityWorkerSystem`.
379
395
 
380
396
  ### Importing in workers
@@ -671,6 +687,12 @@ the following updates, as above). `await world.clear()` instead tears the world
671
687
  empty, reusable state: it waits for every system's in-flight worker run to finish so nothing is still reading
672
688
  the memory, frees the held blocks immediately, and resolves once the world is ready to load into again.
673
689
 
690
+ Prefer `world.load()` over looping `world.loadEntity()` for a large batch. It still emits `entity-added` for every
691
+ entity, but systems ignore those while `world.isBulkLoading` is set and instead join the whole batch once at the end
692
+ through `system.addEntities(batch)`. Entities with the same component keys are grouped, so each query's component
693
+ test runs once per group instead of once per entity; system membership keeps load order. A system that overrides
694
+ `checkAddEntity` (or an `EntityWorkerSystem` that overrides `matchesQuery`) still has it called for each entity.
695
+
674
696
  ## Measuring performance
675
697
 
676
698
  `PerformanceTiming` watches a world and reports what running it costs. Hand it the world and it hooks itself
@@ -15,7 +15,7 @@ export interface ComponentDefinition<Component extends BaseComponent, T extends
15
15
  loadProperties: Array<keyof Config & string>;
16
16
  loadInFinishLoading?: boolean;
17
17
  toBlock(config: Config & Serialization, entity?: BaseEntity): Array<number>;
18
- attach(entity: BaseEntity, memory: MemoryComponent<T>, index: number): Component;
18
+ attach(entity: BaseEntity, memory: MemoryComponent<T>, index: number, config?: Config & Serialization): Component;
19
19
  save?(component: Component): Serialization;
20
20
  free?(component: Component): void;
21
21
  died?(component: Component, entity: BaseEntity, world: BaseWorld): void;
@@ -283,7 +283,18 @@ function b(t, n, r) {
283
283
  componentName: String(t)
284
284
  });
285
285
  }
286
- }, D = !1;
286
+ };
287
+ if (n.queryChanged) {
288
+ for (let [e, t] of Object.entries(S.queries)) if (t.added.length || t.removed.length) try {
289
+ n.queryChanged(S.world, e, t, E);
290
+ } catch (e) {
291
+ w.push({
292
+ error: e,
293
+ phase: "queryChanged"
294
+ });
295
+ }
296
+ }
297
+ let D = !1;
287
298
  if (n.preRun) try {
288
299
  n.preRun(S.world, l, T, E);
289
300
  } catch (e) {
@@ -334,7 +345,7 @@ function x(e, t) {
334
345
  //#region src/systems/workers/create-system-worker.ts
335
346
  function S(e) {
336
347
  let t = (() => {});
337
- return t.preRun = (t, n, r, i) => e(t, r, i), e.init && (t.init = e.init), e.entityRemoved && (t.entityRemoved = e.entityRemoved), t;
348
+ return t.preRun = (t, n, r, i) => e(t, r, i), e.init && (t.init = e.init), e.queryChanged && (t.queryChanged = e.queryChanged), e.entityRemoved && (t.entityRemoved = e.entityRemoved), t;
338
349
  }
339
350
  function C(e, t, n) {
340
351
  b(e, S(t), n);
@@ -342,4 +353,4 @@ function C(e, t, n) {
342
353
  //#endregion
343
354
  export { a as _, v as a, p as c, f as d, h as f, o as g, s as h, y as i, u as l, c as m, S as n, _ as o, l as p, b as r, g as s, C as t, d as u, i as v };
344
355
 
345
- //# sourceMappingURL=create-system-worker-CDQmmww7.js.map
356
+ //# sourceMappingURL=create-system-worker-C4aS3-3R.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-system-worker-CDQmmww7.js","names":[],"sources":["../src/memory-component.ts","../src/constant-string-cache.ts","../src/systems/workers/apply-query-delta.ts","../src/actions/build-worker-entity.ts","../src/component.ts","../src/entity-component.ts","../src/actions/kill-entity-worker.ts","../src/actions/create-entity-worker.ts","../src/actions/add-component-worker.ts","../src/actions/remove-component-worker.ts","../src/systems/workers/create-entity-system-worker.ts","../src/systems/workers/create-system-worker.ts"],"sourcesContent":["import SharedPool from '@daneren2005/shared-memory-objects/shared-pool';\nimport type { SharedPoolMemory } from '@daneren2005/shared-memory-objects/shared-pool';\nimport type MemoryHeap from '@daneren2005/shared-memory-objects/memory-heap';\nimport type { TypedArrayConstructor } from '@daneren2005/shared-memory-objects/interfaces/typed-array-constructor';\n\nexport type ComponentTypedArray = Uint32Array | Int32Array | Float32Array | Float64Array;\n\n// Backed by a SharedPool: all bookkeeping lives in the shared heap, so a worker can reconstruct a handle over\n// the same pool (pass the owner's getSharedMemory() as `memory`) and allocate/read blocks off-thread.\nexport default class MemoryComponent<T extends ComponentTypedArray = ComponentTypedArray> {\n\theap: MemoryHeap;\n\tpool: SharedPool<T>;\n\n\tconstructor(heap: MemoryHeap, type: TypedArrayConstructor<T>, dataLength: number, memory?: SharedPoolMemory) {\n\t\tthis.heap = heap;\n\t\tthis.pool = memory\n\t\t\t? new SharedPool<T>(heap, memory)\n\t\t\t: new SharedPool<T>(heap, {\n\t\t\t\ttype,\n\t\t\t\tdataLength,\n\t\t\t});\n\t}\n\n\t// Reconstructs a handle over an existing pool (from the owner's getSharedMemory()) — used in a worker, where the\n\t// pool's type/dataLength are already recorded in the heap header, so no definition is needed.\n\tstatic fromSharedMemory<T extends ComponentTypedArray = ComponentTypedArray>(heap: MemoryHeap, memory: SharedPoolMemory): MemoryComponent<T> {\n\t\treturn new MemoryComponent<T>(heap, Uint32Array as unknown as TypedArrayConstructor<T>, 1, memory);\n\t}\n\n\tgetSharedMemory(): SharedPoolMemory {\n\t\treturn this.pool.getSharedMemory();\n\t}\n\n\tget length() {\n\t\treturn this.pool.length;\n\t}\n\tget rawLength() {\n\t\treturn this.pool.bufferLength;\n\t}\n\n\tcreate(values: Array<number>): number {\n\t\treturn this.pool.push(values);\n\t}\n\n\tgetBlock(index: number): T {\n\t\treturn this.pool.at(index);\n\t}\n\tget(index: number, dataIndex: number): number {\n\t\treturn this.pool.get(index, dataIndex);\n\t}\n\tset(index: number, dataIndex: number, value: number) {\n\t\tlet array = this.pool.at(index);\n\t\tarray[dataIndex] = value;\n\t}\n\n\tdelete(index: number) {\n\t\tthis.pool.deleteIndex(index);\n\t}\n\tclear() {\n\t\tthis.pool.clear();\n\t}\n}\n","import ConstantString from '@daneren2005/shared-memory-objects/constant-string';\nimport { getPointer } from '@daneren2005/shared-memory-objects/utils/pointer';\nimport type MemoryHeap from '@daneren2005/shared-memory-objects/memory-heap';\n\n// Interns immutable strings in the heap and resolves them back from a pointer. Every distinct value is allocated\n// once as a single ConstantString shared by all users (15 \"Space Ship\" entities point at one allocation), and a\n// pointer resolves to its string through a Map hit before ever rebuilding it from memory. Both sides of the\n// worker boundary hold one: the main thread creates + interns; a worker (with a heap reconstructed from the same\n// SharedArrayBuffers) only ever resolves pointers and caches the results.\nexport default class ConstantStringCache {\n\tprivate heap: MemoryHeap;\n\t// value -> the one interned allocation. Only the creating (main) thread populates this; it owns the memory.\n\tprivate byValue = new Map<string, ConstantString>();\n\t// pointer -> value: the fast lookup every thread checks before touching memory.\n\tprivate byPointer = new Map<number, string>();\n\n\tconstructor(heap: MemoryHeap) {\n\t\tthis.heap = heap;\n\t}\n\n\t// Dedupes on value, so repeated types share a single ConstantString. Main-thread only.\n\tgetOrCreate(value: string): ConstantString {\n\t\tlet existing = this.byValue.get(value);\n\t\tif(existing) {\n\t\t\treturn existing;\n\t\t}\n\n\t\tconst string = new ConstantString(this.heap, value);\n\t\tthis.byValue.set(value, string);\n\t\tthis.byPointer.set(string.pointer, value);\n\n\t\treturn string;\n\t}\n\n\t// pointer -> string, checking the cache before rebuilding from memory. A pointer of 0 is the empty string; a\n\t// pointer into a buffer that has not synced to this thread yet returns undefined.\n\tgetString(pointer: number): string | undefined {\n\t\tif(pointer === 0) {\n\t\t\treturn '';\n\t\t}\n\n\t\tlet cached = this.byPointer.get(pointer);\n\t\tif(cached !== undefined) {\n\t\t\treturn cached;\n\t\t}\n\n\t\tconst memory = getPointer(pointer, this.heap.positionBits);\n\t\tif(this.heap.buffers[memory.bufferPosition] === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst value = new ConstantString(this.heap, memory).value;\n\t\tthis.byPointer.set(pointer, value);\n\n\t\treturn value;\n\t}\n\n\t// Frees every allocation this cache owns and empties the lookups. A worker's cache owns nothing (it only ever\n\t// resolved views), so this just drops its pointer lookups.\n\tclear() {\n\t\tthis.byValue.forEach(string => string.free());\n\t\tthis.byValue.clear();\n\t\tthis.byPointer.clear();\n\t}\n}\n","import type { EntityUpdateComponents, QueryDelta, UpdateEntityConfigObject } from '../entity-worker-system';\n\n// Applies a run's delta to a query's persistent list: drop the entities that left, upsert those that joined or\n// changed. A steady-state run carries empty arrays and returns the list untouched. Returns the list (removals\n// rebuild it via filter), so callers must store the returned reference back.\nexport function applyQueryDelta<T extends EntityUpdateComponents>(\n\tlist: Array<UpdateEntityConfigObject<T>>,\n\tdelta: QueryDelta<T>,\n): Array<UpdateEntityConfigObject<T>> {\n\tif(delta.removed.length) {\n\t\tconst removedSet = new Set(delta.removed);\n\t\tlist = list.filter(entry => !removedSet.has(entry.entityId));\n\t}\n\n\tif(delta.added.length) {\n\t\t// Index existing members so a re-added entity replaces its entry in place instead of duplicating.\n\t\tconst indexByEid = new Map<number, number>();\n\t\tlist.forEach((entry, index) => indexByEid.set(entry.entityId, index));\n\n\t\tfor(let entity of delta.added) {\n\t\t\tconst existing = indexByEid.get(entity.entityId);\n\t\t\tif(existing !== undefined) {\n\t\t\t\tlist[existing] = entity;\n\t\t\t} else {\n\t\t\t\tindexByEid.set(entity.entityId, list.length);\n\t\t\t\tlist.push(entity);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn list;\n}\n","import type { WorkerAllocator, WorkerCreateEntityConfig, WorkerCreatedComponent, WorkerCreatedEntity } from '../systems/entity-worker-system';\nimport type { WorkerEntityClassRegistry } from '../entity-class';\n\n// The slice of a component definition worker-side creation needs. Both the game's ComponentDefinitionMap and the\n// world's RegisteredComponentRegistry satisfy it.\nexport interface WorkerCreatableComponent {\n\tloadProperties: Array<string>\n\tloadInFinishLoading?: boolean\n\ttoBlock(config: Record<string, unknown>): Array<number>\n}\nexport type WorkerCreateRegistry = { [name: string]: WorkerCreatableComponent };\nexport type FactoryConfigs = { [type: string]: Record<string, unknown> };\n\n// Turns a factory-style config ({ type, ...overrides }) into a WorkerCreatedEntity, off-thread: layers the type's\n// factory template under the overrides, mints an id, and allocates + writes each triggered component's block via its\n// toBlock(). The always-present `entity` component is NOT built here - the main thread builds it on adopt (interning\n// the type is main-thread-only), reading `type`/`isStatic` off this descriptor. Shared by the real worker and the\n// main-thread fallback so both behave identically.\nexport function buildWorkerEntity(\n\tconfig: WorkerCreateEntityConfig,\n\tfactoryConfigs: FactoryConfigs,\n\tregistry: WorkerCreateRegistry,\n\tallocator: WorkerAllocator,\n\tclasses?: WorkerEntityClassRegistry,\n): WorkerCreatedEntity {\n\tconst template = factoryConfigs[config.type] ?? {};\n\tif(classes && Object.prototype.hasOwnProperty.call(config, 'class')) {\n\t\tthrow new Error('Entity class comes from its type template and cannot be supplied by a worker config');\n\t}\n\tconst merged: Record<string, unknown> = { ...template, ...config };\n\tconst entityClass = typeof template.class === 'string' ? template.class : undefined;\n\tlet componentNames = Object.keys(registry);\n\tif(classes) {\n\t\tif(template.type !== config.type) {\n\t\t\tthrow new Error(`Entity template ${config.type} must declare the same type`);\n\t\t}\n\t\tconst definition = entityClass ? classes[entityClass] : undefined;\n\t\tif(!definition) {\n\t\t\tthrow new Error(`Entity type ${config.type} has unknown class ${entityClass ?? ''}`);\n\t\t}\n\t\tcomponentNames = definition.components;\n\t}\n\n\tconst eid = allocator.allocateEid();\n\tconst components: { [name: string]: number } = {};\n\tfor(let name of componentNames) {\n\t\t// The entity component is always built on the main thread when the descriptor is adopted (interning the type),\n\t\t// never off-thread - skip it here whether or not the registry includes it.\n\t\tif(name === 'entity') {\n\t\t\tcontinue;\n\t\t}\n\t\tconst definition = registry[name];\n\t\tif(!definition) {\n\t\t\tthrow new Error(`Entity class ${entityClass ?? ''} contains unknown component ${name}`);\n\t\t}\n\t\t// Deferred components need the whole world (other entities) that a worker doesn't have; skip them.\n\t\tif(definition.loadInFinishLoading) {\n\t\t\tcontinue;\n\t\t}\n\t\tif(definition.loadProperties.some(prop => prop in merged)) {\n\t\t\tcomponents[name] = allocator.allocateComponentBlock(name, definition.toBlock(merged));\n\t\t}\n\t}\n\n\treturn {\n\t\teid,\n\t\ttype: config.type,\n\t\tclass: entityClass,\n\t\tisStatic: merged.isStatic as boolean | undefined,\n\t\tcomponents,\n\t};\n}\n\nexport function buildWorkerComponent(\n\tname: string,\n\tconfig: Record<string, unknown>,\n\tregistry: WorkerCreateRegistry,\n\tallocator: WorkerAllocator,\n): WorkerCreatedComponent {\n\tif(name === 'entity') {\n\t\tthrow new Error('The entity component cannot be added at runtime');\n\t}\n\tconst definition = registry[name];\n\tif(!definition) {\n\t\tthrow new Error(`Unknown component: ${name}`);\n\t}\n\tif(definition.loadInFinishLoading) {\n\t\tthrow new Error(`Component ${name} requires main-thread finishLoading and cannot be added in a worker`);\n\t}\n\n\treturn {\n\t\tname,\n\t\tindex: allocator.allocateComponentBlock(name, definition.toBlock(config)),\n\t};\n}\n","import type { ComponentTypedArray } from './memory-component';\nimport type { BaseComponent } from './component-definition';\n\n// Base class for a memory-backed component accessor. Subclass it, declare prototype get/set accessors over\n// `this.block` indexed by the component's exported *_INDEX constants, and return `new YourComponent(block, index)`\n// from `attach`. Because the accessors live on one shared prototype - not fresh closures captured per entity -\n// reads off thousands of entities every frame stay monomorphic and inline, and constructing a component allocates\n// just the instance instead of a closure per accessor. Components that own no extra memory need nothing else; a\n// subclass that does can take more constructor args (the entity, another pool) and store them as fields.\nexport default abstract class Component<T extends ComponentTypedArray = ComponentTypedArray> implements BaseComponent {\n\treadonly index: number;\n\treadonly block: T;\n\n\tconstructor(block: T, index: number) {\n\t\tthis.block = block;\n\t\tthis.index = index;\n\t}\n}\n","import type { ComponentDefinition } from './component-definition';\nimport Component from './component';\nimport type ConstantStringCache from './constant-string-cache';\n\nexport interface EntityComponent {\n\tindex: number\n\treadonly type: string\n\treadonly class: string\n\tdead: boolean\n\tisStatic: boolean\n}\n\nexport interface EntityComponentConfig {\n\ttype: string\n\tisStatic?: boolean\n}\n\nexport interface EntityClassConfig {\n\treadonly class: string\n}\n\nexport interface EntityComponentSerialization {\n\ttype?: string\n\tdead?: boolean\n}\n\nexport const DEAD_INDEX = 0;\nexport const STATIC_INDEX = 1;\nexport const TYPE_INDEX = 2;\nexport const CLASS_INDEX = 3;\n\n// Identity strings are interned, so the accessor needs the world's cache to resolve their pointers.\nclass EntityComponentImpl extends Component<Uint32Array> implements EntityComponent {\n\tprivate cache: ConstantStringCache;\n\n\tconstructor(block: Uint32Array, index: number, cache: ConstantStringCache) {\n\t\tsuper(block, index);\n\t\tthis.cache = cache;\n\t}\n\n\tget type() {\n\t\treturn this.cache.getString(this.block[TYPE_INDEX]) ?? '';\n\t}\n\tget class() {\n\t\treturn this.cache.getString(this.block[CLASS_INDEX]) ?? '';\n\t}\n\tget dead() {\n\t\treturn this.block[DEAD_INDEX] === 1;\n\t}\n\tset dead(value: boolean) {\n\t\tthis.block[DEAD_INDEX] = value ? 1 : 0;\n\t}\n\tget isStatic() {\n\t\treturn this.block[STATIC_INDEX] === 1;\n\t}\n\tset isStatic(value: boolean) {\n\t\tthis.block[STATIC_INDEX] = value ? 1 : 0;\n\t}\n}\n\nexport const entityDefinition: ComponentDefinition<EntityComponent, Uint32Array, EntityComponentConfig & Partial<EntityClassConfig>, EntityComponentSerialization> = {\n\ttype: Uint32Array,\n\tsize: 4,\n\tloadProperties: ['type', 'isStatic'],\n\t// The only component that reads the entity (never worker-created): it interns its identity strings through the heap and\n\t// stores the pointer. `entity` is always passed here since loadComponent runs on the main thread.\n\ttoBlock(config, entity) {\n\t\tconst cache = entity!.world.constantStrings;\n\t\tconst typePointer = config.type ? cache.getOrCreate(config.type).pointer : 0;\n\t\tconst classPointer = config.class ? cache.getOrCreate(config.class).pointer : 0;\n\t\treturn [config.dead ? 1 : 0, config.isStatic ? 1 : 0, typePointer, classPointer];\n\t},\n\tattach(entity, memory, index) {\n\t\treturn new EntityComponentImpl(memory.getBlock(index), index, entity.world.constantStrings);\n\t},\n\tsave(component) {\n\t\tconst config: EntityComponentSerialization = {};\n\t\tif(component.type) {\n\t\t\tconfig.type = component.type;\n\t\t}\n\t\tif(component.dead) {\n\t\t\tconfig.dead = true;\n\t\t}\n\n\t\treturn config;\n\t},\n};\n","import type { ComponentTypedArray } from '../memory-component';\nimport type { EntityWorkerSystemCallbacks, EntityUpdateComponents } from '../systems/entity-worker-system';\nimport { DEAD_INDEX } from '../entity-component';\n\n// Worker-side killEntity: flags the entity dead in its block and reports the death back via callbacks. The\n// entity component must be in the system's query for its block to be available here.\nexport default function killEntityWorker(entityId: number, components: EntityUpdateComponents, callbacks: EntityWorkerSystemCallbacks): void {\n\tconst block = (components as { entity?: ComponentTypedArray }).entity;\n\tif(block) {\n\t\tblock[DEAD_INDEX] = 1;\n\t}\n\n\tcallbacks.entityDied(entityId);\n}\n","import type { EntityWorkerSystemCallbacks, EntityWorkerSystemWorld, WorkerCreateEntityConfig } from '../systems/entity-worker-system';\n\n// Worker-side entity creation from a factory config. The worker merges the selected type template, resolves its\n// optional class, mints a unique id, and allocates + writes every allowed, triggered component block\n// directly into the shared pools (off-thread) via each component's toBlock(). It reports the descriptor back; the main\n// thread adopts it on run-complete (world.adoptEntity), building the `entity` component and class wrapper there\n// worker-written blocks, so the entity first exists on the following frame.\n//\n// Requires the system to be registered with `createsEntities: true` and its worker entry to pass the component\n// registry to createEntitySystemWorker (so the worker has each component's toBlock).\nexport default function createEntityWorker(world: EntityWorkerSystemWorld, config: WorkerCreateEntityConfig, callbacks: EntityWorkerSystemCallbacks): void {\n\tif(!world.buildEntityDescriptor) {\n\t\tthrow new Error('createEntityWorker requires the system to be registered with createsEntities: true and the component registry passed to createEntitySystemWorker');\n\t}\n\n\tcallbacks.createEntity(world.buildEntityDescriptor(config));\n}\n","import type { ComponentMap } from '../component-definition';\nimport type { EntityWorkerSystemCallbacks, EntityWorkerSystemWorld } from '../systems/entity-worker-system';\n\n// Allocates a component block in the worker and asks the main thread to attach it after the run. The entity's\n// current query snapshot is intentionally unchanged until the next run.\nexport default function addComponentWorker<C extends ComponentMap, K extends keyof C>(\n\tworld: EntityWorkerSystemWorld,\n\tentityId: number,\n\tcomponentName: K,\n\tconfig: Record<string, unknown>,\n\tcallbacks: EntityWorkerSystemCallbacks<C>,\n): void {\n\tif(!world.buildComponentDescriptor) {\n\t\tthrow new Error('addComponentWorker requires addsComponents: true and the component registry passed to createEntitySystemWorker');\n\t}\n\n\tcallbacks.addComponent(entityId, world.buildComponentDescriptor(String(componentName), config));\n}\n","import type { ComponentMap } from '../component-definition';\nimport type { EntityWorkerSystemCallbacks } from '../systems/entity-worker-system';\n\n// Requests a main-thread detach after the worker run. Shared memory is freed only after every system that could\n// still hold the old block has completed a run.\nexport default function removeComponentWorker<C extends ComponentMap, K extends keyof C>(\n\tentityId: number,\n\tcomponentName: K,\n\tcallbacks: EntityWorkerSystemCallbacks<C>,\n): void {\n\tcallbacks.removeComponent(entityId, componentName);\n}\n","import MemoryHeap from '@daneren2005/shared-memory-objects/memory-heap';\nimport type AllocatedMemory from '@daneren2005/shared-memory-objects/allocated-memory';\nimport type EntitySystemWorkerMessage from './entity-system-worker-message';\nimport type { EntityEvent, SystemEvents, WorkerRunError } from './entity-system-worker-message';\nimport ConstantStringCache from '../../constant-string-cache';\nimport MemoryComponent from '../../memory-component';\nimport type { ComponentDefinitionMap, ComponentMap } from '../../component-definition';\nimport type {\n\tEntityWorkerSystemCallbacks, EntityWorkerSystemWorld, EntityQueryComponents, EntityUpdateComponents,\n\tEntityUpdateFunction, QueryDelta, UpdateEntityConfigObject, WorkerComponentChange, WorkerCreatedEntity,\n} from '../entity-worker-system';\nimport { buildWorkerComponent, buildWorkerEntity, type FactoryConfigs } from '../../actions/build-worker-entity';\nimport type { WorkerEntityClassRegistry } from '../../entity-class';\nimport { applyQueryDelta } from './apply-query-delta';\n\n// The slice of the worker global scope createEntitySystemWorker touches. Passing `self` explicitly (rather than\n// using the global) lets runners like @vitest/web-worker, which inject `self` as a module local, drive it.\nexport interface EntitySystemWorkerScope {\n\tonmessage: ((e: MessageEvent) => void) | null\n\tpostMessage(message: EntitySystemWorkerMessage): void\n}\n\nexport default function createEntitySystemWorker<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends EntityWorkerSystemWorld = EntityWorkerSystemWorld,\n\tD = unknown,\n>(scope: EntitySystemWorkerScope, updateFunction: EntityUpdateFunction<C, T, W, D>, definitions?: ComponentDefinitionMap) {\n\t// Persistent lists, carried across runs and mutated by each run's delta (see applyQueryDelta).\n\tlet entities: Array<UpdateEntityConfigObject<T>> = [];\n\tconst queryEntities: { [key: string]: Array<UpdateEntityConfigObject<T>> } = {};\n\t// What updateFunction.init returned: persistent state (e.g. a seeded RNG) merged onto `world` each run.\n\tlet worldExtension: Partial<W> | undefined;\n\tlet heap: MemoryHeap | undefined;\n\tlet stringCache: ConstantStringCache | undefined;\n\t// Reconstructed pool handles over the world's shared state, plus the factory templates - for off-thread entity\n\t// allocation. `definitions` (passed by the game's worker entry) supplies each component's toBlock/loadProperties.\n\tlet pools: { [name: string]: MemoryComponent } = {};\n\tlet eidCounter: AllocatedMemory | undefined;\n\tlet factoryConfigs: FactoryConfigs | undefined;\n\tlet factoryClasses: WorkerEntityClassRegistry | undefined;\n\tlet addsComponents = false;\n\tconst getString = (pointer: number): string => stringCache?.getString(pointer) ?? '';\n\n\tscope.onmessage = function(e) {\n\t\tconst message = e.data as EntitySystemWorkerMessage<W, D>;\n\n\t\tif(message.type === 'init') {\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'init-complete',\n\t\t\t});\n\t\t} else if(message.type === 'load') {\n\t\t\tif(message.heap) {\n\t\t\t\theap = new MemoryHeap(message.heap);\n\t\t\t\tstringCache = new ConstantStringCache(heap);\n\t\t\t\t// Report any buffer this worker grows (while allocating off-thread) back to the main thread, which adopts\n\t\t\t\t// it and fans it out to sibling workers.\n\t\t\t\theap.addOnGrowBufferHandlers(buffer => postMessageTyped(scope, { type: 'grow-buffer-from-worker', buffer }));\n\t\t\t}\n\t\t\tif(heap && message.sharedMemory) {\n\t\t\t\tpools = {};\n\t\t\t\tfor(const name of Object.keys(message.sharedMemory.components)) {\n\t\t\t\t\tpools[name] = MemoryComponent.fromSharedMemory(heap, message.sharedMemory.components[name]);\n\t\t\t\t}\n\t\t\t\teidCounter = heap.getSharedAlloc(message.sharedMemory.eidCounter);\n\t\t\t}\n\t\t\tfactoryConfigs = message.factoryConfigs;\n\t\t\tfactoryClasses = message.factoryClasses;\n\t\t\taddsComponents = !!message.addsComponents;\n\t\t\tworldExtension = updateFunction.init?.(message.data) ?? undefined;\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'loaded',\n\t\t\t});\n\t\t} else if(message.type === 'grow-buffer') {\n\t\t\t// Never replace a buffer this worker already holds: the main thread fans a worker-grown buffer back out to\n\t\t\t// every worker (including the one that grew it), and overwriting our own live MemoryBuffer would discard its\n\t\t\t// allocation bookkeeping. The SharedArrayBuffer at that position is already the same one.\n\t\t\tif(heap && heap.buffers[message.buffer.bufferPosition] === undefined) {\n\t\t\t\theap.addSharedBuffer(message.buffer);\n\t\t\t}\n\t\t} else if(message.type === 'reset') {\n\t\t\t// Drop the persistent lists so a reused world starts empty; worldExtension is refreshed by the next load.\n\t\t\tentities = [];\n\t\t\tfor(const key of Object.keys(queryEntities)) {\n\t\t\t\tdelete queryEntities[key];\n\t\t\t}\n\t\t\tworldExtension = undefined;\n\t\t} else if(message.type === 'run') {\n\t\t\tif(worldExtension) {\n\t\t\t\tObject.assign(message.world, worldExtension);\n\t\t\t}\n\t\t\tmessage.world.getString = getString;\n\t\t\tmessage.world.heap = heap;\n\t\t\tconst allocator = {\n\t\t\t\tallocateEid: () => eidCounter ? Atomics.add(eidCounter.data, 0, 1) + 1 : 0,\n\t\t\t\tallocateComponentBlock: (name: string, values: Array<number>) => pools[name].create(values),\n\t\t\t};\n\t\t\tmessage.world.allocate = allocator;\n\t\t\t// Only a system registered with createsEntities (factoryConfigs shipped) whose worker entry passed the\n\t\t\t// component definitions can create entities from a config.\n\t\t\tif(factoryConfigs && definitions) {\n\t\t\t\tmessage.world.buildEntityDescriptor = config => buildWorkerEntity(config, factoryConfigs!, definitions, allocator, factoryClasses);\n\t\t\t}\n\t\t\tif(addsComponents && definitions) {\n\t\t\t\tmessage.world.buildComponentDescriptor = (name, config) => buildWorkerComponent(name, config, definitions, allocator);\n\t\t\t}\n\t\t\tconst start = performance.now();\n\t\t\tlet entityEvents: Array<EntityEvent> = [];\n\t\t\tlet systemEvents: SystemEvents = {};\n\t\t\tlet createdEntities: Array<WorkerCreatedEntity> = [];\n\t\t\tlet componentChanges: Array<WorkerComponentChange> = [];\n\t\t\tlet errors: Array<WorkerRunError> = [];\n\n\t\t\tentities = applyQueryDelta(entities, message.entities as QueryDelta<T>);\n\n\t\t\tlet queries: EntityQueryComponents<C> = {};\n\t\t\tObject.entries(message.queries).forEach(([queryKey, delta]) => {\n\t\t\t\tconst list = applyQueryDelta(queryEntities[queryKey] ?? [], delta as QueryDelta<T>);\n\t\t\t\tqueryEntities[queryKey] = list;\n\t\t\t\tqueries[queryKey] = list;\n\t\t\t});\n\n\t\t\tlet callbacks: EntityWorkerSystemCallbacks<C> = {\n\t\t\t\tentityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'component-property-updated',\n\t\t\t\t\t\targs: [componentName, prop, value],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\temitEntityEvent(entityId: number, event: string, ...args: Array<unknown>) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\targs,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\temitSystemEvent(event: string, entityId: number) {\n\t\t\t\t\t(systemEvents[event] ?? (systemEvents[event] = [])).push(entityId);\n\t\t\t\t},\n\t\t\t\tentityDied(entityId: number) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'death',\n\t\t\t\t\t\targs: [],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tcreateEntity(entity: WorkerCreatedEntity) {\n\t\t\t\t\tcreatedEntities.push(entity);\n\t\t\t\t},\n\t\t\t\taddComponent(entityId, component) {\n\t\t\t\t\tcomponentChanges.push({ type: 'add', entityId, component });\n\t\t\t\t},\n\t\t\t\tremoveComponent(entityId, componentName) {\n\t\t\t\t\tcomponentChanges.push({ type: 'remove', entityId, componentName: String(componentName) });\n\t\t\t\t},\n\t\t\t};\n\t\t\tlet preRunFailed = false;\n\t\t\tif(updateFunction.preRun) {\n\t\t\t\ttry {\n\t\t\t\t\tupdateFunction.preRun(message.world, entities, queries, callbacks);\n\t\t\t\t} catch(err) {\n\t\t\t\t\tpreRunFailed = true;\n\t\t\t\t\terrors.push({ error: err as Error, phase: 'preRun' });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// preRun sets up the run's state; if it threw, skip the entity loop rather than run over half-prepared data.\n\t\t\tif(!preRunFailed) {\n\t\t\t\tentities.forEach(entity => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tupdateFunction(message.world, entity.entityId, entity.components, queries, callbacks);\n\t\t\t\t\t} catch(err) {\n\t\t\t\t\t\t// One entity failing must not stop the rest of the run.\n\t\t\t\t\t\terrors.push({ error: err as Error, phase: 'update', entityId: entity.entityId });\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif(updateFunction.entityRemoved) {\n\t\t\t\tmessage.entities.removed.forEach(entityId => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tupdateFunction.entityRemoved!(message.world, entityId, callbacks);\n\t\t\t\t\t} catch(err) {\n\t\t\t\t\t\terrors.push({ error: err as Error, phase: 'entityRemoved', entityId });\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst runTime = performance.now() - start;\n\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'run-complete',\n\t\t\t\tgeneration: message.generation,\n\t\t\t\trunTime,\n\t\t\t\tevents: entityEvents,\n\t\t\t\tsystemEvents,\n\t\t\t\tcreated: createdEntities,\n\t\t\t\tcomponentChanges,\n\t\t\t\terrors,\n\t\t\t});\n\t\t}\n\t};\n}\n\nfunction postMessageTyped(scope: EntitySystemWorkerScope, message: EntitySystemWorkerMessage) {\n\tscope.postMessage(message);\n}\n","import createEntitySystemWorker, { type EntitySystemWorkerScope } from './create-entity-system-worker';\nimport type { ComponentDefinitionMap, ComponentMap } from '../../component-definition';\nimport type {\n\tEntityWorkerSystemCallbacks, EntityWorkerSystemWorld, EntityQueryComponents, EntityUpdateComponents,\n\tEntityUpdateFunction, EntityUpdateInitFunction, EntityRemovedFunction,\n} from '../entity-worker-system';\n\n// A WorkerSystem's single per-run function: called once per run over the named sub-queries, not once per entity.\n// Optional `init`/`entityRemoved` mirror EntityUpdateFunction's hooks.\nexport type WorkerSystemRunFunction<\n\tC extends ComponentMap,\n\tW extends EntityWorkerSystemWorld = EntityWorkerSystemWorld,\n\tD = unknown,\n> = ((world: W, queries: EntityQueryComponents<C>, callbacks: EntityWorkerSystemCallbacks<C>) => void) & {\n\tinit?: EntityUpdateInitFunction<W, D>\n\tentityRemoved?: EntityRemovedFunction<C, W>\n};\n\n// A WorkerSystem reuses the EntityWorkerSystem worker machinery: the single run function becomes `preRun` (which runs\n// once with query access) and the per-entity body is a no-op, since a WorkerSystem never populates its main query.\nexport function toEntityUpdateFunction<\n\tC extends ComponentMap,\n\tW extends EntityWorkerSystemWorld = EntityWorkerSystemWorld,\n\tD = unknown,\n>(run: WorkerSystemRunFunction<C, W, D>): EntityUpdateFunction<C, EntityUpdateComponents<C>, W, D> {\n\tconst updateFunction = (() => {}) as EntityUpdateFunction<C, EntityUpdateComponents<C>, W, D>;\n\tupdateFunction.preRun = (world, _entities, queries, callbacks) => run(world, queries, callbacks);\n\tif(run.init) {\n\t\tupdateFunction.init = run.init;\n\t}\n\tif(run.entityRemoved) {\n\t\tupdateFunction.entityRemoved = run.entityRemoved;\n\t}\n\n\treturn updateFunction;\n}\n\n// Worker entry helper for a WorkerSystem (mirrors createEntitySystemWorker). Pass the component registry only for a\n// worker that creates entities.\nexport default function createSystemWorker<\n\tC extends ComponentMap,\n\tW extends EntityWorkerSystemWorld = EntityWorkerSystemWorld,\n\tD = unknown,\n>(scope: EntitySystemWorkerScope, runFunction: WorkerSystemRunFunction<C, W, D>, definitions?: ComponentDefinitionMap) {\n\tcreateEntitySystemWorker(scope, toEntityUpdateFunction(runFunction), definitions);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"create-system-worker-C4aS3-3R.js","names":[],"sources":["../src/memory-component.ts","../src/constant-string-cache.ts","../src/systems/workers/apply-query-delta.ts","../src/actions/build-worker-entity.ts","../src/component.ts","../src/entity-component.ts","../src/actions/kill-entity-worker.ts","../src/actions/create-entity-worker.ts","../src/actions/add-component-worker.ts","../src/actions/remove-component-worker.ts","../src/systems/workers/create-entity-system-worker.ts","../src/systems/workers/create-system-worker.ts"],"sourcesContent":["import SharedPool from '@daneren2005/shared-memory-objects/shared-pool';\nimport type { SharedPoolMemory } from '@daneren2005/shared-memory-objects/shared-pool';\nimport type MemoryHeap from '@daneren2005/shared-memory-objects/memory-heap';\nimport type { TypedArrayConstructor } from '@daneren2005/shared-memory-objects/interfaces/typed-array-constructor';\n\nexport type ComponentTypedArray = Uint32Array | Int32Array | Float32Array | Float64Array;\n\n// Backed by a SharedPool: all bookkeeping lives in the shared heap, so a worker can reconstruct a handle over\n// the same pool (pass the owner's getSharedMemory() as `memory`) and allocate/read blocks off-thread.\nexport default class MemoryComponent<T extends ComponentTypedArray = ComponentTypedArray> {\n\theap: MemoryHeap;\n\tpool: SharedPool<T>;\n\n\tconstructor(heap: MemoryHeap, type: TypedArrayConstructor<T>, dataLength: number, memory?: SharedPoolMemory) {\n\t\tthis.heap = heap;\n\t\tthis.pool = memory\n\t\t\t? new SharedPool<T>(heap, memory)\n\t\t\t: new SharedPool<T>(heap, {\n\t\t\t\ttype,\n\t\t\t\tdataLength,\n\t\t\t});\n\t}\n\n\t// Reconstructs a handle over an existing pool (from the owner's getSharedMemory()) — used in a worker, where the\n\t// pool's type/dataLength are already recorded in the heap header, so no definition is needed.\n\tstatic fromSharedMemory<T extends ComponentTypedArray = ComponentTypedArray>(heap: MemoryHeap, memory: SharedPoolMemory): MemoryComponent<T> {\n\t\treturn new MemoryComponent<T>(heap, Uint32Array as unknown as TypedArrayConstructor<T>, 1, memory);\n\t}\n\n\tgetSharedMemory(): SharedPoolMemory {\n\t\treturn this.pool.getSharedMemory();\n\t}\n\n\tget length() {\n\t\treturn this.pool.length;\n\t}\n\tget rawLength() {\n\t\treturn this.pool.bufferLength;\n\t}\n\n\tcreate(values: Array<number>): number {\n\t\treturn this.pool.push(values);\n\t}\n\n\tgetBlock(index: number): T {\n\t\treturn this.pool.at(index);\n\t}\n\tget(index: number, dataIndex: number): number {\n\t\treturn this.pool.get(index, dataIndex);\n\t}\n\tset(index: number, dataIndex: number, value: number) {\n\t\tlet array = this.pool.at(index);\n\t\tarray[dataIndex] = value;\n\t}\n\n\tdelete(index: number) {\n\t\tthis.pool.deleteIndex(index);\n\t}\n\tclear() {\n\t\tthis.pool.clear();\n\t}\n}\n","import ConstantString from '@daneren2005/shared-memory-objects/constant-string';\nimport { getPointer } from '@daneren2005/shared-memory-objects/utils/pointer';\nimport type MemoryHeap from '@daneren2005/shared-memory-objects/memory-heap';\n\n// Interns immutable strings in the heap and resolves them back from a pointer. Every distinct value is allocated\n// once as a single ConstantString shared by all users (15 \"Space Ship\" entities point at one allocation), and a\n// pointer resolves to its string through a Map hit before ever rebuilding it from memory. Both sides of the\n// worker boundary hold one: the main thread creates + interns; a worker (with a heap reconstructed from the same\n// SharedArrayBuffers) only ever resolves pointers and caches the results.\nexport default class ConstantStringCache {\n\tprivate heap: MemoryHeap;\n\t// value -> the one interned allocation. Only the creating (main) thread populates this; it owns the memory.\n\tprivate byValue = new Map<string, ConstantString>();\n\t// pointer -> value: the fast lookup every thread checks before touching memory.\n\tprivate byPointer = new Map<number, string>();\n\n\tconstructor(heap: MemoryHeap) {\n\t\tthis.heap = heap;\n\t}\n\n\t// Dedupes on value, so repeated types share a single ConstantString. Main-thread only.\n\tgetOrCreate(value: string): ConstantString {\n\t\tlet existing = this.byValue.get(value);\n\t\tif(existing) {\n\t\t\treturn existing;\n\t\t}\n\n\t\tconst string = new ConstantString(this.heap, value);\n\t\tthis.byValue.set(value, string);\n\t\tthis.byPointer.set(string.pointer, value);\n\n\t\treturn string;\n\t}\n\n\t// pointer -> string, checking the cache before rebuilding from memory. A pointer of 0 is the empty string; a\n\t// pointer into a buffer that has not synced to this thread yet returns undefined.\n\tgetString(pointer: number): string | undefined {\n\t\tif(pointer === 0) {\n\t\t\treturn '';\n\t\t}\n\n\t\tlet cached = this.byPointer.get(pointer);\n\t\tif(cached !== undefined) {\n\t\t\treturn cached;\n\t\t}\n\n\t\tconst memory = getPointer(pointer, this.heap.positionBits);\n\t\tif(this.heap.buffers[memory.bufferPosition] === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst value = new ConstantString(this.heap, memory).value;\n\t\tthis.byPointer.set(pointer, value);\n\n\t\treturn value;\n\t}\n\n\t// Frees every allocation this cache owns and empties the lookups. A worker's cache owns nothing (it only ever\n\t// resolved views), so this just drops its pointer lookups.\n\tclear() {\n\t\tthis.byValue.forEach(string => string.free());\n\t\tthis.byValue.clear();\n\t\tthis.byPointer.clear();\n\t}\n}\n","import type { EntityUpdateComponents, QueryDelta, UpdateEntityConfigObject } from '../entity-worker-system';\n\n// Applies a run's delta to a query's persistent list: drop the entities that left, upsert those that joined or\n// changed. A steady-state run carries empty arrays and returns the list untouched. Returns the list (removals\n// rebuild it via filter), so callers must store the returned reference back.\nexport function applyQueryDelta<T extends EntityUpdateComponents>(\n\tlist: Array<UpdateEntityConfigObject<T>>,\n\tdelta: QueryDelta<T>,\n): Array<UpdateEntityConfigObject<T>> {\n\tif(delta.removed.length) {\n\t\tconst removedSet = new Set(delta.removed);\n\t\tlist = list.filter(entry => !removedSet.has(entry.entityId));\n\t}\n\n\tif(delta.added.length) {\n\t\t// Index existing members so a re-added entity replaces its entry in place instead of duplicating.\n\t\tconst indexByEid = new Map<number, number>();\n\t\tlist.forEach((entry, index) => indexByEid.set(entry.entityId, index));\n\n\t\tfor(let entity of delta.added) {\n\t\t\tconst existing = indexByEid.get(entity.entityId);\n\t\t\tif(existing !== undefined) {\n\t\t\t\tlist[existing] = entity;\n\t\t\t} else {\n\t\t\t\tindexByEid.set(entity.entityId, list.length);\n\t\t\t\tlist.push(entity);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn list;\n}\n","import type { WorkerAllocator, WorkerCreateEntityConfig, WorkerCreatedComponent, WorkerCreatedEntity } from '../systems/entity-worker-system';\nimport type { WorkerEntityClassRegistry } from '../entity-class';\n\n// The slice of a component definition worker-side creation needs. Both the game's ComponentDefinitionMap and the\n// world's RegisteredComponentRegistry satisfy it.\nexport interface WorkerCreatableComponent {\n\tloadProperties: Array<string>\n\tloadInFinishLoading?: boolean\n\ttoBlock(config: Record<string, unknown>): Array<number>\n}\nexport type WorkerCreateRegistry = { [name: string]: WorkerCreatableComponent };\nexport type FactoryConfigs = { [type: string]: Record<string, unknown> };\n\n// Turns a factory-style config ({ type, ...overrides }) into a WorkerCreatedEntity, off-thread: layers the type's\n// factory template under the overrides, mints an id, and allocates + writes each triggered component's block via its\n// toBlock(). The always-present `entity` component is NOT built here - the main thread builds it on adopt (interning\n// the type is main-thread-only), reading `type`/`isStatic` off this descriptor. Shared by the real worker and the\n// main-thread fallback so both behave identically.\nexport function buildWorkerEntity(\n\tconfig: WorkerCreateEntityConfig,\n\tfactoryConfigs: FactoryConfigs,\n\tregistry: WorkerCreateRegistry,\n\tallocator: WorkerAllocator,\n\tclasses?: WorkerEntityClassRegistry,\n): WorkerCreatedEntity {\n\tconst template = factoryConfigs[config.type] ?? {};\n\tif(classes && Object.prototype.hasOwnProperty.call(config, 'class')) {\n\t\tthrow new Error('Entity class comes from its type template and cannot be supplied by a worker config');\n\t}\n\tconst merged: Record<string, unknown> = { ...template, ...config };\n\tconst entityClass = typeof template.class === 'string' ? template.class : undefined;\n\tlet componentNames = Object.keys(registry);\n\tif(classes) {\n\t\tif(template.type !== config.type) {\n\t\t\tthrow new Error(`Entity template ${config.type} must declare the same type`);\n\t\t}\n\t\tconst definition = entityClass ? classes[entityClass] : undefined;\n\t\tif(!definition) {\n\t\t\tthrow new Error(`Entity type ${config.type} has unknown class ${entityClass ?? ''}`);\n\t\t}\n\t\tcomponentNames = definition.components;\n\t}\n\n\tconst eid = allocator.allocateEid();\n\tconst components: { [name: string]: number } = {};\n\tfor(let name of componentNames) {\n\t\t// The entity component is always built on the main thread when the descriptor is adopted (interning the type),\n\t\t// never off-thread - skip it here whether or not the registry includes it.\n\t\tif(name === 'entity') {\n\t\t\tcontinue;\n\t\t}\n\t\tconst definition = registry[name];\n\t\tif(!definition) {\n\t\t\tthrow new Error(`Entity class ${entityClass ?? ''} contains unknown component ${name}`);\n\t\t}\n\t\t// Deferred components need the whole world (other entities) that a worker doesn't have; skip them.\n\t\tif(definition.loadInFinishLoading) {\n\t\t\tcontinue;\n\t\t}\n\t\tif(definition.loadProperties.some(prop => prop in merged)) {\n\t\t\tcomponents[name] = allocator.allocateComponentBlock(name, definition.toBlock(merged));\n\t\t}\n\t}\n\n\treturn {\n\t\teid,\n\t\ttype: config.type,\n\t\tclass: entityClass,\n\t\tisStatic: merged.isStatic as boolean | undefined,\n\t\tcomponents,\n\t};\n}\n\nexport function buildWorkerComponent(\n\tname: string,\n\tconfig: Record<string, unknown>,\n\tregistry: WorkerCreateRegistry,\n\tallocator: WorkerAllocator,\n): WorkerCreatedComponent {\n\tif(name === 'entity') {\n\t\tthrow new Error('The entity component cannot be added at runtime');\n\t}\n\tconst definition = registry[name];\n\tif(!definition) {\n\t\tthrow new Error(`Unknown component: ${name}`);\n\t}\n\tif(definition.loadInFinishLoading) {\n\t\tthrow new Error(`Component ${name} requires main-thread finishLoading and cannot be added in a worker`);\n\t}\n\n\treturn {\n\t\tname,\n\t\tindex: allocator.allocateComponentBlock(name, definition.toBlock(config)),\n\t};\n}\n","import type { ComponentTypedArray } from './memory-component';\nimport type { BaseComponent } from './component-definition';\n\n// Base class for a memory-backed component accessor. Subclass it, declare prototype get/set accessors over\n// `this.block` indexed by the component's exported *_INDEX constants, and return `new YourComponent(block, index)`\n// from `attach`. Because the accessors live on one shared prototype - not fresh closures captured per entity -\n// reads off thousands of entities every frame stay monomorphic and inline, and constructing a component allocates\n// just the instance instead of a closure per accessor. Components that own no extra memory need nothing else; a\n// subclass that does can take more constructor args (the entity, another pool) and store them as fields.\nexport default abstract class Component<T extends ComponentTypedArray = ComponentTypedArray> implements BaseComponent {\n\treadonly index: number;\n\treadonly block: T;\n\n\tconstructor(block: T, index: number) {\n\t\tthis.block = block;\n\t\tthis.index = index;\n\t}\n}\n","import type { ComponentDefinition } from './component-definition';\nimport Component from './component';\nimport type ConstantStringCache from './constant-string-cache';\n\nexport interface EntityComponent {\n\tindex: number\n\treadonly type: string\n\treadonly class: string\n\tdead: boolean\n\tisStatic: boolean\n}\n\nexport interface EntityComponentConfig {\n\ttype: string\n\tisStatic?: boolean\n}\n\nexport interface EntityClassConfig {\n\treadonly class: string\n}\n\nexport interface EntityComponentSerialization {\n\ttype?: string\n\tdead?: boolean\n}\n\nexport const DEAD_INDEX = 0;\nexport const STATIC_INDEX = 1;\nexport const TYPE_INDEX = 2;\nexport const CLASS_INDEX = 3;\n\n// Identity strings are interned, so the accessor needs the world's cache to resolve their pointers.\nclass EntityComponentImpl extends Component<Uint32Array> implements EntityComponent {\n\tprivate cache: ConstantStringCache;\n\n\tconstructor(block: Uint32Array, index: number, cache: ConstantStringCache) {\n\t\tsuper(block, index);\n\t\tthis.cache = cache;\n\t}\n\n\tget type() {\n\t\treturn this.cache.getString(this.block[TYPE_INDEX]) ?? '';\n\t}\n\tget class() {\n\t\treturn this.cache.getString(this.block[CLASS_INDEX]) ?? '';\n\t}\n\tget dead() {\n\t\treturn this.block[DEAD_INDEX] === 1;\n\t}\n\tset dead(value: boolean) {\n\t\tthis.block[DEAD_INDEX] = value ? 1 : 0;\n\t}\n\tget isStatic() {\n\t\treturn this.block[STATIC_INDEX] === 1;\n\t}\n\tset isStatic(value: boolean) {\n\t\tthis.block[STATIC_INDEX] = value ? 1 : 0;\n\t}\n}\n\nexport const entityDefinition: ComponentDefinition<EntityComponent, Uint32Array, EntityComponentConfig & Partial<EntityClassConfig>, EntityComponentSerialization> = {\n\ttype: Uint32Array,\n\tsize: 4,\n\tloadProperties: ['type', 'isStatic'],\n\t// The only component that reads the entity (never worker-created): it interns its identity strings through the heap and\n\t// stores the pointer. `entity` is always passed here since loadComponent runs on the main thread.\n\ttoBlock(config, entity) {\n\t\tconst cache = entity!.world.constantStrings;\n\t\tconst typePointer = config.type ? cache.getOrCreate(config.type).pointer : 0;\n\t\tconst classPointer = config.class ? cache.getOrCreate(config.class).pointer : 0;\n\t\treturn [config.dead ? 1 : 0, config.isStatic ? 1 : 0, typePointer, classPointer];\n\t},\n\tattach(entity, memory, index) {\n\t\treturn new EntityComponentImpl(memory.getBlock(index), index, entity.world.constantStrings);\n\t},\n\tsave(component) {\n\t\tconst config: EntityComponentSerialization = {};\n\t\tif(component.type) {\n\t\t\tconfig.type = component.type;\n\t\t}\n\t\tif(component.dead) {\n\t\t\tconfig.dead = true;\n\t\t}\n\n\t\treturn config;\n\t},\n};\n","import type { ComponentTypedArray } from '../memory-component';\nimport type { EntityWorkerSystemCallbacks, EntityUpdateComponents } from '../systems/entity-worker-system';\nimport { DEAD_INDEX } from '../entity-component';\n\n// Worker-side killEntity: flags the entity dead in its block and reports the death back via callbacks. The\n// entity component must be in the system's query for its block to be available here.\nexport default function killEntityWorker(entityId: number, components: EntityUpdateComponents, callbacks: EntityWorkerSystemCallbacks): void {\n\tconst block = (components as { entity?: ComponentTypedArray }).entity;\n\tif(block) {\n\t\tblock[DEAD_INDEX] = 1;\n\t}\n\n\tcallbacks.entityDied(entityId);\n}\n","import type { EntityWorkerSystemCallbacks, EntityWorkerSystemWorld, WorkerCreateEntityConfig } from '../systems/entity-worker-system';\n\n// Worker-side entity creation from a factory config. The worker merges the selected type template, resolves its\n// optional class, mints a unique id, and allocates + writes every allowed, triggered component block\n// directly into the shared pools (off-thread) via each component's toBlock(). It reports the descriptor back; the main\n// thread adopts it on run-complete (world.adoptEntity), building the `entity` component and class wrapper there\n// worker-written blocks, so the entity first exists on the following frame.\n//\n// Requires the system to be registered with `createsEntities: true` and its worker entry to pass the component\n// registry to createEntitySystemWorker (so the worker has each component's toBlock).\nexport default function createEntityWorker(world: EntityWorkerSystemWorld, config: WorkerCreateEntityConfig, callbacks: EntityWorkerSystemCallbacks): void {\n\tif(!world.buildEntityDescriptor) {\n\t\tthrow new Error('createEntityWorker requires the system to be registered with createsEntities: true and the component registry passed to createEntitySystemWorker');\n\t}\n\n\tcallbacks.createEntity(world.buildEntityDescriptor(config));\n}\n","import type { ComponentMap } from '../component-definition';\nimport type { EntityWorkerSystemCallbacks, EntityWorkerSystemWorld } from '../systems/entity-worker-system';\n\n// Allocates a component block in the worker and asks the main thread to attach it after the run. The entity's\n// current query snapshot is intentionally unchanged until the next run.\nexport default function addComponentWorker<C extends ComponentMap, K extends keyof C>(\n\tworld: EntityWorkerSystemWorld,\n\tentityId: number,\n\tcomponentName: K,\n\tconfig: Record<string, unknown>,\n\tcallbacks: EntityWorkerSystemCallbacks<C>,\n): void {\n\tif(!world.buildComponentDescriptor) {\n\t\tthrow new Error('addComponentWorker requires addsComponents: true and the component registry passed to createEntitySystemWorker');\n\t}\n\n\tcallbacks.addComponent(entityId, world.buildComponentDescriptor(String(componentName), config));\n}\n","import type { ComponentMap } from '../component-definition';\nimport type { EntityWorkerSystemCallbacks } from '../systems/entity-worker-system';\n\n// Requests a main-thread detach after the worker run. Shared memory is freed only after every system that could\n// still hold the old block has completed a run.\nexport default function removeComponentWorker<C extends ComponentMap, K extends keyof C>(\n\tentityId: number,\n\tcomponentName: K,\n\tcallbacks: EntityWorkerSystemCallbacks<C>,\n): void {\n\tcallbacks.removeComponent(entityId, componentName);\n}\n","import MemoryHeap from '@daneren2005/shared-memory-objects/memory-heap';\nimport type AllocatedMemory from '@daneren2005/shared-memory-objects/allocated-memory';\nimport type EntitySystemWorkerMessage from './entity-system-worker-message';\nimport type { EntityEvent, SystemEvents, WorkerRunError } from './entity-system-worker-message';\nimport ConstantStringCache from '../../constant-string-cache';\nimport MemoryComponent from '../../memory-component';\nimport type { ComponentDefinitionMap, ComponentMap } from '../../component-definition';\nimport type {\n\tEntityWorkerSystemCallbacks, EntityWorkerSystemWorld, EntityQueryComponents, EntityUpdateComponents,\n\tEntityUpdateFunction, QueryDelta, UpdateEntityConfigObject, WorkerComponentChange, WorkerCreatedEntity,\n} from '../entity-worker-system';\nimport { buildWorkerComponent, buildWorkerEntity, type FactoryConfigs } from '../../actions/build-worker-entity';\nimport type { WorkerEntityClassRegistry } from '../../entity-class';\nimport { applyQueryDelta } from './apply-query-delta';\n\n// The slice of the worker global scope createEntitySystemWorker touches. Passing `self` explicitly (rather than\n// using the global) lets runners like @vitest/web-worker, which inject `self` as a module local, drive it.\nexport interface EntitySystemWorkerScope {\n\tonmessage: ((e: MessageEvent) => void) | null\n\tpostMessage(message: EntitySystemWorkerMessage): void\n}\n\nexport default function createEntitySystemWorker<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends EntityWorkerSystemWorld = EntityWorkerSystemWorld,\n\tD = unknown,\n>(scope: EntitySystemWorkerScope, updateFunction: EntityUpdateFunction<C, T, W, D>, definitions?: ComponentDefinitionMap) {\n\t// Persistent lists, carried across runs and mutated by each run's delta (see applyQueryDelta).\n\tlet entities: Array<UpdateEntityConfigObject<T>> = [];\n\tconst queryEntities: { [key: string]: Array<UpdateEntityConfigObject<T>> } = {};\n\t// What updateFunction.init returned: persistent state (e.g. a seeded RNG) merged onto `world` each run.\n\tlet worldExtension: Partial<W> | undefined;\n\tlet heap: MemoryHeap | undefined;\n\tlet stringCache: ConstantStringCache | undefined;\n\t// Reconstructed pool handles over the world's shared state, plus the factory templates - for off-thread entity\n\t// allocation. `definitions` (passed by the game's worker entry) supplies each component's toBlock/loadProperties.\n\tlet pools: { [name: string]: MemoryComponent } = {};\n\tlet eidCounter: AllocatedMemory | undefined;\n\tlet factoryConfigs: FactoryConfigs | undefined;\n\tlet factoryClasses: WorkerEntityClassRegistry | undefined;\n\tlet addsComponents = false;\n\tconst getString = (pointer: number): string => stringCache?.getString(pointer) ?? '';\n\n\tscope.onmessage = function(e) {\n\t\tconst message = e.data as EntitySystemWorkerMessage<W, D>;\n\n\t\tif(message.type === 'init') {\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'init-complete',\n\t\t\t});\n\t\t} else if(message.type === 'load') {\n\t\t\tif(message.heap) {\n\t\t\t\theap = new MemoryHeap(message.heap);\n\t\t\t\tstringCache = new ConstantStringCache(heap);\n\t\t\t\t// Report any buffer this worker grows (while allocating off-thread) back to the main thread, which adopts\n\t\t\t\t// it and fans it out to sibling workers.\n\t\t\t\theap.addOnGrowBufferHandlers(buffer => postMessageTyped(scope, { type: 'grow-buffer-from-worker', buffer }));\n\t\t\t}\n\t\t\tif(heap && message.sharedMemory) {\n\t\t\t\tpools = {};\n\t\t\t\tfor(const name of Object.keys(message.sharedMemory.components)) {\n\t\t\t\t\tpools[name] = MemoryComponent.fromSharedMemory(heap, message.sharedMemory.components[name]);\n\t\t\t\t}\n\t\t\t\teidCounter = heap.getSharedAlloc(message.sharedMemory.eidCounter);\n\t\t\t}\n\t\t\tfactoryConfigs = message.factoryConfigs;\n\t\t\tfactoryClasses = message.factoryClasses;\n\t\t\taddsComponents = !!message.addsComponents;\n\t\t\tworldExtension = updateFunction.init?.(message.data) ?? undefined;\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'loaded',\n\t\t\t});\n\t\t} else if(message.type === 'grow-buffer') {\n\t\t\t// Never replace a buffer this worker already holds: the main thread fans a worker-grown buffer back out to\n\t\t\t// every worker (including the one that grew it), and overwriting our own live MemoryBuffer would discard its\n\t\t\t// allocation bookkeeping. The SharedArrayBuffer at that position is already the same one.\n\t\t\tif(heap && heap.buffers[message.buffer.bufferPosition] === undefined) {\n\t\t\t\theap.addSharedBuffer(message.buffer);\n\t\t\t}\n\t\t} else if(message.type === 'reset') {\n\t\t\t// Drop the persistent lists so a reused world starts empty; worldExtension is refreshed by the next load.\n\t\t\tentities = [];\n\t\t\tfor(const key of Object.keys(queryEntities)) {\n\t\t\t\tdelete queryEntities[key];\n\t\t\t}\n\t\t\tworldExtension = undefined;\n\t\t} else if(message.type === 'run') {\n\t\t\tif(worldExtension) {\n\t\t\t\tObject.assign(message.world, worldExtension);\n\t\t\t}\n\t\t\tmessage.world.getString = getString;\n\t\t\tmessage.world.heap = heap;\n\t\t\tconst allocator = {\n\t\t\t\tallocateEid: () => eidCounter ? Atomics.add(eidCounter.data, 0, 1) + 1 : 0,\n\t\t\t\tallocateComponentBlock: (name: string, values: Array<number>) => pools[name].create(values),\n\t\t\t};\n\t\t\tmessage.world.allocate = allocator;\n\t\t\t// Only a system registered with createsEntities (factoryConfigs shipped) whose worker entry passed the\n\t\t\t// component definitions can create entities from a config.\n\t\t\tif(factoryConfigs && definitions) {\n\t\t\t\tmessage.world.buildEntityDescriptor = config => buildWorkerEntity(config, factoryConfigs!, definitions, allocator, factoryClasses);\n\t\t\t}\n\t\t\tif(addsComponents && definitions) {\n\t\t\t\tmessage.world.buildComponentDescriptor = (name, config) => buildWorkerComponent(name, config, definitions, allocator);\n\t\t\t}\n\t\t\tconst start = performance.now();\n\t\t\tlet entityEvents: Array<EntityEvent> = [];\n\t\t\tlet systemEvents: SystemEvents = {};\n\t\t\tlet createdEntities: Array<WorkerCreatedEntity> = [];\n\t\t\tlet componentChanges: Array<WorkerComponentChange> = [];\n\t\t\tlet errors: Array<WorkerRunError> = [];\n\n\t\t\tentities = applyQueryDelta(entities, message.entities as QueryDelta<T>);\n\n\t\t\tlet queries: EntityQueryComponents<C> = {};\n\t\t\tObject.entries(message.queries).forEach(([queryKey, delta]) => {\n\t\t\t\tconst list = applyQueryDelta(queryEntities[queryKey] ?? [], delta as QueryDelta<T>);\n\t\t\t\tqueryEntities[queryKey] = list;\n\t\t\t\tqueries[queryKey] = list;\n\t\t\t});\n\n\t\t\tlet callbacks: EntityWorkerSystemCallbacks<C> = {\n\t\t\t\tentityComponentChanged<K extends keyof C, P extends keyof C[K]>(entityId: number, componentName: K, prop: P, value: C[K][P]) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'component-property-updated',\n\t\t\t\t\t\targs: [componentName, prop, value],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\temitEntityEvent(entityId: number, event: string, ...args: Array<unknown>) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent,\n\t\t\t\t\t\targs,\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\temitSystemEvent(event: string, entityId: number) {\n\t\t\t\t\t(systemEvents[event] ?? (systemEvents[event] = [])).push(entityId);\n\t\t\t\t},\n\t\t\t\tentityDied(entityId: number) {\n\t\t\t\t\tentityEvents.push({\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tevent: 'death',\n\t\t\t\t\t\targs: [],\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tcreateEntity(entity: WorkerCreatedEntity) {\n\t\t\t\t\tcreatedEntities.push(entity);\n\t\t\t\t},\n\t\t\t\taddComponent(entityId, component) {\n\t\t\t\t\tcomponentChanges.push({ type: 'add', entityId, component });\n\t\t\t\t},\n\t\t\t\tremoveComponent(entityId, componentName) {\n\t\t\t\t\tcomponentChanges.push({ type: 'remove', entityId, componentName: String(componentName) });\n\t\t\t\t},\n\t\t\t};\n\t\t\tif(updateFunction.queryChanged) {\n\t\t\t\tfor(const [queryName, delta] of Object.entries(message.queries)) {\n\t\t\t\t\tif(!delta.added.length && !delta.removed.length) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\ttry {\n\t\t\t\t\t\tupdateFunction.queryChanged(message.world, queryName, delta, callbacks);\n\t\t\t\t\t} catch(err) {\n\t\t\t\t\t\terrors.push({ error: err as Error, phase: 'queryChanged' });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet preRunFailed = false;\n\t\t\tif(updateFunction.preRun) {\n\t\t\t\ttry {\n\t\t\t\t\tupdateFunction.preRun(message.world, entities, queries, callbacks);\n\t\t\t\t} catch(err) {\n\t\t\t\t\tpreRunFailed = true;\n\t\t\t\t\terrors.push({ error: err as Error, phase: 'preRun' });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// preRun sets up the run's state; if it threw, skip the entity loop rather than run over half-prepared data.\n\t\t\tif(!preRunFailed) {\n\t\t\t\tentities.forEach(entity => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tupdateFunction(message.world, entity.entityId, entity.components, queries, callbacks);\n\t\t\t\t\t} catch(err) {\n\t\t\t\t\t\t// One entity failing must not stop the rest of the run.\n\t\t\t\t\t\terrors.push({ error: err as Error, phase: 'update', entityId: entity.entityId });\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif(updateFunction.entityRemoved) {\n\t\t\t\tmessage.entities.removed.forEach(entityId => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tupdateFunction.entityRemoved!(message.world, entityId, callbacks);\n\t\t\t\t\t} catch(err) {\n\t\t\t\t\t\terrors.push({ error: err as Error, phase: 'entityRemoved', entityId });\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst runTime = performance.now() - start;\n\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'run-complete',\n\t\t\t\tgeneration: message.generation,\n\t\t\t\trunTime,\n\t\t\t\tevents: entityEvents,\n\t\t\t\tsystemEvents,\n\t\t\t\tcreated: createdEntities,\n\t\t\t\tcomponentChanges,\n\t\t\t\terrors,\n\t\t\t});\n\t\t}\n\t};\n}\n\nfunction postMessageTyped(scope: EntitySystemWorkerScope, message: EntitySystemWorkerMessage) {\n\tscope.postMessage(message);\n}\n","import createEntitySystemWorker, { type EntitySystemWorkerScope } from './create-entity-system-worker';\nimport type { ComponentDefinitionMap, ComponentMap } from '../../component-definition';\nimport type {\n\tEntityWorkerSystemCallbacks, EntityWorkerSystemWorld, EntityQueryComponents, EntityUpdateComponents,\n\tEntityUpdateFunction, EntityUpdateInitFunction, EntityRemovedFunction, EntityQueryChangedFunction,\n} from '../entity-worker-system';\n\n// A WorkerSystem's single per-run function: called once per run over the named sub-queries, not once per entity.\n// Optional `init`/`queryChanged`/`entityRemoved` mirror EntityUpdateFunction's hooks.\nexport type WorkerSystemRunFunction<\n\tC extends ComponentMap,\n\tW extends EntityWorkerSystemWorld = EntityWorkerSystemWorld,\n\tD = unknown,\n> = ((world: W, queries: EntityQueryComponents<C>, callbacks: EntityWorkerSystemCallbacks<C>) => void) & {\n\tinit?: EntityUpdateInitFunction<W, D>\n\tqueryChanged?: EntityQueryChangedFunction<C, W>\n\tentityRemoved?: EntityRemovedFunction<C, W>\n};\n\n// A WorkerSystem reuses the EntityWorkerSystem worker machinery: the single run function becomes `preRun` (which runs\n// once with query access) and the per-entity body is a no-op, since a WorkerSystem never populates its main query.\nexport function toEntityUpdateFunction<\n\tC extends ComponentMap,\n\tW extends EntityWorkerSystemWorld = EntityWorkerSystemWorld,\n\tD = unknown,\n>(run: WorkerSystemRunFunction<C, W, D>): EntityUpdateFunction<C, EntityUpdateComponents<C>, W, D> {\n\tconst updateFunction = (() => {}) as EntityUpdateFunction<C, EntityUpdateComponents<C>, W, D>;\n\tupdateFunction.preRun = (world, _entities, queries, callbacks) => run(world, queries, callbacks);\n\tif(run.init) {\n\t\tupdateFunction.init = run.init;\n\t}\n\tif(run.queryChanged) {\n\t\tupdateFunction.queryChanged = run.queryChanged;\n\t}\n\tif(run.entityRemoved) {\n\t\tupdateFunction.entityRemoved = run.entityRemoved;\n\t}\n\n\treturn updateFunction;\n}\n\n// Worker entry helper for a WorkerSystem (mirrors createEntitySystemWorker). Pass the component registry only for a\n// worker that creates entities.\nexport default function createSystemWorker<\n\tC extends ComponentMap,\n\tW extends EntityWorkerSystemWorld = EntityWorkerSystemWorld,\n\tD = unknown,\n>(scope: EntitySystemWorkerScope, runFunction: WorkerSystemRunFunction<C, W, D>, definitions?: ComponentDefinitionMap) {\n\tcreateEntitySystemWorker(scope, toEntityUpdateFunction(runFunction), definitions);\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ export default class EntityFactory<C extends ComponentMap = ComponentMap, Cfg =
9
9
  };
10
10
  classes: EntityClassRegistry;
11
11
  private allowedComponents;
12
+ private forbiddenProperties;
12
13
  constructor(configs?: {
13
14
  [type: string]: Cfg;
14
15
  }, classes?: EntityClassRegistry);
@@ -29,4 +30,6 @@ export default class EntityFactory<C extends ComponentMap = ComponentMap, Cfg =
29
30
  private validateResolvedConfig;
30
31
  private validateTypeClass;
31
32
  private validateComponentTriggers;
33
+ private getForbiddenProperties;
34
+ private throwComponentTriggerError;
32
35
  }
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@ export type { ComponentTypedArray } from './memory-component';
19
19
  export { default as Component } from './component';
20
20
  export type { BaseComponent, ComponentMap, ComponentDefinition, ComponentDefinitionMap, ComponentRegistry, ComponentsOf, EntityConfigOf, RegisteredComponentDefinition, RegisteredComponentRegistry, } from './component-definition';
21
21
  export { default as System } from './systems/system';
22
- export type { SystemConfig, SystemError, SystemErrorPhase } from './systems/system';
22
+ export type { EntityBatch, SystemConfig, SystemError, SystemErrorPhase } from './systems/system';
23
23
  export { default as IterableSystem } from './systems/iterable-system';
24
24
  export type { IterableSystemConfig } from './systems/iterable-system';
25
25
  export { default as EntitySystem } from './systems/entity-system';
@@ -27,7 +27,7 @@ export type { EntitySystemConfig } from './systems/entity-system';
27
27
  export { default as EntityWorkerSystem } from './systems/entity-worker-system';
28
28
  export { default as WorkerSystem } from './systems/worker-system';
29
29
  export type { WorkerSystemConfig, WorkerSystemRunFunction } from './systems/worker-system';
30
- export type { EntityWorkerSystemConfig, EntityWorkerSystemQuery, EntityWorkerSystemWorld, EntityWorkerSystemCallbacks, WorkerAllocator, WorkerCreateEntityConfig, WorkerCreatedEntity, WorkerCreatedComponent, WorkerComponentChange, EntityUpdateComponents, EntityQueryComponents, EntityUpdateFunction, EntityUpdateInitFunction, EntityUpdatePreRunFunction, EntityRemovedFunction, UpdateEntityConfig, UpdateEntityConfigObject, } from './systems/entity-worker-system';
30
+ export type { EntityWorkerSystemConfig, EntityWorkerSystemQuery, EntityWorkerSystemWorld, EntityWorkerSystemCallbacks, WorkerAllocator, WorkerCreateEntityConfig, WorkerCreatedEntity, WorkerCreatedComponent, WorkerComponentChange, EntityUpdateComponents, EntityQueryComponents, EntityUpdateFunction, EntityUpdateInitFunction, EntityUpdatePreRunFunction, EntityQueryChangedFunction, QueryDelta, EntityRemovedFunction, UpdateEntityConfig, UpdateEntityConfigObject, } from './systems/entity-worker-system';
31
31
  export { default as WebWorker } from './systems/workers/web-worker';
32
32
  export { default as EntitySystemWebWorker } from './systems/workers/entity-system-web-worker';
33
33
  export { default as createEntitySystemWorker } from './systems/workers/create-entity-system-worker';