@daneren2005/shared-memory-ecs 1.0.1 → 1.1.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 +61 -13
- package/dist/actions/create-entity-worker.d.ts +2 -0
- package/dist/actions/kill-entity-worker.d.ts +2 -0
- package/dist/actions/kill-entity.d.ts +2 -0
- package/dist/component-definition.d.ts +38 -0
- package/dist/entity-component.d.ts +18 -0
- package/dist/entity-factory.d.ts +16 -0
- package/dist/entity.d.ts +26 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +443 -184
- package/dist/index.js.map +1 -1
- package/dist/memory-component.d.ts +15 -0
- package/dist/systems/component-system.d.ts +85 -0
- package/dist/systems/entity-system.d.ts +21 -0
- package/dist/systems/iterable-system.d.ts +21 -0
- package/dist/systems/system.d.ts +22 -0
- package/dist/systems/workers/apply-query-delta.d.ts +2 -0
- package/dist/systems/workers/component-web-worker.d.ts +12 -0
- package/dist/systems/workers/component-worker-message.d.ts +27 -0
- package/dist/systems/workers/create-component-worker.d.ts +8 -0
- package/dist/systems/workers/web-worker.d.ts +4 -0
- package/dist/world.d.ts +54 -0
- package/package.json +7 -4
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`,
|
|
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
|
-
- **`
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
85
|
+
const entity = world.loadEntity({ maxHealth: 100 });
|
|
73
86
|
entity.components.health!.health -= 10;
|
|
74
87
|
|
|
75
|
-
console.log(entity.save()); // { health:
|
|
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,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
|
+
}
|
package/dist/entity.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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';
|