@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.
- package/README.md +116 -4
- package/dist/component-definition.d.ts +1 -0
- package/dist/{create-component-worker-Bd1BTUkq.js → create-component-worker-vbX3vEia.js} +35 -26
- package/dist/create-component-worker-vbX3vEia.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +169 -44
- package/dist/index.js.map +1 -1
- package/dist/systems/component-system.d.ts +19 -8
- package/dist/systems/iterable-system.d.ts +2 -0
- package/dist/systems/system.d.ts +4 -1
- package/dist/systems/workers/component-web-worker.d.ts +5 -4
- package/dist/systems/workers/component-worker-message.d.ts +13 -1
- package/dist/systems/workers/create-component-worker.d.ts +1 -1
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +1 -1
- package/dist/world.d.ts +14 -0
- package/package.json +2 -1
- package/dist/create-component-worker-Bd1BTUkq.js.map +0 -1
package/README.md
CHANGED
|
@@ -10,10 +10,11 @@ fog of war, sub-classed entities, required components, etc).
|
|
|
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
12
|
- **`ComponentDefinition`** – describes a component: its typed array `type`, block `size`, the config keys
|
|
13
|
-
that trigger loading (`loadProperties`), a `load(entity, memory, config)
|
|
14
|
-
A component's data splits into `Config` (defining props supplied up
|
|
15
|
-
`Serialization` (runtime-derived state, e.g. current `health`); `load` sees
|
|
16
|
-
`save` returns only the `Serialization` slice.
|
|
13
|
+
that trigger loading (`loadProperties`), a `load(entity, memory, config)`, an optional `save(component)`,
|
|
14
|
+
and an optional `free(component)`. A component's data splits into `Config` (defining props supplied up
|
|
15
|
+
front, e.g. `maxHealth`) and `Serialization` (runtime-derived state, e.g. current `health`); `load` sees
|
|
16
|
+
`Config & Serialization` while `save` returns only the `Serialization` slice. `free` runs when the
|
|
17
|
+
component is torn down (see [Freeing extra resources](#freeing-extra-resources)).
|
|
17
18
|
- **`ComponentRegistry<C>`** – the map of all component definitions for a game.
|
|
18
19
|
- **`EntityFactory<C>`** – maps an entity `type` name to a base (template) config. Loading an entity layers
|
|
19
20
|
the caller's config over its type's template, so shared static data lives in one place and a save only
|
|
@@ -190,6 +191,67 @@ class DamageSystem extends ComponentSystem<Components, { health: Int32Array }, D
|
|
|
190
191
|
}
|
|
191
192
|
```
|
|
192
193
|
|
|
194
|
+
### Update-function hooks: `init` and `preRun`
|
|
195
|
+
|
|
196
|
+
`addDataToWorld` runs on the main thread and re-sends its data every run. When the data instead needs to
|
|
197
|
+
*live in the worker* — computed once, or too big to ship each frame — attach hooks to the update function
|
|
198
|
+
itself. Both are optional properties on the `EntityUpdateFunction`.
|
|
199
|
+
|
|
200
|
+
**`init`** runs on `system.finishLoading()` — once at startup (`world.init()` calls it), and again on each
|
|
201
|
+
`world.load()` so a reused world re-seeds its workers. It receives whatever the system's `getInitData()` returned
|
|
202
|
+
(structured-cloned across the boundary) and returns a `Partial<W>` that is merged onto `world` on every
|
|
203
|
+
subsequent run. Use it for state that must be seeded from the main thread but then persist inside the worker
|
|
204
|
+
— a seeded RNG, a lookup table, a config object — without paying to re-send it each frame. Type the init
|
|
205
|
+
data with the `D` type parameter (the fourth on `EntityUpdateFunction` / `ComponentSystem`) so `getInitData`
|
|
206
|
+
and the `init` hook agree on its shape; it defaults to `unknown` when a system has no init data:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
interface DamageWorld extends ComponentSystemWorld {
|
|
210
|
+
damage: number
|
|
211
|
+
}
|
|
212
|
+
interface DamageInitData {
|
|
213
|
+
baseDamage: number
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const damageUpdate: EntityUpdateFunction<Components, { health: Int32Array }, DamageWorld, DamageInitData> =
|
|
217
|
+
(world, entityId, components) => { components.health[0] -= world.damage; };
|
|
218
|
+
|
|
219
|
+
// Runs once in the worker; its return is merged onto `world` before every run. `data` is typed as
|
|
220
|
+
// `DamageInitData | undefined` (undefined when the system supplies no getInitData).
|
|
221
|
+
damageUpdate.init = (data) => ({ damage: data?.baseDamage ?? 1 });
|
|
222
|
+
|
|
223
|
+
class DamageSystem extends ComponentSystem<Components, { health: Int32Array }, DamageWorld, DamageInitData> {
|
|
224
|
+
constructor(world: BaseWorld<Components>) {
|
|
225
|
+
super(world, {
|
|
226
|
+
name: 'DamageSystem',
|
|
227
|
+
required: ['health'],
|
|
228
|
+
updateFunction: damageUpdate,
|
|
229
|
+
getWorker: () => new Worker(/* your worker entry */),
|
|
230
|
+
// Runs on the main thread; its result is structured-cloned to the worker's `init`.
|
|
231
|
+
getInitData: () => ({ baseDamage: 5 }),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Unlike `addDataToWorld` (an overridable method on the system), `getInitData` is a config option — it lives in
|
|
238
|
+
the options passed to the `ComponentSystem` constructor, so a system used without a subclass can supply it
|
|
239
|
+
there directly.
|
|
240
|
+
|
|
241
|
+
**`preRun`** runs once per run, before any entity is updated, with the run's `world`, the full entity list,
|
|
242
|
+
the query results, and the same `callbacks`. Use it for setup that spans the whole batch — seeding a
|
|
243
|
+
spatial index, resetting an accumulator, or emitting a run-level event — that would be wasteful or wrong to
|
|
244
|
+
repeat inside the per-entity loop:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
damageUpdate.preRun = (world, entities, queries, callbacks) => {
|
|
248
|
+
// Runs before the per-entity pass; `entities` is everything this run will touch.
|
|
249
|
+
};
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
(An `entityRemoved(world, entityId, callbacks)` hook completes the set — it fires once per entity that left
|
|
253
|
+
the system this run, so a worker can release any per-entity state it was holding.)
|
|
254
|
+
|
|
193
255
|
### Importing in workers
|
|
194
256
|
|
|
195
257
|
Each worker entry file is bundled on its own, and a single-entry bundle cannot tree-shake this package's
|
|
@@ -273,6 +335,56 @@ thread already holds the values - sending them along would only pay to copy what
|
|
|
273
335
|
System events are dispatched before the per-entity events of the same run, so an entity a run both moved and
|
|
274
336
|
killed is still in the world when its move is reported.
|
|
275
337
|
|
|
338
|
+
### Freeing component memory safely
|
|
339
|
+
|
|
340
|
+
Component blocks live in a shared pool, so a freed block gets handed straight back out to the next entity that
|
|
341
|
+
needs one. That is a problem across workers: if one system kills an entity, a second reuses its freed block for
|
|
342
|
+
a brand-new entity, and a third is still mid-run over what it thinks is the old entity, the third system writes
|
|
343
|
+
into the new entity's memory.
|
|
344
|
+
|
|
345
|
+
So the library never frees a block the instant it is orphaned. When an entity dies or you call
|
|
346
|
+
`removeComponent`, the block is *deferred* — the component is gone from the entity immediately, but the memory is
|
|
347
|
+
held until every system that could be mid-run over it has finished a run. Only then is the block returned to the
|
|
348
|
+
pool for reuse. This is automatic; there is nothing to call. The one visible effect is that
|
|
349
|
+
`memoryComponent.length` can briefly sit one higher than the number of live components, until the next update
|
|
350
|
+
lets the holding systems finish. If a system stays stuck (never completes a run) for more than ten seconds of
|
|
351
|
+
unscaled time, the world logs a warning naming that system and frees the blocks anyway rather than leak them —
|
|
352
|
+
a stuck system there is a bug worth chasing down.
|
|
353
|
+
|
|
354
|
+
### Freeing extra resources
|
|
355
|
+
|
|
356
|
+
Everything above frees a component's own block. A component that allocates something *else* in `load` —
|
|
357
|
+
another heap structure (a `SharedList`, a `SharedString`) or child entities it owns — needs to release that
|
|
358
|
+
too, and the block-level deferred free won't do it. Give the definition an optional `free(component)`:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
const cargoDefinition: ComponentDefinition<Cargo, Uint32Array, CargoConfig> = {
|
|
362
|
+
type: Uint32Array,
|
|
363
|
+
size: 3,
|
|
364
|
+
loadProperties: ['cargoSpace'],
|
|
365
|
+
load(entity, memory, config) {
|
|
366
|
+
/* allocate a SharedList in the heap, stash its pointer in the block, return accessors */
|
|
367
|
+
},
|
|
368
|
+
free(component) {
|
|
369
|
+
component.items.free(); // release the SharedList + the item entities it holds
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
`free` runs exactly once per component teardown: on `removeComponent`, and on every path that removes an
|
|
375
|
+
entity — `removeEntity`, a `death` (from `killEntity`), a `load` that drops the old batch, and `clear`.
|
|
376
|
+
It is deferred to the same safe point as the component's own block (see above), not fired the instant the
|
|
377
|
+
entity dies — so a system still mid-run over that memory can't see your resource released early. Reload calls
|
|
378
|
+
it without any `death` event, so it is the reliable place to avoid leaks when a world is reused.
|
|
379
|
+
|
|
380
|
+
### Reusing a world: `load` and `clear`
|
|
381
|
+
|
|
382
|
+
A world is meant to be reused rather than rebuilt. `world.load(config)` swaps in a fresh scenario — it removes
|
|
383
|
+
the old entities, clears every system, and loads the new batch (deferred frees from the old contents drain over
|
|
384
|
+
the following updates, as above). `await world.clear()` instead tears the world all the way back down to an
|
|
385
|
+
empty, reusable state: it waits for every system's in-flight worker run to finish so nothing is still reading
|
|
386
|
+
the memory, frees the held blocks immediately, and resolves once the world is ready to load into again.
|
|
387
|
+
|
|
276
388
|
## Measuring performance
|
|
277
389
|
|
|
278
390
|
`PerformanceTiming` watches a world and reports what running it costs. Hand it the world and it hooks itself
|
|
@@ -14,6 +14,7 @@ export interface ComponentDefinition<Component extends BaseComponent, T extends
|
|
|
14
14
|
loadInFinishLoading?: boolean;
|
|
15
15
|
load(entity: BaseEntity, memory: MemoryComponent<T>, config: Config & Serialization): Component;
|
|
16
16
|
save?(component: Component): Serialization;
|
|
17
|
+
free?(component: Component): void;
|
|
17
18
|
}
|
|
18
19
|
export type RegisteredComponentDefinition<Component extends BaseComponent, T extends ComponentTypedArray = ComponentTypedArray, Config = any, Serialization = object> = ComponentDefinition<Component, T, Config, Serialization> & {
|
|
19
20
|
memoryComponent: MemoryComponent<T>;
|
|
@@ -58,21 +58,27 @@ function a(e, t) {
|
|
|
58
58
|
//#endregion
|
|
59
59
|
//#region src/systems/workers/create-component-worker.ts
|
|
60
60
|
function o(t, n) {
|
|
61
|
-
let r = [], i = {};
|
|
62
|
-
t.onmessage = function(
|
|
63
|
-
let
|
|
64
|
-
if (
|
|
65
|
-
else if (
|
|
66
|
-
|
|
67
|
-
r =
|
|
68
|
-
let
|
|
69
|
-
|
|
61
|
+
let r = [], i = {}, a;
|
|
62
|
+
t.onmessage = function(o) {
|
|
63
|
+
let c = o.data;
|
|
64
|
+
if (c.type === "init") s(t, { type: "init-complete" });
|
|
65
|
+
else if (c.type === "load") a = n.init?.(c.data) ?? void 0, s(t, { type: "loaded" });
|
|
66
|
+
else if (c.type === "reset") {
|
|
67
|
+
r = [];
|
|
68
|
+
for (let e of Object.keys(i)) delete i[e];
|
|
69
|
+
a = void 0;
|
|
70
|
+
} else if (c.type === "run") {
|
|
71
|
+
a && Object.assign(c.world, a);
|
|
72
|
+
let o = performance.now(), l = [], u = {}, d = [];
|
|
73
|
+
r = e(r, c.entities);
|
|
74
|
+
let f = {};
|
|
75
|
+
Object.entries(c.queries).forEach(([t, n]) => {
|
|
70
76
|
let r = e(i[t] ?? [], n);
|
|
71
|
-
i[t] = r,
|
|
77
|
+
i[t] = r, f[t] = r;
|
|
72
78
|
});
|
|
73
|
-
let
|
|
79
|
+
let p = {
|
|
74
80
|
entityComponentChanged(e, t, n, r) {
|
|
75
|
-
|
|
81
|
+
l.push({
|
|
76
82
|
entityId: e,
|
|
77
83
|
event: "component-property-updated",
|
|
78
84
|
args: [
|
|
@@ -83,36 +89,39 @@ function o(t, n) {
|
|
|
83
89
|
});
|
|
84
90
|
},
|
|
85
91
|
emitEntityEvent(e, t, ...n) {
|
|
86
|
-
|
|
92
|
+
l.push({
|
|
87
93
|
entityId: e,
|
|
88
94
|
event: t,
|
|
89
95
|
args: n
|
|
90
96
|
});
|
|
91
97
|
},
|
|
92
98
|
emitSystemEvent(e, t) {
|
|
93
|
-
(
|
|
99
|
+
(u[e] ?? (u[e] = [])).push(t);
|
|
94
100
|
},
|
|
95
101
|
entityDied(e) {
|
|
96
|
-
|
|
102
|
+
l.push({
|
|
97
103
|
entityId: e,
|
|
98
104
|
event: "death",
|
|
99
105
|
args: []
|
|
100
106
|
});
|
|
101
107
|
},
|
|
102
108
|
createEntity(e) {
|
|
103
|
-
|
|
109
|
+
d.push(e);
|
|
104
110
|
}
|
|
105
111
|
};
|
|
106
|
-
n.preRun && n.preRun(
|
|
107
|
-
n(
|
|
108
|
-
}), n.entityRemoved &&
|
|
109
|
-
n.entityRemoved(
|
|
110
|
-
})
|
|
112
|
+
n.preRun && n.preRun(c.world, r, f, p), r.forEach((e) => {
|
|
113
|
+
n(c.world, e.entityId, e.components, f, p);
|
|
114
|
+
}), n.entityRemoved && c.entities.removed.forEach((e) => {
|
|
115
|
+
n.entityRemoved(c.world, e, p);
|
|
116
|
+
});
|
|
117
|
+
let m = performance.now() - o;
|
|
118
|
+
s(t, {
|
|
111
119
|
type: "run-complete",
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
generation: c.generation,
|
|
121
|
+
runTime: m,
|
|
122
|
+
events: l,
|
|
123
|
+
systemEvents: u,
|
|
124
|
+
created: d
|
|
116
125
|
});
|
|
117
126
|
}
|
|
118
127
|
};
|
|
@@ -123,4 +132,4 @@ function s(e, t) {
|
|
|
123
132
|
//#endregion
|
|
124
133
|
export { n as a, t as i, a as n, r as o, i as r, e as s, o as t };
|
|
125
134
|
|
|
126
|
-
//# sourceMappingURL=create-component-worker-
|
|
135
|
+
//# sourceMappingURL=create-component-worker-vbX3vEia.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-component-worker-vbX3vEia.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// 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 { ComponentDefinition } from './component-definition';\n\n// dead/isStatic live in the shared block (worker-visible); type is main-thread only.\nexport interface EntityComponent {\n\tindex: number\n\ttype: string\n\tdead: boolean\n\tisStatic: boolean\n}\n\nexport interface EntityComponentConfig {\n\ttype: string\n\tisStatic?: boolean\n}\n\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\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\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\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// 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: 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// Worker-side loadEntity: buffers the config through callbacks. The main thread builds it via world.loadEntity\n// once the run completes, so 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 worker global scope createComponentWorker 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 ComponentWorkerScope {\n\tonmessage: ((e: MessageEvent) => void) | null\n\tpostMessage(message: ComponentWorkerMessage): void\n}\n\nexport default function createComponentWorker<\n\tC extends ComponentMap,\n\tT extends EntityUpdateComponents<C>,\n\tW extends ComponentSystemWorld = ComponentSystemWorld,\n\tD = unknown,\n>(scope: ComponentWorkerScope, updateFunction: EntityUpdateFunction<C, T, W, D>) {\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\n\tscope.onmessage = function(e) {\n\t\tconst message = e.data as ComponentWorkerMessage<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\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 === '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\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(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\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\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});\n\t\t}\n\t};\n}\n\nfunction postMessageTyped(scope: ComponentWorkerScope, message: ComponentWorkerMessage) {\n\tscope.postMessage(message);\n}\n"],"mappings":";AAKA,SAAgB,EACf,GACA,GACqC;CACrC,IAAG,EAAM,QAAQ,QAAQ;EACxB,IAAM,IAAa,IAAI,IAAI,EAAM,OAAO;EACxC,IAAO,EAAK,QAAO,MAAS,CAAC,EAAW,IAAI,EAAM,QAAQ,CAAC;CAC5D;CAEA,IAAG,EAAM,MAAM,QAAQ;EAEtB,IAAM,oBAAa,IAAI,IAAoB;EAC3C,EAAK,SAAS,GAAO,MAAU,EAAW,IAAI,EAAM,UAAU,CAAK,CAAC;EAEpE,KAAI,IAAI,KAAU,EAAM,OAAO;GAC9B,IAAM,IAAW,EAAW,IAAI,EAAO,QAAQ;GAC/C,AAAG,MAAa,KAAA,KAGf,EAAW,IAAI,EAAO,UAAU,EAAK,MAAM,GAC3C,EAAK,KAAK,CAAM,KAHhB,EAAK,KAAY;EAKnB;CACD;CAEA,OAAO;AACR;;;ACXA,IAAa,IAAa,GACb,IAAe,GAEf,IAA2H;CACvI,MAAM;CACN,MAAM;CACN,gBAAgB,CAAC,QAAQ,UAAU;CACnC,KAAK,GAAQ,GAAQ,GAAQ;EAC5B,IAAM,IAAQ,EAAO,OAAO,CAAC,KAAO,MAAc,KAAO,QAAgB,CAAC,GACpE,IAAQ,EAAO,SAAS,CAAK;EAEnC,OAAO;GACN;GACA,MAAM,EAAO;GACb,IAAI,OAAO;IACV,OAAO,EAAA,OAAsB;GAC9B;GACA,IAAI,KAAK,GAAgB;IACxB,EAAA,KAAoB;GACrB;GACA,IAAI,WAAW;IACd,OAAO,EAAA,OAAwB;GAChC;GACA,IAAI,SAAS,GAAgB;IAC5B,EAAA,KAAsB;GACvB;EACD;CACD;CACA,KAAK,GAAW;EACf,IAAM,IAAuC,CAAC;EAQ9C,OAPG,EAAU,SACZ,EAAO,OAAO,EAAU,OAEtB,EAAU,SACZ,EAAO,OAAO,KAGR;CACR;AACD;;;ACrDA,SAAwB,EAAiB,GAAkB,GAAoC,GAA2C;CACzI,IAAM,IAAS,EAAgD;CAK/D,AAJG,MACF,EAAA,KAAoB,IAGrB,EAAU,WAAW,CAAQ;AAC9B;;;ACTA,SAAwB,EAAmB,GAA4B,GAA2C;CACjH,EAAU,aAAa,CAAM;AAC9B;;;ACOA,SAAwB,EAKtB,GAA6B,GAAkD;CAEhF,IAAI,IAA+C,CAAC,GAC9C,IAAuE,CAAC,GAE1E;CAEJ,EAAM,YAAY,SAAS,GAAG;EAC7B,IAAM,IAAU,EAAE;EAElB,IAAG,EAAQ,SAAS,QACnB,EAAiB,GAAO,EACvB,MAAM,gBACP,CAAC;OACK,IAAG,EAAQ,SAAS,QAE1B,AADA,IAAiB,EAAe,OAAO,EAAQ,IAAI,KAAK,KAAA,GACxD,EAAiB,GAAO,EACvB,MAAM,SACP,CAAC;OACK,IAAG,EAAQ,SAAS,SAAS;GAEnC,IAAW,CAAC;GACZ,KAAI,IAAM,KAAO,OAAO,KAAK,CAAa,GACzC,OAAO,EAAc;GAEtB,IAAiB,KAAA;EAClB,OAAO,IAAG,EAAQ,SAAS,OAAO;GACjC,AAAG,KACF,OAAO,OAAO,EAAQ,OAAO,CAAc;GAE5C,IAAM,IAAQ,YAAY,IAAI,GAC1B,IAAmC,CAAC,GACpC,IAA6B,CAAC,GAC9B,IAAkD,CAAC;GAEvD,IAAW,EAAgB,GAAU,EAAQ,QAAyB;GAEtE,IAAI,IAAoC,CAAC;GACzC,OAAO,QAAQ,EAAQ,OAAO,CAAC,CAAC,SAAS,CAAC,GAAU,OAAW;IAC9D,IAAM,IAAO,EAAgB,EAAc,MAAa,CAAC,GAAG,CAAsB;IAElF,AADA,EAAc,KAAY,GAC1B,EAAQ,KAAY;GACrB,CAAC;GAED,IAAI,IAAyC;IAC5C,uBAAgE,GAAkB,GAAkB,GAAS,GAAgB;KAC5H,EAAa,KAAK;MACjB;MACA,OAAO;MACP,MAAM;OAAC;OAAe;OAAM;MAAK;KAClC,CAAC;IACF;IACA,gBAAgB,GAAkB,GAAe,GAAG,GAAsB;KACzE,EAAa,KAAK;MACjB;MACA;MACA;KACD,CAAC;IACF;IACA,gBAAgB,GAAe,GAAkB;KAChD,CAAC,EAAa,OAAW,EAAa,KAAS,CAAC,GAAA,CAAI,KAAK,CAAQ;IAClE;IACA,WAAW,GAAkB;KAC5B,EAAa,KAAK;MACjB;MACA,OAAO;MACP,MAAM,CAAC;KACR,CAAC;IACF;IACA,aAAa,GAAiC;KAC7C,EAAgB,KAAK,CAAM;IAC5B;GACD;GASA,AARG,EAAe,UACjB,EAAe,OAAO,EAAQ,OAAO,GAAU,GAAS,CAAS,GAGlE,EAAS,SAAQ,MAAU;IAC1B,EAAe,EAAQ,OAAO,EAAO,UAAU,EAAO,YAAY,GAAS,CAAS;GACrF,CAAC,GAEE,EAAe,iBACjB,EAAQ,SAAS,QAAQ,SAAQ,MAAY;IAC5C,EAAe,cAAe,EAAQ,OAAO,GAAU,CAAS;GACjE,CAAC;GAEF,IAAM,IAAU,YAAY,IAAI,IAAI;GAEpC,EAAiB,GAAO;IACvB,MAAM;IACN,YAAY,EAAQ;IACpB;IACA,QAAQ;IACR;IACA,SAAS;GACV,CAAC;EACF;CACD;AACD;AAEA,SAAS,EAAiB,GAA6B,GAAiC;CACvF,EAAM,YAAY,CAAO;AAC1B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export type { IterableSystemConfig } from './systems/iterable-system';
|
|
|
19
19
|
export { default as EntitySystem } from './systems/entity-system';
|
|
20
20
|
export type { EntitySystemConfig } from './systems/entity-system';
|
|
21
21
|
export { default as ComponentSystem } from './systems/component-system';
|
|
22
|
-
export type { ComponentSystemConfig, ComponentSystemQuery, ComponentSystemWorld, ComponentSystemCallbacks, CreateEntityConfig, EntityUpdateComponents, EntityQueryComponents, EntityUpdateFunction, EntityUpdatePreRunFunction, EntityRemovedFunction, UpdateEntityConfig, UpdateEntityConfigObject, } from './systems/component-system';
|
|
22
|
+
export type { ComponentSystemConfig, ComponentSystemQuery, ComponentSystemWorld, ComponentSystemCallbacks, CreateEntityConfig, EntityUpdateComponents, EntityQueryComponents, EntityUpdateFunction, EntityUpdateInitFunction, EntityUpdatePreRunFunction, EntityRemovedFunction, UpdateEntityConfig, UpdateEntityConfigObject, } from './systems/component-system';
|
|
23
23
|
export { default as WebWorker } from './systems/workers/web-worker';
|
|
24
24
|
export { default as ComponentWebWorker } from './systems/workers/component-web-worker';
|
|
25
25
|
export { default as createComponentWorker } from './systems/workers/create-component-worker';
|