@daneren2005/shared-memory-ecs 1.4.0 → 1.5.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
@@ -10,11 +10,14 @@ 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)`, 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)).
13
+ that trigger loading (`loadProperties`), and the two halves that build it — `toBlock(config)` (maps the
14
+ config to the raw block values) and `attach(entity, memory, index)` (builds the accessor over that block).
15
+ Loading a component is `attach(entity, memory, memory.create(toBlock(config)))`; splitting it this way lets a
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)` and `free(component)`. A component's data splits into
18
+ `Config` (defining props supplied up front, e.g. `maxHealth`) and `Serialization` (runtime-derived state,
19
+ e.g. current `health`); `toBlock` sees `Config & Serialization` while `save` returns only the `Serialization`
20
+ slice. `free` runs when the component is torn down (see [Freeing extra resources](#freeing-extra-resources)).
18
21
  - **`ComponentRegistry<C>`** – the map of all component definitions for a game.
19
22
  - **`EntityFactory<C>`** – maps an entity `type` name to a base (template) config. Loading an entity layers
20
23
  the caller's config over its type's template, so shared static data lives in one place and a save only
@@ -33,6 +36,7 @@ fog of war, sub-classed entities, required components, etc).
33
36
  ## Defining components
34
37
 
35
38
  ```ts
39
+ import { Component } from '@daneren2005/shared-memory-ecs';
36
40
  import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';
37
41
 
38
42
  interface HealthComponent {
@@ -44,7 +48,16 @@ interface HealthComponent {
44
48
  const HEALTH_INDEX = 0;
45
49
  const HEALTH_MAX_INDEX = 1;
46
50
 
47
- // `maxHealth` is the defining Config prop; `health` is optional, runtime-derived Serialization. `load`
51
+ // The accessor is a subclass of `Component`, so its get/set live on one shared prototype: reading a component off
52
+ // thousands of entities stays monomorphic and inline, and each instance is a single allocation rather than a
53
+ // closure per accessor. Index `this.block` (the typed-array view, set for you) by the exported *_INDEX constants.
54
+ class Health extends Component<Int32Array> implements HealthComponent {
55
+ get health() { return this.block[HEALTH_INDEX]; }
56
+ set health(value: number) { this.block[HEALTH_INDEX] = value; }
57
+ get maxHealth() { return this.block[HEALTH_MAX_INDEX]; }
58
+ }
59
+
60
+ // `maxHealth` is the defining Config prop; `health` is optional, runtime-derived Serialization. `toBlock`
48
61
  // and `save` both see the combined `{ maxHealth: number, health?: number }`.
49
62
  const healthDefinition: ComponentDefinition<HealthComponent, Int32Array, { maxHealth: number }, { health?: number }> = {
50
63
  type: Int32Array,
@@ -52,16 +65,15 @@ const healthDefinition: ComponentDefinition<HealthComponent, Int32Array, { maxHe
52
65
  // Configs are flat and shared: this component loads whenever `maxHealth` is present, then reads what it
53
66
  // needs off the same config object. Only Config props may appear here.
54
67
  loadProperties: ['maxHealth'],
55
- load(entity, memoryComponent, config) {
56
- const index = memoryComponent.create([config.health ?? config.maxHealth, config.maxHealth]);
57
- const memory = memoryComponent.getBlock(index);
58
-
59
- return {
60
- index,
61
- get health() { return memory[HEALTH_INDEX]; },
62
- set health(value: number) { memory[HEALTH_INDEX] = value; },
63
- get maxHealth() { return memory[HEALTH_MAX_INDEX]; }
64
- };
68
+ // The block values, purely from config — so a worker can build the block off-thread. No entity/world access.
69
+ toBlock(config) {
70
+ return [config.health ?? config.maxHealth, config.maxHealth];
71
+ },
72
+ // The accessor over an already-allocated block. Runs on the main thread both when loading and when adopting a
73
+ // worker-built entity; a component's own extra allocations (a SharedList, a resource block) go here — a
74
+ // subclass that owns extra memory takes more constructor args (the entity, another pool) and stores them.
75
+ attach(entity, memoryComponent, index) {
76
+ return new Health(memoryComponent.getBlock(index), index);
65
77
  },
66
78
  save(component) {
67
79
  // save returns only Serialization; maxHealth is Config and comes back from the template on reload.
@@ -74,7 +86,7 @@ const registry = {
74
86
  health: healthDefinition
75
87
  // ...other component definitions
76
88
  };
77
- type Components = { [K in keyof typeof registry]: ReturnType<(typeof registry)[K]['load']> };
89
+ type Components = { [K in keyof typeof registry]: ReturnType<(typeof registry)[K]['attach']> };
78
90
  ```
79
91
 
80
92
  ## Using the world
@@ -111,6 +123,12 @@ console.log(goblin.save()); // { type: 'goblin', health: 10 } - no templated max
111
123
  world.loadEntity(goblin.save());
112
124
  ```
113
125
 
126
+ An entity's `type` lives in shared memory (worker-visible), not as a plain field. Each distinct type string is
127
+ interned once as an immutable `ConstantString` in the heap — 15 `"Space Ship"` entities share one allocation —
128
+ and the `entity` block stores only a pointer to it at `TYPE_INDEX`. Reading `entity.components.entity.type`
129
+ resolves that pointer back to the string through the world's cache; a worker can do the same (see
130
+ [Reading an entity's type in a worker](#reading-an-entitys-type-in-a-worker)).
131
+
114
132
  ## Iterating entities
115
133
 
116
134
  `world.entities` is a `Map` keyed by `eid`, not an array, and so is `entities` on `EntitySystem` and
@@ -141,10 +159,10 @@ one: it is typed, it is readable, and one read costs nothing worth measuring.
141
159
 
142
160
  It is not free, though, and the cost shows up in exactly one situation — reading the same component off
143
161
  *thousands* of entities, *every frame*. The accessor walks several objects to get there and ends in a getter
144
- closure over the shared block, and because every entity has its own closure those call sites go megamorphic
145
- once enough entities are alive, so none of it inlines. Measured over ~10,000 entities, reading four values per
146
- entity cost **~950ns through the accessors against ~140ns straight off the block** the difference between
147
- 10ms a frame and 1.5ms.
162
+ call over the shared block. `Component`-subclass accessors are prototype getters (all instances of a type share
163
+ one hidden class, so those reads stay monomorphic and inline unlike the old per-entity closures, which went
164
+ megamorphic), but a getter call plus the property walk still loses to a raw indexed read in the very tightest
165
+ per-frame loops.
148
166
 
149
167
  Where that matters, hold the block instead. It is the same memory the accessors read, so nothing changes about
150
168
  what you get, and it is what the update functions already work on:
@@ -152,15 +170,15 @@ what you get, and it is what the update functions already work on:
152
170
  ```ts
153
171
  import { TRANSFORM_X_INDEX } from '@daneren2005/shared-memory-physics';
154
172
 
155
- // Resolve once, when whatever is doing the reading is set up.
156
- const health = entity.components.health!;
157
- const block = world.registry.health.memoryComponent.getBlock(health.index) as Int32Array;
173
+ // Resolve once, when whatever is doing the reading is set up. The component caches its view on `.block` when it
174
+ // is attached, so read that rather than paying for another getBlock (each call allocates a fresh subarray).
175
+ const block = entity.components.health!.block as Int32Array;
158
176
 
159
177
  // Then per frame, per entity:
160
178
  block[HEALTH_INDEX];
161
179
  ```
162
180
 
163
- Two things come with that. The block is only valid while the component is: resolve it again if the component
181
+ Two things come with that. The block is only valid while the component is: re-read `.block` if the component
164
182
  can be removed and re-added, or hang it off something that dies with the entity. And it is indexed rather than
165
183
  named, so the offsets have to be exported alongside the definition — which is why every component in the
166
184
  physics library exports its `*_INDEX` constants.
@@ -259,8 +277,8 @@ barrel: importing `createComponentWorker` from `@daneren2005/shared-memory-ecs`
259
277
  `BaseWorld`, every system, their `@daneren2005/shared-memory-objects` dependencies - into the worker, even
260
278
  though a worker never runs any of it (easily ~20kb of dead code per worker). Import worker-side helpers from
261
279
  the `@daneren2005/shared-memory-ecs/worker` subpath instead. It exposes only what runs in a worker -
262
- `createComponentWorker`, `createEntityWorker`, `killEntityWorker`, `DEAD_INDEX` (plus the worker-relevant
263
- types) - so the bundle stays tiny:
280
+ `createComponentWorker`, `createEntityWorker`, `killEntityWorker`, `DEAD_INDEX`, `TYPE_INDEX` (plus the
281
+ worker-relevant types) - so the bundle stays tiny:
264
282
 
265
283
  ```ts
266
284
  // damage.worker.ts - the worker entry file
@@ -275,12 +293,46 @@ or `killEntityWorker` should import them from `/worker` too. Type-only imports (
275
293
  `ComponentSystemWorld`, ...) can come from either path since types are erased, and main-thread code
276
294
  (`ComponentSystem`, `BaseWorld`, `EntityFactory`, ...) keeps importing from the package root.
277
295
 
296
+ A worker that creates entities (see [Creating entities from a worker](#creating-entities-from-a-worker)) passes
297
+ your component registry as the third argument - `createComponentWorker(self, shipUpdate, registry)` - so it has
298
+ each component's `toBlock`. Only do this in workers that actually create entities; it pulls the registry (and
299
+ whatever it imports) into that worker's bundle.
300
+
301
+ ### Reading an entity's type in a worker
302
+
303
+ The `entity` component carries the entity's `type` in shared memory as a pointer, so a worker can resolve it
304
+ back to a string. Pull `entity` into the system's query (its block only reaches the worker if it is `required`
305
+ or `optional`), read the pointer at `TYPE_INDEX`, and hand it to `world.getString` — the framework injects that
306
+ resolver on `world` before every run:
307
+
308
+ ```ts
309
+ import { TYPE_INDEX } from '@daneren2005/shared-memory-ecs/worker';
310
+ import type { EntityUpdateFunction } from '@daneren2005/shared-memory-ecs/worker';
311
+
312
+ const update: EntityUpdateFunction<Components, { entity: Uint32Array }> = (world, entityId, components) => {
313
+ const type = world.getString(components.entity[TYPE_INDEX]); // e.g. "Space Ship"
314
+ // ...branch on type, etc.
315
+ };
316
+
317
+ class TypedSystem extends ComponentSystem<Components, { entity: Uint32Array }> {
318
+ constructor(world: BaseWorld<Components>) {
319
+ super(world, { name: 'TypedSystem', required: ['entity'], updateFunction: update, getWorker: () => new Worker(/* ... */) });
320
+ }
321
+ }
322
+ ```
323
+
324
+ `world.getString(pointer)` is a Map lookup before it ever rebuilds the string from memory, and returns `''` for
325
+ the empty type or a pointer whose buffer has not synced to the worker yet. This works identically on the
326
+ main-thread fallback. It is not limited to type — any pointer to a `ConstantString` (via `world.constantStrings`
327
+ on the main thread) resolves the same way.
328
+
278
329
  ### Reporting back to the main thread
279
330
 
280
331
  An update function runs on shared memory, so anything it writes is already visible on the main thread. What
281
332
  it cannot do from there is touch the world, so the things that have to happen back on it go through
282
333
  `callbacks`: `entityComponentChanged` (emitted on the entity as `component-property-updated`), `entityDied`
283
- (as `death`), and `createEntity`. All of them are collected during the run and applied once it completes.
334
+ (as `death`), and `createEntity` (see [Creating entities from a worker](#creating-entities-from-a-worker)).
335
+ All of them are collected during the run and applied once it completes.
284
336
 
285
337
  `emitEntityEvent` is the escape hatch for an event of your own: name it whatever you like and give it
286
338
  whatever args suit it, and it is emitted on the entity under that name. It exists so a system does not have
@@ -335,6 +387,59 @@ thread already holds the values - sending them along would only pay to copy what
335
387
  System events are dispatched before the per-entity events of the same run, so an entity a run both moved and
336
388
  killed is still in the world when its move is reported.
337
389
 
390
+ ### Creating entities from a worker
391
+
392
+ An update function can spawn a whole entity, off-thread, from a factory config - the same
393
+ `{ type, ...overrides }` you would pass to `world.loadEntity`. Because component pools live in shared memory,
394
+ the worker merges the type's factory template, allocates each component's block and writes it, then reports
395
+ what it made; the main thread only wraps the result:
396
+
397
+ ```ts
398
+ // in the update function - spawn a ship, overriding two fields of its template
399
+ createEntityWorker(world, { type: 'ship', x: 100, y: 150 }, callbacks);
400
+ ```
401
+
402
+ `createEntityWorker` layers your overrides over the `ship` template, mints a unique id from a shared atomic
403
+ counter (so it never collides with one the main thread or another worker hands out), and for each component
404
+ the merged config triggers, pushes a block into its pool and writes the values - all off-thread. It reports
405
+ back an id-plus-block-indexes descriptor; when the run completes the main thread *adopts* it, building the
406
+ always-present `entity` component there (interning the `type` is a main-thread job) and wrapping each block the
407
+ worker wrote - no block is copied or re-allocated. Like every other worker report-back, the new entity first
408
+ exists on the following frame, so the system picks it up next run.
409
+
410
+ Two things make this work, both opt-in so only the workers that create entities pay for them:
411
+
412
+ - Register the system with `createsEntities: true`. That ships the factory templates to its worker on load.
413
+ - In that system's worker entry, pass your component registry to `createComponentWorker`, so the worker has
414
+ each component's block builder:
415
+
416
+ ```ts
417
+ createComponentWorker(self, shipUpdate, registry);
418
+ ```
419
+
420
+ Every component is already defined as two halves for exactly this — `toBlock(config)` (the block values) and
421
+ `attach(entity, memory, index)` (the accessor), see [Defining components](#defining-components). The worker
422
+ runs only `toBlock` (off-thread, no entity/world), and the main thread runs `attach` when it adopts the entity.
423
+ So creating a component in a worker needs nothing extra as long as its `toBlock` is genuinely pure over config —
424
+ no `entity`/`world` access:
425
+
426
+ ```ts
427
+ const shipHealth: ComponentDefinition<HealthComponent, Int32Array, HealthConfig> = {
428
+ // ...
429
+ toBlock(config) {
430
+ return [config.health ?? config.maxHealth, config.maxHealth];
431
+ },
432
+ attach(entity, memory, index) {
433
+ const block = memory.getBlock(index);
434
+ // ...build the accessor over `block`
435
+ },
436
+ };
437
+ ```
438
+
439
+ This runs identically on the main-thread fallback. Two current limits: a `loadInFinishLoading` component (one
440
+ that reads other entities) is skipped, since the worker has no world to read; and an adopted entity is always
441
+ the base `BaseEntity` - a factory per-type subclass is not applied to it.
442
+
338
443
  ### Freeing component memory safely
339
444
 
340
445
  Component blocks live in a shared pool, so a freed block gets handed straight back out to the next entity that
@@ -353,7 +458,7 @@ a stuck system there is a bug worth chasing down.
353
458
 
354
459
  ### Freeing extra resources
355
460
 
356
- Everything above frees a component's own block. A component that allocates something *else* in `load` —
461
+ Everything above frees a component's own block. A component that allocates something *else* in `attach` —
357
462
  another heap structure (a `SharedList`, a `SharedString`) or child entities it owns — needs to release that
358
463
  too, and the block-level deferred free won't do it. Give the definition an optional `free(component)`:
359
464
 
@@ -362,7 +467,10 @@ const cargoDefinition: ComponentDefinition<Cargo, Uint32Array, CargoConfig> = {
362
467
  type: Uint32Array,
363
468
  size: 3,
364
469
  loadProperties: ['cargoSpace'],
365
- load(entity, memory, config) {
470
+ toBlock(config) {
471
+ /* the block's own values */
472
+ },
473
+ attach(entity, memory, index) {
366
474
  /* allocate a SharedList in the heap, stash its pointer in the block, return accessors */
367
475
  },
368
476
  free(component) {
@@ -0,0 +1,13 @@
1
+ import type { WorkerAllocator, WorkerCreateEntityConfig, WorkerCreatedEntity } from '../systems/component-system';
2
+ export interface WorkerCreatableComponent {
3
+ loadProperties: Array<string>;
4
+ loadInFinishLoading?: boolean;
5
+ toBlock(config: Record<string, unknown>): Array<number>;
6
+ }
7
+ export type WorkerCreateRegistry = {
8
+ [name: string]: WorkerCreatableComponent;
9
+ };
10
+ export type FactoryConfigs = {
11
+ [type: string]: Record<string, unknown>;
12
+ };
13
+ export declare function buildWorkerEntity(config: WorkerCreateEntityConfig, factoryConfigs: FactoryConfigs, registry: WorkerCreateRegistry, allocator: WorkerAllocator): WorkerCreatedEntity;
@@ -1,2 +1,2 @@
1
- import type { ComponentSystemCallbacks, CreateEntityConfig } from '../systems/component-system';
2
- export default function createEntityWorker(config: CreateEntityConfig, callbacks: ComponentSystemCallbacks): void;
1
+ import type { ComponentSystemCallbacks, ComponentSystemWorld, WorkerCreateEntityConfig } from '../systems/component-system';
2
+ export default function createEntityWorker(world: ComponentSystemWorld, config: WorkerCreateEntityConfig, callbacks: ComponentSystemCallbacks): void;
@@ -5,6 +5,7 @@ import type BaseEntity from './entity';
5
5
  import type { EntityComponent, EntityComponentConfig, EntityComponentSerialization } from './entity-component';
6
6
  export interface BaseComponent {
7
7
  index: number;
8
+ block?: ComponentTypedArray;
8
9
  }
9
10
  export type ComponentMap = Record<string, BaseComponent>;
10
11
  export interface ComponentDefinition<Component extends BaseComponent, T extends ComponentTypedArray = ComponentTypedArray, Config = any, Serialization = object> {
@@ -12,7 +13,8 @@ export interface ComponentDefinition<Component extends BaseComponent, T extends
12
13
  size: number;
13
14
  loadProperties: Array<keyof Config & string>;
14
15
  loadInFinishLoading?: boolean;
15
- load(entity: BaseEntity, memory: MemoryComponent<T>, config: Config & Serialization): Component;
16
+ toBlock(config: Config & Serialization, entity?: BaseEntity): Array<number>;
17
+ attach(entity: BaseEntity, memory: MemoryComponent<T>, index: number): Component;
16
18
  save?(component: Component): Serialization;
17
19
  free?(component: Component): void;
18
20
  }
@@ -29,9 +31,9 @@ export type ComponentDefinitionMap = Record<string, ComponentDefinition<BaseComp
29
31
  type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
30
32
  type DefinitionConfig<D> = D extends ComponentDefinition<BaseComponent, ComponentTypedArray, infer Config, infer Serialization> ? Config & Serialization : never;
31
33
  export type ComponentsOf<R extends ComponentDefinitionMap> = {
32
- [K in keyof R]: ReturnType<R[K]['load']>;
34
+ [K in keyof R]: ReturnType<R[K]['attach']> & BaseComponent;
33
35
  } & {
34
- entity: EntityComponent;
36
+ entity: EntityComponent & BaseComponent;
35
37
  };
36
38
  export type EntityConfigOf<R extends ComponentDefinitionMap> = Partial<UnionToIntersection<{
37
39
  [K in keyof R]: DefinitionConfig<R[K]>;
@@ -0,0 +1,7 @@
1
+ import type { ComponentTypedArray } from './memory-component';
2
+ import type { BaseComponent } from './component-definition';
3
+ export default abstract class Component<T extends ComponentTypedArray = ComponentTypedArray> implements BaseComponent {
4
+ readonly index: number;
5
+ readonly block: T;
6
+ constructor(block: T, index: number);
7
+ }
@@ -0,0 +1,11 @@
1
+ import ConstantString from '@daneren2005/shared-memory-objects/constant-string';
2
+ import type MemoryHeap from '@daneren2005/shared-memory-objects/memory-heap';
3
+ export default class ConstantStringCache {
4
+ private heap;
5
+ private byValue;
6
+ private byPointer;
7
+ constructor(heap: MemoryHeap);
8
+ getOrCreate(value: string): ConstantString;
9
+ getString(pointer: number): string | undefined;
10
+ clear(): void;
11
+ }
@@ -0,0 +1,263 @@
1
+ import e from "@daneren2005/shared-memory-objects/memory-heap";
2
+ import { getPointer as t } from "@daneren2005/shared-memory-objects/utils/pointer";
3
+ import n from "@daneren2005/shared-memory-objects/shared-pool";
4
+ import r from "@daneren2005/shared-memory-objects/constant-string";
5
+ //#region src/memory-component.ts
6
+ var i = class e {
7
+ heap;
8
+ pool;
9
+ constructor(e, t, r, i) {
10
+ this.heap = e, this.pool = i ? new n(e, i) : new n(e, {
11
+ type: t,
12
+ dataLength: r
13
+ });
14
+ }
15
+ static fromSharedMemory(t, n) {
16
+ return new e(t, Uint32Array, 1, n);
17
+ }
18
+ getSharedMemory() {
19
+ return this.pool.getSharedMemory();
20
+ }
21
+ get length() {
22
+ return this.pool.length;
23
+ }
24
+ get rawLength() {
25
+ return this.pool.bufferLength;
26
+ }
27
+ create(e) {
28
+ return this.pool.push(e);
29
+ }
30
+ getBlock(e) {
31
+ return this.pool.at(e);
32
+ }
33
+ get(e, t) {
34
+ return this.pool.get(e, t);
35
+ }
36
+ set(e, t, n) {
37
+ let r = this.pool.at(e);
38
+ r[t] = n;
39
+ }
40
+ delete(e) {
41
+ this.pool.deleteIndex(e);
42
+ }
43
+ clear() {
44
+ this.pool.clear();
45
+ }
46
+ }, a = class {
47
+ heap;
48
+ byValue = /* @__PURE__ */ new Map();
49
+ byPointer = /* @__PURE__ */ new Map();
50
+ constructor(e) {
51
+ this.heap = e;
52
+ }
53
+ getOrCreate(e) {
54
+ let t = this.byValue.get(e);
55
+ if (t) return t;
56
+ let n = new r(this.heap, e);
57
+ return this.byValue.set(e, n), this.byPointer.set(n.pointer, e), n;
58
+ }
59
+ getString(e) {
60
+ if (e === 0) return "";
61
+ let n = this.byPointer.get(e);
62
+ if (n !== void 0) return n;
63
+ let i = t(e);
64
+ if (this.heap.buffers[i.bufferPosition] === void 0) return;
65
+ let a = new r(this.heap, i).value;
66
+ return this.byPointer.set(e, a), a;
67
+ }
68
+ clear() {
69
+ this.byValue.forEach((e) => e.free()), this.byValue.clear(), this.byPointer.clear();
70
+ }
71
+ };
72
+ //#endregion
73
+ //#region src/systems/workers/apply-query-delta.ts
74
+ function o(e, t) {
75
+ if (t.removed.length) {
76
+ let n = new Set(t.removed);
77
+ e = e.filter((e) => !n.has(e.entityId));
78
+ }
79
+ if (t.added.length) {
80
+ let n = /* @__PURE__ */ new Map();
81
+ e.forEach((e, t) => n.set(e.entityId, t));
82
+ for (let r of t.added) {
83
+ let t = n.get(r.entityId);
84
+ t === void 0 ? (n.set(r.entityId, e.length), e.push(r)) : e[t] = r;
85
+ }
86
+ }
87
+ return e;
88
+ }
89
+ //#endregion
90
+ //#region src/actions/build-worker-entity.ts
91
+ function s(e, t, n, r) {
92
+ let i = {
93
+ ...t[e.type] ?? {},
94
+ ...e
95
+ }, a = r.allocateEid(), o = {};
96
+ for (let e of Object.keys(n)) {
97
+ if (e === "entity") continue;
98
+ let t = n[e];
99
+ t.loadInFinishLoading || t.loadProperties.some((e) => e in i) && (o[e] = r.allocateComponentBlock(e, t.toBlock(i)));
100
+ }
101
+ return {
102
+ eid: a,
103
+ type: e.type,
104
+ isStatic: i.isStatic,
105
+ components: o
106
+ };
107
+ }
108
+ //#endregion
109
+ //#region src/component.ts
110
+ var c = class {
111
+ index;
112
+ block;
113
+ constructor(e, t) {
114
+ this.block = e, this.index = t;
115
+ }
116
+ }, l = 0, u = 1, d = 2, f = class extends c {
117
+ cache;
118
+ constructor(e, t, n) {
119
+ super(e, t), this.cache = n;
120
+ }
121
+ get type() {
122
+ return this.cache.getString(this.block[2]) ?? "";
123
+ }
124
+ set type(e) {
125
+ this.block[2] = e ? this.cache.getOrCreate(e).pointer : 0;
126
+ }
127
+ get dead() {
128
+ return this.block[0] === 1;
129
+ }
130
+ set dead(e) {
131
+ this.block[0] = +!!e;
132
+ }
133
+ get isStatic() {
134
+ return this.block[1] === 1;
135
+ }
136
+ set isStatic(e) {
137
+ this.block[1] = +!!e;
138
+ }
139
+ }, p = {
140
+ type: Uint32Array,
141
+ size: 3,
142
+ loadProperties: ["type", "isStatic"],
143
+ toBlock(e, t) {
144
+ let n = t.world.constantStrings, r = e.type ? n.getOrCreate(e.type).pointer : 0;
145
+ return [
146
+ +!!e.dead,
147
+ +!!e.isStatic,
148
+ r
149
+ ];
150
+ },
151
+ attach(e, t, n) {
152
+ return new f(t.getBlock(n), n, e.world.constantStrings);
153
+ },
154
+ save(e) {
155
+ let t = {};
156
+ return e.type && (t.type = e.type), e.dead && (t.dead = !0), t;
157
+ }
158
+ };
159
+ //#endregion
160
+ //#region src/actions/kill-entity-worker.ts
161
+ function m(e, t, n) {
162
+ let r = t.entity;
163
+ r && (r[0] = 1), n.entityDied(e);
164
+ }
165
+ //#endregion
166
+ //#region src/actions/create-entity-worker.ts
167
+ function h(e, t, n) {
168
+ if (!e.buildEntityDescriptor) throw Error("createEntityWorker requires the system to be registered with createsEntities: true and the component registry passed to createComponentWorker");
169
+ n.createEntity(e.buildEntityDescriptor(t));
170
+ }
171
+ //#endregion
172
+ //#region src/systems/workers/create-component-worker.ts
173
+ function g(t, n, r) {
174
+ let c = [], l = {}, u, d, f, p = {}, m, h, g = (e) => f?.getString(e) ?? "";
175
+ t.onmessage = function(v) {
176
+ let y = v.data;
177
+ if (y.type === "init") _(t, { type: "init-complete" });
178
+ else if (y.type === "load") {
179
+ if (y.heap && (d = new e(y.heap), f = new a(d), d.addOnGrowBufferHandlers((e) => _(t, {
180
+ type: "grow-buffer-from-worker",
181
+ buffer: e
182
+ }))), d && y.sharedMemory) {
183
+ p = {};
184
+ for (let e of Object.keys(y.sharedMemory.components)) p[e] = i.fromSharedMemory(d, y.sharedMemory.components[e]);
185
+ m = d.getSharedAlloc(y.sharedMemory.eidCounter);
186
+ }
187
+ h = y.factoryConfigs, u = n.init?.(y.data) ?? void 0, _(t, { type: "loaded" });
188
+ } else if (y.type === "grow-buffer") d && d.buffers[y.buffer.bufferPosition] === void 0 && d.addSharedBuffer(y.buffer);
189
+ else if (y.type === "reset") {
190
+ c = [];
191
+ for (let e of Object.keys(l)) delete l[e];
192
+ u = void 0;
193
+ } else if (y.type === "run") {
194
+ u && Object.assign(y.world, u), y.world.getString = g;
195
+ let e = {
196
+ allocateEid: () => m ? Atomics.add(m.data, 0, 1) + 1 : 0,
197
+ allocateComponentBlock: (e, t) => p[e].create(t)
198
+ };
199
+ y.world.allocate = e, h && r && (y.world.buildEntityDescriptor = (t) => s(t, h, r, e));
200
+ let i = performance.now(), a = [], d = {}, f = [];
201
+ c = o(c, y.entities);
202
+ let v = {};
203
+ Object.entries(y.queries).forEach(([e, t]) => {
204
+ let n = o(l[e] ?? [], t);
205
+ l[e] = n, v[e] = n;
206
+ });
207
+ let b = {
208
+ entityComponentChanged(e, t, n, r) {
209
+ a.push({
210
+ entityId: e,
211
+ event: "component-property-updated",
212
+ args: [
213
+ t,
214
+ n,
215
+ r
216
+ ]
217
+ });
218
+ },
219
+ emitEntityEvent(e, t, ...n) {
220
+ a.push({
221
+ entityId: e,
222
+ event: t,
223
+ args: n
224
+ });
225
+ },
226
+ emitSystemEvent(e, t) {
227
+ (d[e] ?? (d[e] = [])).push(t);
228
+ },
229
+ entityDied(e) {
230
+ a.push({
231
+ entityId: e,
232
+ event: "death",
233
+ args: []
234
+ });
235
+ },
236
+ createEntity(e) {
237
+ f.push(e);
238
+ }
239
+ };
240
+ n.preRun && n.preRun(y.world, c, v, b), c.forEach((e) => {
241
+ n(y.world, e.entityId, e.components, v, b);
242
+ }), n.entityRemoved && y.entities.removed.forEach((e) => {
243
+ n.entityRemoved(y.world, e, b);
244
+ });
245
+ let x = performance.now() - i;
246
+ _(t, {
247
+ type: "run-complete",
248
+ generation: y.generation,
249
+ runTime: x,
250
+ events: a,
251
+ systemEvents: d,
252
+ created: f
253
+ });
254
+ }
255
+ };
256
+ }
257
+ function _(e, t) {
258
+ e.postMessage(t);
259
+ }
260
+ //#endregion
261
+ export { u as a, c, a as d, i as f, l as i, s as l, h as n, d as o, m as r, p as s, g as t, o as u };
262
+
263
+ //# sourceMappingURL=create-component-worker-CaB32Dgn.js.map