@daneren2005/shared-memory-ecs 1.0.1 → 1.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/README.md CHANGED
@@ -9,14 +9,21 @@ fog of war, sub-classed entities, required components, etc).
9
9
 
10
10
  - **Component** – a plain object with an `index` (its block inside a shared-memory pool) plus getters/
11
11
  setters over that memory. Games decide what components exist.
12
- - **`ComponentDefinition`** – describes a component: its typed array `type`, block `size`, a `load(entity, memory, config)`
13
- and an optional `save(component)`.
12
+ - **`ComponentDefinition`** – describes a component: its typed array `type`, block `size`, the config keys
13
+ that trigger loading (`loadProperties`), a `load(entity, memory, config)` and an optional `save(component)`.
14
+ A component's data splits into `Config` (defining props supplied up front, e.g. `maxHealth`) and
15
+ `Serialization` (runtime-derived state, e.g. current `health`); `load` sees `Config & Serialization` while
16
+ `save` returns only the `Serialization` slice.
14
17
  - **`ComponentRegistry<C>`** – the map of all component definitions for a game.
15
- - **`BaseWorld<C>`** – builds one `MemoryComponent` per registered component and runs systems. It is
16
- generic over your component map `C`, so `world.components`, `entity.components`, `setComponent`, etc.
17
- are fully typed.
18
+ - **`EntityFactory<C>`** – maps an entity `type` name to a base (template) config. Loading an entity layers
19
+ the caller's config over its type's template, so shared static data lives in one place and a save only
20
+ needs the `type` plus the entity's serialization. `BaseWorld#loadEntity` always goes through the factory.
21
+ - **`BaseWorld<C>`** – builds one `MemoryComponent` per registered component (attached to that component's
22
+ definition as `world.registry[name].memoryComponent`) and runs systems. It is generic over your component
23
+ map `C`, so `world.registry`, `entity.components`, `setComponent`, etc. are fully typed.
18
24
  - **`BaseEntity<C>`** – an `eid`, an optional `id`, and a bag of memory-backed components. It has no
19
- direct property accessors and only loads/saves component data.
25
+ direct property accessors and only loads/saves component data. Every entity has an `entity` component
26
+ whose `type` (a plain, worker-invisible string) records the factory template it was built from.
20
27
  - **Systems** – `System`, `IterableSystem`, `EntitySystem` (main-thread iteration over entities with a
21
28
  given set of components) and `ComponentSystem` (runs an update function over raw memory blocks,
22
29
  off-thread when Web Workers + `SharedArrayBuffer` are available).
@@ -35,10 +42,15 @@ interface HealthComponent {
35
42
  const HEALTH_INDEX = 0;
36
43
  const HEALTH_MAX_INDEX = 1;
37
44
 
38
- const healthDefinition: ComponentDefinition<HealthComponent, Int32Array> = {
45
+ // `maxHealth` is the defining Config prop; `health` is optional, runtime-derived Serialization. `load`
46
+ // and `save` both see the combined `{ maxHealth: number, health?: number }`.
47
+ const healthDefinition: ComponentDefinition<HealthComponent, Int32Array, { maxHealth: number }, { health?: number }> = {
39
48
  type: Int32Array,
40
49
  size: 2,
41
- load(entity, memoryComponent, config: { health?: number, maxHealth: number }) {
50
+ // Configs are flat and shared: this component loads whenever `maxHealth` is present, then reads what it
51
+ // needs off the same config object. Only Config props may appear here.
52
+ loadProperties: ['maxHealth'],
53
+ load(entity, memoryComponent, config) {
42
54
  const index = memoryComponent.create([config.health ?? config.maxHealth, config.maxHealth]);
43
55
  const memory = memoryComponent.getBlock(index);
44
56
 
@@ -50,7 +62,8 @@ const healthDefinition: ComponentDefinition<HealthComponent, Int32Array> = {
50
62
  };
51
63
  },
52
64
  save(component) {
53
- return { maxHealth: component.maxHealth, health: component.health };
65
+ // save returns only Serialization; maxHealth is Config and comes back from the template on reload.
66
+ return { health: component.health };
54
67
  }
55
68
  };
56
69
 
@@ -69,19 +82,54 @@ import { BaseWorld } from '@daneren2005/shared-memory-ecs';
69
82
 
70
83
  const world = new BaseWorld<Components>(registry);
71
84
 
72
- const entity = world.loadEntity({ health: { maxHealth: 100 } });
85
+ const entity = world.loadEntity({ maxHealth: 100 });
73
86
  entity.components.health!.health -= 10;
74
87
 
75
- console.log(entity.save()); // { health: { maxHealth: 100, health: 90 } }
88
+ console.log(entity.save()); // { health: 90 }
89
+ ```
90
+
91
+ ### Entity types via the factory
92
+
93
+ Register per-type base configs, then load entities by `type`. The template supplies the defining config, so
94
+ only the `type` and runtime serialization need to be saved:
95
+
96
+ ```ts
97
+ import { BaseWorld, EntityFactory } from '@daneren2005/shared-memory-ecs';
98
+
99
+ const factory = new EntityFactory<Components>({
100
+ goblin: { maxHealth: 20 },
101
+ });
102
+ const world = new BaseWorld<Components>(registry, { factory });
103
+
104
+ const goblin = world.loadEntity({ type: 'goblin', health: 10 });
105
+ console.log(goblin.components.health!.maxHealth); // 20 (from the template)
106
+ console.log(goblin.save()); // { type: 'goblin', health: 10 } - no templated maxHealth
107
+
108
+ // A save re-expands back through the factory:
109
+ world.loadEntity(goblin.save());
76
110
  ```
77
111
 
78
112
  ## ComponentSystem workers
79
113
 
80
114
  `ComponentSystem` needs a `getWorker()` that returns a real `Worker`, and an `updateFunction`. Your
81
- worker entry file calls `createComponentWorker(updateFunction)`. When Web Workers or
115
+ worker entry file calls `createComponentWorker(self, updateFunction)`. When Web Workers or
82
116
  `SharedArrayBuffer` are unavailable it transparently falls back to running the same update function on
83
117
  the main thread. Attach any extra per-run data (the equivalent of the old faction/fog-of-war fields)
84
- by overriding `addDataToWorld(world)`.
118
+ by overriding `addDataToWorld(world)`. Declare its shape with the `W` type parameter (an interface
119
+ extending `ComponentSystemWorld`) so both `addDataToWorld` and the `updateFunction` see it typed:
120
+
121
+ ```ts
122
+ interface DamageWorld extends ComponentSystemWorld {
123
+ damage: number
124
+ }
125
+
126
+ const damageUpdate: EntityUpdateFunction<Components, { health: Int32Array }, DamageWorld> =
127
+ (world, entityId, components) => { components.health[0] -= world.damage; };
128
+
129
+ class DamageSystem extends ComponentSystem<Components, { health: Int32Array }, DamageWorld> {
130
+ addDataToWorld(world: DamageWorld) { world.damage = 5; }
131
+ }
132
+ ```
85
133
 
86
134
  ## Building
87
135
 
@@ -0,0 +1,2 @@
1
+ import type { ComponentSystemCallbacks, CreateEntityConfig } from '../systems/component-system';
2
+ export default function createEntityWorker(config: CreateEntityConfig, callbacks: ComponentSystemCallbacks): void;
@@ -0,0 +1,2 @@
1
+ import type { ComponentSystemCallbacks, EntityUpdateComponents } from '../systems/component-system';
2
+ export default function killEntityWorker(entityId: number, components: EntityUpdateComponents, callbacks: ComponentSystemCallbacks): void;
@@ -0,0 +1,2 @@
1
+ import type BaseEntity from '../entity';
2
+ export default function killEntity(entity: BaseEntity): void;
@@ -0,0 +1,38 @@
1
+ import type { TypedArrayConstructor } from '@daneren2005/shared-memory-objects';
2
+ import type MemoryComponent from './memory-component';
3
+ import type { ComponentTypedArray } from './memory-component';
4
+ import type BaseEntity from './entity';
5
+ import type { EntityComponent, EntityComponentConfig, EntityComponentSerialization } from './entity-component';
6
+ export interface BaseComponent {
7
+ index: number;
8
+ }
9
+ export type ComponentMap = Record<string, BaseComponent>;
10
+ export interface ComponentDefinition<Component extends BaseComponent, T extends ComponentTypedArray = ComponentTypedArray, Config = any, Serialization = object> {
11
+ type: TypedArrayConstructor<T>;
12
+ size: number;
13
+ loadProperties: Array<keyof Config & string>;
14
+ loadInFinishLoading?: boolean;
15
+ load(entity: BaseEntity, memory: MemoryComponent<T>, config: Config & Serialization): Component;
16
+ save?(component: Component): Serialization;
17
+ }
18
+ export type RegisteredComponentDefinition<Component extends BaseComponent, T extends ComponentTypedArray = ComponentTypedArray, Config = any, Serialization = object> = ComponentDefinition<Component, T, Config, Serialization> & {
19
+ memoryComponent: MemoryComponent<T>;
20
+ };
21
+ export type ComponentRegistry<C extends ComponentMap> = {
22
+ [K in keyof C]: ComponentDefinition<C[K]>;
23
+ };
24
+ export type RegisteredComponentRegistry<C extends ComponentMap> = {
25
+ [K in keyof C]: RegisteredComponentDefinition<C[K]>;
26
+ };
27
+ export type ComponentDefinitionMap = Record<string, ComponentDefinition<BaseComponent>>;
28
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
29
+ type DefinitionConfig<D> = D extends ComponentDefinition<BaseComponent, ComponentTypedArray, infer Config, infer Serialization> ? Config & Serialization : never;
30
+ export type ComponentsOf<R extends ComponentDefinitionMap> = {
31
+ [K in keyof R]: ReturnType<R[K]['load']>;
32
+ } & {
33
+ entity: EntityComponent;
34
+ };
35
+ export type EntityConfigOf<R extends ComponentDefinitionMap> = Partial<UnionToIntersection<{
36
+ [K in keyof R]: DefinitionConfig<R[K]>;
37
+ }[keyof R]> & EntityComponentConfig & EntityComponentSerialization>;
38
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { ComponentDefinition } from './component-definition';
2
+ export interface EntityComponent {
3
+ index: number;
4
+ type: string;
5
+ dead: boolean;
6
+ isStatic: boolean;
7
+ }
8
+ export interface EntityComponentConfig {
9
+ type: string;
10
+ isStatic?: boolean;
11
+ }
12
+ export interface EntityComponentSerialization {
13
+ type?: string;
14
+ dead?: boolean;
15
+ }
16
+ export declare const DEAD_INDEX = 0;
17
+ export declare const STATIC_INDEX = 1;
18
+ export declare const entityDefinition: ComponentDefinition<EntityComponent, Uint32Array, EntityComponentConfig, EntityComponentSerialization>;
@@ -0,0 +1,16 @@
1
+ import BaseEntity from './entity';
2
+ import type BaseWorld from './world';
3
+ import type { ComponentDefinitionMap, ComponentMap } from './component-definition';
4
+ export default class EntityFactory<C extends ComponentMap = ComponentMap, Cfg = any> {
5
+ world: BaseWorld<ComponentDefinitionMap, C, Cfg>;
6
+ configs: {
7
+ [type: string]: Cfg;
8
+ };
9
+ constructor(configs?: {
10
+ [type: string]: Cfg;
11
+ });
12
+ register(type: string, config: Cfg): void;
13
+ getConfig(config: Cfg): Cfg;
14
+ loadEntity(config: Cfg, created?: boolean): BaseEntity<C, Cfg>;
15
+ protected createEntity(config: Cfg): BaseEntity<C, Cfg>;
16
+ }
@@ -0,0 +1,26 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import type BaseWorld from './world';
3
+ import type { ComponentDefinitionMap, ComponentMap } from './component-definition';
4
+ import type { EntityComponent } from './entity-component';
5
+ export default class BaseEntity<C extends ComponentMap = ComponentMap, Cfg = any> extends EventEmitter {
6
+ static eidCounter: number;
7
+ readonly eid: number;
8
+ config?: Cfg;
9
+ world: BaseWorld<ComponentDefinitionMap, C, Cfg>;
10
+ components: Partial<C> & {
11
+ entity: EntityComponent;
12
+ };
13
+ constructor(world: BaseWorld<ComponentDefinitionMap, C, Cfg>, config?: Cfg);
14
+ loadComponent<K extends keyof C>(name: K, config: any, emitAdded?: boolean): C[K];
15
+ removeComponent<K extends keyof C>(name: K): void;
16
+ setComponent<K extends keyof C, P extends keyof C[K]>(componentName: K, prop: P, value: C[K][P]): void;
17
+ /**
18
+ * NOTE: Does not emit component-property-updated!
19
+ */
20
+ setComponentBulk<K extends keyof C>(componentName: K, values: Partial<C[K]>): void;
21
+ deleteComponent<K extends keyof C>(componentName: K, prop: keyof C[K]): void;
22
+ deleteAllComponentMemory(): void;
23
+ load(config: Cfg): void;
24
+ save(): Cfg;
25
+ finishLoading(): void;
26
+ }
@@ -0,0 +1,24 @@
1
+ export { default as BaseWorld } from './world';
2
+ export type { WorldOptions, WorldConfig } from './world';
3
+ export { default as BaseEntity } from './entity';
4
+ export { default as EntityFactory } from './entity-factory';
5
+ export { entityDefinition, DEAD_INDEX, STATIC_INDEX } from './entity-component';
6
+ export type { EntityComponent, EntityComponentConfig, EntityComponentSerialization, } from './entity-component';
7
+ export { default as killEntity } from './actions/kill-entity';
8
+ export { default as killEntityWorker } from './actions/kill-entity-worker';
9
+ export { default as createEntityWorker } from './actions/create-entity-worker';
10
+ export { default as MemoryComponent } from './memory-component';
11
+ export type { ComponentTypedArray } from './memory-component';
12
+ export type { BaseComponent, ComponentMap, ComponentDefinition, ComponentDefinitionMap, ComponentRegistry, ComponentsOf, EntityConfigOf, RegisteredComponentDefinition, RegisteredComponentRegistry, } from './component-definition';
13
+ export { default as System } from './systems/system';
14
+ export type { SystemConfig } from './systems/system';
15
+ export { default as IterableSystem } from './systems/iterable-system';
16
+ export type { IterableSystemConfig } from './systems/iterable-system';
17
+ export { default as EntitySystem } from './systems/entity-system';
18
+ export type { EntitySystemConfig } from './systems/entity-system';
19
+ export { default as ComponentSystem } from './systems/component-system';
20
+ export type { ComponentSystemConfig, ComponentSystemQuery, ComponentSystemWorld, ComponentSystemCallbacks, CreateEntityConfig, EntityUpdateComponents, EntityQueryComponents, EntityUpdateFunction, EntityUpdatePreRunFunction, EntityRemovedFunction, UpdateEntityConfig, UpdateEntityConfigObject, } from './systems/component-system';
21
+ export { default as WebWorker } from './systems/workers/web-worker';
22
+ export { default as ComponentWebWorker } from './systems/workers/component-web-worker';
23
+ export { default as createComponentWorker } from './systems/workers/create-component-worker';
24
+ export type { default as ComponentWorkerMessage } from './systems/workers/component-worker-message';