@daneren2005/shared-memory-ecs 1.2.2 → 1.4.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.
@@ -2,11 +2,22 @@ import type { ComponentSystemWorld, QueryDelta } from '../component-system';
2
2
  interface InitMessage {
3
3
  type: 'init';
4
4
  }
5
+ interface InitCompleteMessage {
6
+ type: 'init-complete';
7
+ }
8
+ interface LoadMessage<D = unknown> {
9
+ type: 'load';
10
+ data?: D;
11
+ }
5
12
  interface LoadedMessage {
6
13
  type: 'loaded';
7
14
  }
15
+ interface ResetMessage {
16
+ type: 'reset';
17
+ }
8
18
  interface RunUpdateMessage<W extends ComponentSystemWorld = ComponentSystemWorld> {
9
19
  type: 'run';
20
+ generation: number;
10
21
  world: W;
11
22
  entities: QueryDelta;
12
23
  queries: {
@@ -23,10 +34,11 @@ export type SystemEvents = {
23
34
  };
24
35
  interface EntityEventsMessage {
25
36
  type: 'run-complete';
37
+ generation: number;
26
38
  runTime: number;
27
39
  events: Array<EntityEvent>;
28
40
  systemEvents: SystemEvents;
29
41
  created: Array<Record<string, unknown>>;
30
42
  }
31
- type ComponentWorkerMessage<W extends ComponentSystemWorld = ComponentSystemWorld> = InitMessage | LoadedMessage | RunUpdateMessage<W> | EntityEventsMessage;
43
+ type ComponentWorkerMessage<W extends ComponentSystemWorld = ComponentSystemWorld, D = unknown> = InitMessage | InitCompleteMessage | LoadMessage<D> | LoadedMessage | ResetMessage | RunUpdateMessage<W> | EntityEventsMessage;
32
44
  export default ComponentWorkerMessage;
@@ -5,4 +5,4 @@ export interface ComponentWorkerScope {
5
5
  onmessage: ((e: MessageEvent) => void) | null;
6
6
  postMessage(message: ComponentWorkerMessage): void;
7
7
  }
8
- export default function createComponentWorker<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld>(scope: ComponentWorkerScope, updateFunction: EntityUpdateFunction<C, T, W>): void;
8
+ export default function createComponentWorker<C extends ComponentMap, T extends EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld, D = unknown>(scope: ComponentWorkerScope, updateFunction: EntityUpdateFunction<C, T, W, D>): void;
package/dist/worker.d.ts CHANGED
@@ -5,4 +5,4 @@ export { default as killEntityWorker } from './actions/kill-entity-worker';
5
5
  export { DEAD_INDEX } from './entity-component';
6
6
  export type { default as ComponentWorkerMessage } from './systems/workers/component-worker-message';
7
7
  export type { EntityEvent, SystemEvents } from './systems/workers/component-worker-message';
8
- export type { ComponentSystemWorld, ComponentSystemCallbacks, CreateEntityConfig, EntityUpdateComponents, EntityQueryComponents, EntityUpdateFunction, EntityUpdatePreRunFunction, EntityRemovedFunction, UpdateEntityConfigObject, } from './systems/component-system';
8
+ export type { ComponentSystemWorld, ComponentSystemCallbacks, CreateEntityConfig, EntityUpdateComponents, EntityQueryComponents, EntityUpdateFunction, EntityUpdateInitFunction, EntityUpdatePreRunFunction, EntityRemovedFunction, UpdateEntityConfigObject, } from './systems/component-system';
package/dist/worker.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as e, n as t, r as n, t as r } from "./create-component-worker-Bd1BTUkq.js";
1
+ import { i as e, n as t, r as n, t as r } from "./create-component-worker-vbX3vEia.js";
2
2
  export { e as DEAD_INDEX, r as createComponentWorker, t as createEntityWorker, n as killEntityWorker };
package/dist/world.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from 'eventemitter3';
2
2
  import MemoryHeap from '@daneren2005/shared-memory-objects/memory-heap';
3
+ import MemoryComponent from './memory-component';
3
4
  import type BaseEntity from './entity';
4
5
  import type System from './systems/system';
5
6
  import type { ComponentDefinitionMap, ComponentMap, ComponentsOf, EntityConfigOf, RegisteredComponentDefinition, RegisteredComponentRegistry } from './component-definition';
@@ -28,14 +29,27 @@ export default class BaseWorld<R extends ComponentDefinitionMap = ComponentDefin
28
29
  timeScale: number;
29
30
  paused: boolean;
30
31
  destroyed: boolean;
32
+ pristine: boolean;
33
+ private activeFreeBuffer;
34
+ private nextFreeBuffer;
31
35
  constructor(registry: R, options?: WorldOptions<C, Cfg>);
32
36
  init(): Promise<void>;
37
+ private finishLoadingSystems;
33
38
  addEntity(entity: BaseEntity<C, Cfg>, created?: boolean): BaseEntity<C, Cfg>;
34
39
  loadEntity(config: Cfg, created?: boolean): BaseEntity<C, Cfg>;
35
40
  load(config: WorldConfig<Cfg>): void;
41
+ clear(): Promise<void>;
36
42
  removeEntity(entity: BaseEntity<C, Cfg>): void;
37
43
  onEntityDied(entity: BaseEntity<C, Cfg>): void;
38
44
  getEntityByEid(eid: number): BaseEntity<C, Cfg> | undefined;
45
+ deferComponentMemoryFree(memoryComponent: MemoryComponent, index: number, free?: () => void): void;
46
+ notifySystemRunCompleted(system: System<C>): void;
47
+ private processFreeBuffers;
48
+ private promoteNextFreeBuffer;
49
+ private consolidateFreeBuffersForReload;
50
+ private freeAllPendingBuffers;
51
+ private performFrees;
52
+ private checkFreeBufferTimeout;
39
53
  addSystem<T extends System<C>>(system: T): T;
40
54
  addSystemIfNotExists(system: System<C>): void;
41
55
  removeSystem(name: string): void;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@daneren2005/shared-memory-ecs",
3
3
  "description": "A small, reusable Entity/Component/System core backed by shared memory.",
4
4
  "author": "Scott Jackson",
5
- "version": "1.2.2",
5
+ "version": "1.4.0",
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "repository": {
@@ -34,6 +34,7 @@
34
34
  ],
35
35
  "scripts": {
36
36
  "build": "vite build && tsc -p tsconfig.build.json --emitDeclarationOnly",
37
+ "build:game": "npm run build && node scripts/build-game.mjs",
37
38
  "type-check": "tsc --noEmit",
38
39
  "lint": "cross-env NODE_ENV=production oxlint .",
39
40
  "lint:fix": "cross-env NODE_ENV=production oxlint --fix .",
@@ -1 +0,0 @@
1
- {"version":3,"file":"create-component-worker-Bd1BTUkq.js","names":[],"sources":["../src/systems/workers/apply-query-delta.ts","../src/entity-component.ts","../src/actions/kill-entity-worker.ts","../src/actions/create-entity-worker.ts","../src/systems/workers/create-component-worker.ts"],"sourcesContent":["import type { EntityUpdateComponents, QueryDelta, UpdateEntityConfigObject } from '../component-system';\n\n// A worker keeps one persistent list per query and applies the delta each run carries: drop the entities that\n// left, then upsert the ones that joined (or whose component blocks changed). Because the main thread only\n// sends changes, a steady-state run - where membership is unchanged - carries empty arrays and this returns the\n// existing list untouched with no allocation, which is the whole point of the delta protocol: we stop\n// re-transmitting (and re-cloning) every entity id every single frame.\n//\n// The list is returned (rather than mutated in place) because removals rebuild it via filter; callers must store\n// the returned reference back as their persistent list.\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// Map existing members so a re-added entity (its component set changed) replaces its entry in place\n\t\t// instead of being duplicated; genuinely new entities are appended.\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 { ComponentDefinition } from './component-definition';\n\n// The one component every entity is required to have. `dead` and `isStatic` are boolean flags stored in\n// the shared-memory block so a worker that pulls the `entity` component into its query can read them too.\n// `type` is a plain string that names the EntityFactory template this entity was built from; it lives off\n// to the side (not in shared memory), so - unlike `dead`/`isStatic` - it is NOT visible to workers.\n// Games that need a stable string id (or any other per-entity data) add their own component for it.\nexport interface EntityComponent {\n\tindex: number\n\ttype: string\n\tdead: boolean\n\tisStatic: boolean\n}\n\n// The defining config for the entity component. `type` is required - every entity is created from a factory\n// template, named by its type - while `isStatic` is an optional flag supplied up front.\nexport interface EntityComponentConfig {\n\ttype: string\n\tisStatic?: boolean\n}\n\n// Runtime-derived state plus `type`: `dead` is only meaningful once the entity is alive, and `type` is kept\n// so a save can be re-expanded through the factory. `isStatic` is intentionally absent - it comes back from\n// the type's template config, not the save.\nexport interface EntityComponentSerialization {\n\ttype?: string\n\tdead?: boolean\n}\n\nexport const DEAD_INDEX = 0;\nexport const STATIC_INDEX = 1;\n\nexport const entityDefinition: ComponentDefinition<EntityComponent, Uint32Array, EntityComponentConfig, EntityComponentSerialization> = {\n\ttype: Uint32Array,\n\tsize: 2,\n\t// The entity component is always loaded by BaseEntity's constructor, so this never actually gates\n\t// loading; it documents the defining config props and keeps it consistent with game components.\n\tloadProperties: ['type', 'isStatic'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([config.dead ? 1 : 0, config.isStatic ? 1 : 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\t// Plain (non-memory) property, so it is main-thread only and not readable from workers.\n\t\t\ttype: config.type,\n\t\t\tget dead() {\n\t\t\t\treturn block[DEAD_INDEX] === 1;\n\t\t\t},\n\t\t\tset dead(value: boolean) {\n\t\t\t\tblock[DEAD_INDEX] = value ? 1 : 0;\n\t\t\t},\n\t\t\tget isStatic() {\n\t\t\t\treturn block[STATIC_INDEX] === 1;\n\t\t\t},\n\t\t\tset isStatic(value: boolean) {\n\t\t\t\tblock[STATIC_INDEX] = value ? 1 : 0;\n\t\t\t},\n\t\t};\n\t},\n\tsave(component) {\n\t\t// `type` + `dead` are serialized; `isStatic` (and the rest of the template) come back from the type's\n\t\t// config on reload, so they are deliberately left out.\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 { ComponentSystemCallbacks, EntityUpdateComponents } from '../systems/component-system';\nimport { DEAD_INDEX } from '../entity-component';\n\n// The worker-thread counterpart of killEntity. Called from inside a component system's update function,\n// it flags the entity dead directly in its shared-memory block and reports the death back to the main\n// thread through the callbacks so the world can run the same cleanup killEntity would. The entity\n// component must be part of the system's query for its block to be available here.\nexport default function killEntityWorker(entityId: number, components: EntityUpdateComponents, callbacks: ComponentSystemCallbacks): 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 { ComponentSystemCallbacks, CreateEntityConfig } from '../systems/component-system';\n\n// The worker-thread counterpart of loadEntity. Called from inside a component system's update function, it\n// can't build the entity itself (eid generation, shared-memory allocation, and factory expansion all live on\n// the main thread), so it buffers the flat config through the callbacks. The main thread creates the entity\n// via world.loadEntity once the run completes, meaning it first exists on the following frame.\nexport default function createEntityWorker(config: CreateEntityConfig, callbacks: ComponentSystemCallbacks): void {\n\tcallbacks.createEntity(config);\n}\n","import type ComponentWorkerMessage from './component-worker-message';\nimport type { EntityEvent, SystemEvents } from './component-worker-message';\nimport type { ComponentMap } from '../../component-definition';\nimport type { ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents, EntityUpdateFunction, QueryDelta, UpdateEntityConfigObject } from '../component-system';\nimport { applyQueryDelta } from './apply-query-delta';\n\n// The slice of the Web Worker global scope createComponentWorker actually touches. Worker files pass the\n// real `self` (e.g. `createComponentWorker(self, fn)`); passing it explicitly also lets test runners that\n// inject `self` as a module local rather than a true global - like @vitest/web-worker - drive the worker.\nexport interface ComponentWorkerScope {\n\tonmessage: ((e: MessageEvent) => void) | null\n\tpostMessage(message: ComponentWorkerMessage): void\n}\n\n// Entry point a game's worker file calls with its update function. It wires up `scope.onmessage`,\n// caches component blocks by entity id (so subsequent runs only need the id), and posts results back.\nexport default function createComponentWorker<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends ComponentSystemWorld = ComponentSystemWorld,\n>(scope: ComponentWorkerScope, updateFunction: EntityUpdateFunction<C, T, W>) {\n\t// The worker's persistent iteration lists. The main thread now sends only membership changes, so these are\n\t// carried across runs and mutated by the deltas each run brings (see applyQueryDelta).\n\tlet entities: Array<UpdateEntityConfigObject<T>> = [];\n\tconst queryEntities: { [key: string]: Array<UpdateEntityConfigObject<T>> } = {};\n\n\tscope.onmessage = function(e) {\n\t\tconst message = e.data as ComponentWorkerMessage<W>;\n\n\t\tif(message.type === 'init') {\n\t\t\tpostMessageTyped(scope, {\n\t\t\t\ttype: 'loaded',\n\t\t\t});\n\t\t} else if(message.type === 'run') {\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<Record<string, unknown>> = [];\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: ComponentSystemCallbacks<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// The list per event name is only made the first time that event comes up in a run, so a system\n\t\t\t\t\t// that has an event it rarely reports does not pay an empty array for it every run.\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(config: Record<string, unknown>) {\n\t\t\t\t\tcreatedEntities.push(config);\n\t\t\t\t},\n\t\t\t};\n\t\t\tif(updateFunction.preRun) {\n\t\t\t\tupdateFunction.preRun(message.world, entities, queries, callbacks);\n\t\t\t}\n\n\t\t\tentities.forEach(entity => {\n\t\t\t\tupdateFunction(message.world, entity.entityId, entity.components, queries, callbacks);\n\t\t\t});\n\n\t\t\t// applyQueryDelta already dropped the removed entities from the persistent lists above; the hook just\n\t\t\t// lets the update function react to entities that left the system's main query.\n\t\t\tif(updateFunction.entityRemoved) {\n\t\t\t\tmessage.entities.removed.forEach(entityId => {\n\t\t\t\t\tupdateFunction.entityRemoved!(message.world, entityId, callbacks);\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\trunTime,\n\t\t\t\tevents: entityEvents,\n\t\t\t\tsystemEvents,\n\t\t\t\tcreated: createdEntities,\n\t\t\t});\n\t\t}\n\t};\n}\n\nfunction postMessageTyped(scope: ComponentWorkerScope, message: ComponentWorkerMessage) {\n\tscope.postMessage(message);\n}\n"],"mappings":"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"}