@vworlds/vecs 1.0.12 → 1.0.13
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 +85 -70
- package/dist/component.d.ts +22 -70
- package/dist/component.js +4 -59
- package/dist/component.js.map +1 -1
- package/dist/dsl.d.ts +9 -5
- package/dist/dsl.js +6 -3
- package/dist/dsl.js.map +1 -1
- package/dist/entity.d.ts +10 -11
- package/dist/entity.js +55 -51
- package/dist/entity.js.map +1 -1
- package/dist/filter.d.ts +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/query.d.ts +21 -21
- package/dist/query.js +17 -16
- package/dist/query.js.map +1 -1
- package/dist/system.d.ts +7 -7
- package/dist/system.js +11 -10
- package/dist/system.js.map +1 -1
- package/dist/util/array_map.js +11 -7
- package/dist/util/array_map.js.map +1 -1
- package/dist/util/bitset.d.ts +17 -3
- package/dist/util/bitset.js +47 -30
- package/dist/util/bitset.js.map +1 -1
- package/dist/world.d.ts +22 -15
- package/dist/world.js +28 -19
- package/dist/world.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ yarn add @vworlds/vecs
|
|
|
15
15
|
| Concept | What it is |
|
|
16
16
|
| ------------------------ | ------------------------------------------------------------------------------- |
|
|
17
17
|
| **World** | Central container. Owns every entity, query, system, and pipeline phase. |
|
|
18
|
-
| **Component** | A plain data class
|
|
18
|
+
| **Component** | A registered plain data class with a no-argument constructor. |
|
|
19
19
|
| **Entity** | A numeric id with a set of components. Created via the world. |
|
|
20
20
|
| **Query** | A reactive, always-up-to-date set of entities matching a predicate. |
|
|
21
21
|
| **System** | A `Query` with phase placement and per-tick logic (`update`, `each`, `run`). |
|
|
@@ -30,28 +30,28 @@ yarn add @vworlds/vecs
|
|
|
30
30
|
registerComponent() × N → addPhase() / system() / query() × N → start() → progress() every frame
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
After `start()`, component registration is disabled. Systems and queries can still be created — standalone queries backfill existing matched entities immediately.
|
|
33
|
+
Components must be registered before they are used as component classes. After `start()`, component registration is disabled. Systems and queries can still be created — standalone queries backfill existing matched entities immediately.
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
37
|
## Example
|
|
38
38
|
|
|
39
39
|
```ts
|
|
40
|
-
import { World,
|
|
40
|
+
import { World, type IPhase } from "@vworlds/vecs";
|
|
41
41
|
|
|
42
42
|
// ─── Components ────────────────────────────────────────────────────────────
|
|
43
43
|
|
|
44
|
-
class Position
|
|
44
|
+
class Position {
|
|
45
45
|
x = 0;
|
|
46
46
|
y = 0;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
class Velocity
|
|
49
|
+
class Velocity {
|
|
50
50
|
vx = 0;
|
|
51
51
|
vy = 0;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
class Health
|
|
54
|
+
class Health {
|
|
55
55
|
hp = 100;
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -78,7 +78,7 @@ world
|
|
|
78
78
|
.each([Position, Velocity], (e, [pos, vel]) => {
|
|
79
79
|
pos.x += vel.vx;
|
|
80
80
|
pos.y += vel.vy;
|
|
81
|
-
|
|
81
|
+
e.modified(Position); // signal that Position changed so other systems react
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
// HealthSystem: despawns entities whose HP drops to zero.
|
|
@@ -86,9 +86,9 @@ world
|
|
|
86
86
|
.system("Health")
|
|
87
87
|
.phase(cleanup)
|
|
88
88
|
.requires(Health)
|
|
89
|
-
.update(Health, (health) => {
|
|
89
|
+
.update(Health, (entity, health) => {
|
|
90
90
|
if (health.hp <= 0) {
|
|
91
|
-
|
|
91
|
+
entity.destroy();
|
|
92
92
|
}
|
|
93
93
|
});
|
|
94
94
|
|
|
@@ -96,8 +96,8 @@ world
|
|
|
96
96
|
|
|
97
97
|
world
|
|
98
98
|
.hook(Health)
|
|
99
|
-
.onAdd((h) => console.log(`entity ${
|
|
100
|
-
.onRemove((
|
|
99
|
+
.onAdd((entity, h) => console.log(`entity ${entity.eid} spawned with hp=${h.hp}`))
|
|
100
|
+
.onRemove((entity) => console.log(`entity ${entity.eid} died`));
|
|
101
101
|
|
|
102
102
|
// ─── Start ─────────────────────────────────────────────────────────────────
|
|
103
103
|
|
|
@@ -144,9 +144,16 @@ const world = new World();
|
|
|
144
144
|
|
|
145
145
|
#### Component registration
|
|
146
146
|
|
|
147
|
+
Components are ordinary classes. They do not inherit from a vecs base class, and vecs constructs them with `new ComponentClass()`, so constructors should take no parameters. Register every component class before using it in `add`, `set`, `get`, `requires`, `query`, `filter`, `hook`, or `setExclusiveComponents`.
|
|
148
|
+
|
|
147
149
|
```ts
|
|
150
|
+
class Position {
|
|
151
|
+
x = 0;
|
|
152
|
+
y = 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
148
155
|
// Auto-assigned type id (≥ 256 for "local" components):
|
|
149
|
-
world.registerComponent(Position);
|
|
156
|
+
const positionMeta = world.registerComponent(Position);
|
|
150
157
|
|
|
151
158
|
// Explicit numeric type id (e.g. server-assigned):
|
|
152
159
|
world.registerComponent(Position, 1);
|
|
@@ -158,7 +165,9 @@ world.registerComponent(Position, "pos");
|
|
|
158
165
|
world.registerComponentType("Position", 1);
|
|
159
166
|
```
|
|
160
167
|
|
|
161
|
-
|
|
168
|
+
`registerComponent` returns the `ComponentMeta` for the class. Internally, vecs stores that metadata on the component class under a hidden world-specific key, so the same class can be registered independently in multiple worlds. Numeric type lookup still goes through the world's type table.
|
|
169
|
+
|
|
170
|
+
After `world.start()` (or `world.disableComponentRegistration()`) any further call to `registerComponent` throws. There is no automatic component registration; using an unregistered component class as a component is an error.
|
|
162
171
|
|
|
163
172
|
#### Exclusive component groups
|
|
164
173
|
|
|
@@ -199,12 +208,12 @@ world.clearAllEntities();
|
|
|
199
208
|
```ts
|
|
200
209
|
world
|
|
201
210
|
.hook(Sprite)
|
|
202
|
-
.onAdd((sprite) => sprite.initialize(scene))
|
|
203
|
-
.onRemove((sprite) => sprite.destroy())
|
|
204
|
-
.onSet((sprite) => sprite.syncToScene());
|
|
211
|
+
.onAdd((entity, sprite) => sprite.initialize(scene, entity))
|
|
212
|
+
.onRemove((entity, sprite) => sprite.destroy(scene, entity))
|
|
213
|
+
.onSet((entity, sprite) => sprite.syncToScene(entity));
|
|
205
214
|
```
|
|
206
215
|
|
|
207
|
-
`onAdd` fires when the component is first attached. `onRemove` fires when it is removed (or the entity is destroyed). `onSet` fires whenever `
|
|
216
|
+
`onAdd` fires when the component is first attached. `onRemove` fires when it is removed (or the entity is destroyed). `onSet` fires whenever `entity.modified(C)` is called, and when `entity.set(C, props)` is applied to an entity that already has the component. Hook callbacks receive the owning entity because component instances do not carry entity references.
|
|
208
217
|
|
|
209
218
|
#### Phases
|
|
210
219
|
|
|
@@ -217,7 +226,7 @@ const send = world.addPhase("send");
|
|
|
217
226
|
world.progress(now, delta);
|
|
218
227
|
|
|
219
228
|
// ...or run individual phases manually:
|
|
220
|
-
world.beginFrame(
|
|
229
|
+
world.beginFrame(delta);
|
|
221
230
|
try {
|
|
222
231
|
world.runPhase(preUpdate, now, delta);
|
|
223
232
|
world.runPhase(update, now, delta);
|
|
@@ -358,8 +367,10 @@ world.endDefer();
|
|
|
358
367
|
|
|
359
368
|
### `Component`
|
|
360
369
|
|
|
370
|
+
Components are plain classes. vecs does not provide a runtime base class and does not attach `entity`, `meta`, `type`, `bitPtr`, or `modified()` to component instances.
|
|
371
|
+
|
|
361
372
|
```ts
|
|
362
|
-
class Position
|
|
373
|
+
class Position {
|
|
363
374
|
x = 0;
|
|
364
375
|
y = 0;
|
|
365
376
|
}
|
|
@@ -369,20 +380,21 @@ world.registerComponent(Position);
|
|
|
369
380
|
entity.add(Position);
|
|
370
381
|
const pos = entity.get(Position)!;
|
|
371
382
|
pos.x = 100;
|
|
372
|
-
|
|
383
|
+
entity.modified(Position); // tell the world this component changed
|
|
373
384
|
|
|
374
385
|
// Equivalent — set assigns props and fires onSet automatically:
|
|
375
386
|
entity.set(Position, { x: 100 });
|
|
376
387
|
```
|
|
377
388
|
|
|
378
|
-
|
|
|
379
|
-
|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
| `
|
|
383
|
-
|
|
|
384
|
-
|
|
|
385
|
-
|
|
389
|
+
| Rule | Description |
|
|
390
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
391
|
+
| Plain class | Components should be ordinary classes with field initializers and methods as needed. |
|
|
392
|
+
| No-arg construction | vecs calls `new ComponentClass()`, so constructors should be omitted or take no parameters. |
|
|
393
|
+
| Explicit registration | Call `world.registerComponent(C)` before using the class as a component. |
|
|
394
|
+
| Shared instances possible | A component instance does not know which entity owns it; code should use the entity passed by vecs callbacks. |
|
|
395
|
+
| Manual dirty marking | After mutating fields directly, call `entity.modified(C)` to notify hooks, queries, and systems. |
|
|
396
|
+
|
|
397
|
+
Use `world.getComponentMeta(C)` or `world.getComponentType(C)` when you need metadata such as the numeric type id or component name. Metadata is world-specific.
|
|
386
398
|
|
|
387
399
|
---
|
|
388
400
|
|
|
@@ -390,32 +402,32 @@ entity.set(Position, { x: 100 });
|
|
|
390
402
|
|
|
391
403
|
Created via `world.entity()` (auto-assigned id) or `world.getOrCreateEntity(id, ...)` (caller-supplied id).
|
|
392
404
|
|
|
393
|
-
| Property / Method
|
|
394
|
-
|
|
|
395
|
-
| `eid`
|
|
396
|
-
| `world`
|
|
397
|
-
| `componentBitmask`
|
|
398
|
-
| `properties`
|
|
399
|
-
| `add(Class)`
|
|
400
|
-
| `set(Class, props)`
|
|
401
|
-
| `modified(
|
|
402
|
-
| `get(Class)`
|
|
403
|
-
| `remove(Class)`
|
|
404
|
-
| `destroy()`
|
|
405
|
-
| `components`
|
|
406
|
-
| `empty`
|
|
407
|
-
| `parent`
|
|
408
|
-
| `children`
|
|
409
|
-
| `setParent(p)`
|
|
410
|
-
| `events`
|
|
411
|
-
| `toString()`
|
|
412
|
-
|
|
413
|
-
`entity.modified(
|
|
405
|
+
| Property / Method | Description |
|
|
406
|
+
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
407
|
+
| `eid` | Unique numeric entity id. |
|
|
408
|
+
| `world` | The `World` that owns this entity. |
|
|
409
|
+
| `componentBitmask` | `Bitset` of component type ids attached to this entity. Used by archetype matching. |
|
|
410
|
+
| `properties` | `Map<string, any>` free-form bag for module-level bookkeeping. |
|
|
411
|
+
| `add(Class)` | Attach a component (idempotent). Returns the entity for chaining. |
|
|
412
|
+
| `set(Class, props)` | Attach a component and assign `props`; fires `onSet`. Returns the entity for chaining. |
|
|
413
|
+
| `modified(Class)` | Queue an `onSet` / `update` notification for a component class or numeric type id. Returns the entity for chaining. |
|
|
414
|
+
| `get(Class)` | Return the component instance, or `undefined`. |
|
|
415
|
+
| `remove(Class)` | Detach a component (fires `onRemove` and `exit`). |
|
|
416
|
+
| `destroy()` | Remove all components, unregister from the world, recurse into children. |
|
|
417
|
+
| `components` | `ReadonlyArrayMap<Component>` — read-only view of attached components keyed by type id. Supports `forEach`, `get`, `has`, and `size`. |
|
|
418
|
+
| `empty` | `true` when no components are attached. |
|
|
419
|
+
| `parent` | Parent entity, or `undefined` for a root entity. |
|
|
420
|
+
| `children` | `ReadonlySet<Entity>` of direct children (lazy). |
|
|
421
|
+
| `setParent(p)` | Reparent the entity. `undefined` makes it a root entity. Throws on cycles. |
|
|
422
|
+
| `events` | Typed event emitter. Currently emits `"destroy"` just before teardown. |
|
|
423
|
+
| `toString()` | Returns `"EntityN"`. |
|
|
424
|
+
|
|
425
|
+
Call `entity.modified(C)` after mutating a component directly. Repeated calls for the same component type are coalesced while the world is deferred:
|
|
414
426
|
|
|
415
427
|
```ts
|
|
416
428
|
const vel = entity.get(Velocity)!;
|
|
417
429
|
vel.vx += accel;
|
|
418
|
-
entity.modified(
|
|
430
|
+
entity.modified(Velocity); // chainable
|
|
419
431
|
```
|
|
420
432
|
|
|
421
433
|
#### Parent / child hierarchy
|
|
@@ -462,6 +474,8 @@ Declare which entities the system tracks.
|
|
|
462
474
|
| A single class / id | Shorthand for `{ HAS: [C] }` |
|
|
463
475
|
| A predicate function | Custom membership logic |
|
|
464
476
|
|
|
477
|
+
Class-valued query terms are recognized as components by looking up registered metadata for the current world. Register component classes before using them in a `QueryDSL`; an unregistered class is treated like a predicate function and will fail if it cannot be called that way.
|
|
478
|
+
|
|
465
479
|
**Type inference.** `requires()` records the listed classes as a type parameter `R` on the system. Callbacks in `.sort()`, `.each()`, and `.update()` injection treat those components as non-nullable — no `!` needed. For complex `query()` expressions the type system can't introspect, supply a `_guaranteed` second argument:
|
|
466
480
|
|
|
467
481
|
```ts
|
|
@@ -506,12 +520,12 @@ Fires when an entity leaves the system (component removed or entity destroyed).
|
|
|
506
520
|
|
|
507
521
|
#### `.update(ComponentClass, callback)` / `.update(ComponentClass, inject, callback)`
|
|
508
522
|
|
|
509
|
-
Fires when `
|
|
523
|
+
Fires when `entity.modified(ComponentClass)` is called for the watched component on a tracked entity. It also fires for `entity.set(C, props)` on an already-attached component and for initial watched components when an entity enters the query. The callback receives the entity first because component instances do not carry owner references.
|
|
510
524
|
|
|
511
525
|
```ts
|
|
512
|
-
.update(Position, (pos) => renderer.setPosition(pos.x, pos.y));
|
|
526
|
+
.update(Position, (entity, pos) => renderer.setPosition(entity.eid, pos.x, pos.y));
|
|
513
527
|
|
|
514
|
-
.update(Position, [Sprite], (pos, [sprite]) => {
|
|
528
|
+
.update(Position, [Sprite], (entity, pos, [sprite]) => {
|
|
515
529
|
sprite.sprite.setPosition(pos.x, pos.y);
|
|
516
530
|
});
|
|
517
531
|
```
|
|
@@ -537,7 +551,7 @@ Store matched entities in a custom order determined by `compare`. Implies `.trac
|
|
|
537
551
|
world
|
|
538
552
|
.system("Render")
|
|
539
553
|
.requires(Position, Sprite)
|
|
540
|
-
.sort([Position], ([posA], [posB]) => posA.z - posB.z)
|
|
554
|
+
.sort([Position], (_entityA, [posA], _entityB, [posB]) => posA.z - posB.z)
|
|
541
555
|
.each([Position, Sprite], (e, [pos, sprite]) => sprite.draw(pos.x, pos.y));
|
|
542
556
|
```
|
|
543
557
|
|
|
@@ -585,7 +599,7 @@ Both methods return `this` for chaining and are idempotent (calling `disable()`
|
|
|
585
599
|
const projectiles = world
|
|
586
600
|
.query("Projectiles")
|
|
587
601
|
.requires(Position, Velocity)
|
|
588
|
-
.sort([Position], ([a], [b]) => a.z - b.z)
|
|
602
|
+
.sort([Position], (_entityA, [a], _entityB, [b]) => a.z - b.z)
|
|
589
603
|
.enter([Position], (e, [pos]) => {
|
|
590
604
|
pos.x = spawnX;
|
|
591
605
|
});
|
|
@@ -596,20 +610,20 @@ projectiles.forEach((e) => { ... });
|
|
|
596
610
|
console.log(projectiles.entities.size, "active projectiles");
|
|
597
611
|
```
|
|
598
612
|
|
|
599
|
-
| Method | Description
|
|
600
|
-
| ------------------------------------------------------- |
|
|
601
|
-
| `.requires(...components)` | Set the membership predicate to `HAS(...components)` and start tracking.
|
|
602
|
-
| `.query(expr, _guaranteed?)` | Set the membership predicate using a `QueryDSL` expression.
|
|
603
|
-
| `.enter(callback)` / `.enter(inject, callback)` | Fires when an entity joins the query.
|
|
604
|
-
| `.exit(callback)` / `.exit(inject, callback)` | Fires when an entity leaves the query.
|
|
605
|
-
| `.update(C, callback)` / `.update(C, inject, callback)` | Fires when `C` is modified on a tracked entity.
|
|
606
|
-
| `.sort(components, compare)` | Store matched entities in sorted order.
|
|
607
|
-
| `.track()` | Enable tracking. Backfills when called after `start()`.
|
|
608
|
-
| `.belongs(e)` | Returns `true` if the entity satisfies the predicate.
|
|
609
|
-
| `.forEach(callback)` | Iterate currently tracked entities.
|
|
610
|
-
| `.forEach(components, callback)` | Iterate with component injection.
|
|
611
|
-
| `.entities` | `ReadonlySet<Entity>` of currently tracked entities.
|
|
612
|
-
| `.destroy()` | Remove the query from the world and from every entity (no exit fires).
|
|
613
|
+
| Method | Description |
|
|
614
|
+
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
615
|
+
| `.requires(...components)` | Set the membership predicate to `HAS(...components)` and start tracking. |
|
|
616
|
+
| `.query(expr, _guaranteed?)` | Set the membership predicate using a `QueryDSL` expression. |
|
|
617
|
+
| `.enter(callback)` / `.enter(inject, callback)` | Fires when an entity joins the query. |
|
|
618
|
+
| `.exit(callback)` / `.exit(inject, callback)` | Fires when an entity leaves the query. |
|
|
619
|
+
| `.update(C, callback)` / `.update(C, inject, callback)` | Fires when `C` is modified on a tracked entity. Callback receives `(entity, component, injected?)`. |
|
|
620
|
+
| `.sort(components, compare)` | Store matched entities in sorted order. Comparator receives `(entityA, tupleA, entityB, tupleB)`. |
|
|
621
|
+
| `.track()` | Enable tracking. Backfills when called after `start()`. |
|
|
622
|
+
| `.belongs(e)` | Returns `true` if the entity satisfies the predicate. |
|
|
623
|
+
| `.forEach(callback)` | Iterate currently tracked entities. |
|
|
624
|
+
| `.forEach(components, callback)` | Iterate with component injection. |
|
|
625
|
+
| `.entities` | `ReadonlySet<Entity>` of currently tracked entities. |
|
|
626
|
+
| `.destroy()` | Remove the query from the world and from every entity (no exit fires). |
|
|
613
627
|
|
|
614
628
|
#### `.destroy()` semantics
|
|
615
629
|
|
|
@@ -663,6 +677,7 @@ A compact, growable set of non-negative integers backed by 32-bit words. Used in
|
|
|
663
677
|
| `add(n)` | Set bit `n`. |
|
|
664
678
|
| `addBit(bptr)` | Set the bit at a pre-computed `BitPtr`. |
|
|
665
679
|
| `delete(n)` | Clear bit `n`. Trims trailing zero words. |
|
|
680
|
+
| `clear()` | Remove every set bit. |
|
|
666
681
|
| `has(n)` | Returns `true` if bit `n` is set. |
|
|
667
682
|
| `hasBit(bptr)` | Fast check via a pre-computed `BitPtr`. |
|
|
668
683
|
| `equal(other)` | Returns `true` when both bitsets have the same bits set. |
|
|
@@ -671,7 +686,7 @@ A compact, growable set of non-negative integers backed by 32-bit words. Used in
|
|
|
671
686
|
| `indices()` | Return all set bit indices as a `number[]`. |
|
|
672
687
|
|
|
673
688
|
```ts
|
|
674
|
-
class Tags
|
|
689
|
+
class Tags {
|
|
675
690
|
tags = new Bitset();
|
|
676
691
|
}
|
|
677
692
|
|
package/dist/component.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { BitPtr } from "./util/bitset.js";
|
|
2
2
|
import type { Entity } from "./entity.js";
|
|
3
|
+
/** A component instance. Components are plain objects created with a no-arg constructor. */
|
|
4
|
+
export type Component = object;
|
|
5
|
+
/** A component class constructor. */
|
|
6
|
+
export type ComponentClass<T extends Component = Component> = new () => T;
|
|
7
|
+
/** A component class constructor or its numeric type id. */
|
|
8
|
+
export type ComponentClassOrType = number | ComponentClass;
|
|
3
9
|
/**
|
|
4
10
|
* Lifecycle hook for a registered component class. Obtained via
|
|
5
11
|
* {@link World.hook}.
|
|
@@ -11,16 +17,16 @@ import type { Entity } from "./entity.js";
|
|
|
11
17
|
*
|
|
12
18
|
* ```ts
|
|
13
19
|
* world.hook(Sprite)
|
|
14
|
-
* .onAdd(c => initSprite(c))
|
|
15
|
-
* .onRemove(c => destroySprite(c))
|
|
16
|
-
* .onSet(c => syncSprite(c));
|
|
20
|
+
* .onAdd((e, c) => initSprite(e, c))
|
|
21
|
+
* .onRemove((e, c) => destroySprite(e, c))
|
|
22
|
+
* .onSet((e, c) => syncSprite(e, c));
|
|
17
23
|
* ```
|
|
18
24
|
*
|
|
19
25
|
* Callbacks fire synchronously when the corresponding entity command is
|
|
20
26
|
* applied: inline outside deferred mode, or while the world drains its command
|
|
21
27
|
* queue inside a system / `forEach` / `defer` block.
|
|
22
28
|
*
|
|
23
|
-
* @typeParam C - Component
|
|
29
|
+
* @typeParam C - Component class this hook is bound to.
|
|
24
30
|
*/
|
|
25
31
|
export interface Hook<C extends Component = Component> {
|
|
26
32
|
/**
|
|
@@ -28,29 +34,29 @@ export interface Hook<C extends Component = Component> {
|
|
|
28
34
|
* to an entity (`entity.add(C)` or `entity.set(C, ...)` on an entity that
|
|
29
35
|
* does not yet have the component).
|
|
30
36
|
*
|
|
31
|
-
* @param handler - Receives the freshly created component instance.
|
|
37
|
+
* @param handler - Receives the entity and freshly created component instance.
|
|
32
38
|
* @returns This hook, for chaining.
|
|
33
39
|
*/
|
|
34
|
-
onAdd(handler: (c: C) => void): Hook<C>;
|
|
40
|
+
onAdd(handler: (entity: Entity, c: C) => void): Hook<C>;
|
|
35
41
|
/**
|
|
36
42
|
* Register a handler invoked when a component of this type is removed from
|
|
37
43
|
* an entity (explicit `entity.remove(C)` or implicit removal during
|
|
38
44
|
* `entity.destroy()`).
|
|
39
45
|
*
|
|
40
|
-
* @param handler - Receives the component instance that was removed.
|
|
46
|
+
* @param handler - Receives the entity and component instance that was removed.
|
|
41
47
|
* @returns This hook, for chaining.
|
|
42
48
|
*/
|
|
43
|
-
onRemove(handler: (c: C) => void): Hook<C>;
|
|
49
|
+
onRemove(handler: (entity: Entity, c: C) => void): Hook<C>;
|
|
44
50
|
/**
|
|
45
51
|
* Register a handler invoked when a component's data has been marked as
|
|
46
|
-
* changed (`
|
|
52
|
+
* changed (`entity.modified(C)`), and when
|
|
47
53
|
* `entity.set(C, props)` is called on an entity that already has the
|
|
48
54
|
* component.
|
|
49
55
|
*
|
|
50
|
-
* @param handler - Receives the component instance whose data changed.
|
|
56
|
+
* @param handler - Receives the entity and component instance whose data changed.
|
|
51
57
|
* @returns This hook, for chaining.
|
|
52
58
|
*/
|
|
53
|
-
onSet(handler: (c: C) => void): Hook<C>;
|
|
59
|
+
onSet(handler: (entity: Entity, c: C) => void): Hook<C>;
|
|
54
60
|
}
|
|
55
61
|
/**
|
|
56
62
|
* Bookkeeping record produced for each component class registered via
|
|
@@ -63,72 +69,18 @@ export interface Hook<C extends Component = Component> {
|
|
|
63
69
|
*/
|
|
64
70
|
export declare class ComponentMeta implements Hook<Component> {
|
|
65
71
|
/** The component class constructor this meta represents. */
|
|
66
|
-
readonly Class:
|
|
72
|
+
readonly Class: ComponentClass;
|
|
67
73
|
/** Numeric type id assigned at registration time. */
|
|
68
74
|
readonly type: number;
|
|
69
75
|
/** Human-readable name used in logs and serialization lookups. */
|
|
70
76
|
readonly componentName: string;
|
|
71
77
|
/** Pre-computed bit-pointer into the entity archetype {@link Bitset}. */
|
|
72
78
|
readonly bitPtr: BitPtr;
|
|
73
|
-
|
|
74
|
-
* Type ids of components that cannot coexist with this one on the same
|
|
75
|
-
* entity. Set via {@link World.setExclusiveComponents}; `undefined` means
|
|
76
|
-
* no restriction.
|
|
77
|
-
*/
|
|
78
|
-
exclusive: number[] | undefined;
|
|
79
|
-
constructor(Class: typeof Component, type: number, componentName: string);
|
|
79
|
+
constructor(Class: ComponentClass, type: number, componentName: string);
|
|
80
80
|
/** @inheritdoc */
|
|
81
|
-
onAdd(handler: (c: Component) => void): ComponentMeta;
|
|
81
|
+
onAdd(handler: (entity: Entity, c: Component) => void): ComponentMeta;
|
|
82
82
|
/** @inheritdoc */
|
|
83
|
-
onRemove(handler: (c: Component) => void): ComponentMeta;
|
|
83
|
+
onRemove(handler: (entity: Entity, c: Component) => void): ComponentMeta;
|
|
84
84
|
/** @inheritdoc */
|
|
85
|
-
onSet(handler: (c: Component) => void): ComponentMeta;
|
|
86
|
-
}
|
|
87
|
-
/** A component class constructor or its numeric type id. */
|
|
88
|
-
export type ComponentClassOrType = number | typeof Component;
|
|
89
|
-
/**
|
|
90
|
-
* Base class for all ECS components.
|
|
91
|
-
*
|
|
92
|
-
* Subclass `Component` to declare data that can be attached to an
|
|
93
|
-
* {@link Entity}. Instances are constructed by the world when
|
|
94
|
-
* {@link Entity.add} or {@link Entity.set} runs — never instantiate manually.
|
|
95
|
-
*
|
|
96
|
-
* ```ts
|
|
97
|
-
* class Position extends Component {
|
|
98
|
-
* x = 0;
|
|
99
|
-
* y = 0;
|
|
100
|
-
* }
|
|
101
|
-
*
|
|
102
|
-
* world.registerComponent(Position);
|
|
103
|
-
* entity.set(Position, { x: 100 });
|
|
104
|
-
* ```
|
|
105
|
-
*
|
|
106
|
-
* Each instance is bound to a single entity via {@link entity}; that link is
|
|
107
|
-
* permanent for the component's lifetime.
|
|
108
|
-
*/
|
|
109
|
-
export declare class Component {
|
|
110
|
-
/** The entity this component belongs to. */
|
|
111
|
-
readonly entity: Entity;
|
|
112
|
-
/** Registration metadata (type id, display name, bit-pointer). */
|
|
113
|
-
readonly meta: ComponentMeta;
|
|
114
|
-
constructor(
|
|
115
|
-
/** The entity this component belongs to. */
|
|
116
|
-
entity: Entity,
|
|
117
|
-
/** Registration metadata (type id, display name, bit-pointer). */
|
|
118
|
-
meta: ComponentMeta);
|
|
119
|
-
/** Numeric type id — shorthand for `this.meta.type`. */
|
|
120
|
-
get type(): number;
|
|
121
|
-
/** Pre-computed bit-pointer — shorthand for `this.meta.bitPtr`. */
|
|
122
|
-
get bitPtr(): BitPtr;
|
|
123
|
-
/**
|
|
124
|
-
* Notify the world that this component's data has changed.
|
|
125
|
-
*
|
|
126
|
-
* Queues a modified event that fires `update` callbacks on every system /
|
|
127
|
-
* query that watches this component type, plus the component's `onSet`
|
|
128
|
-
* hook. Repeated calls before the world drains its queue are coalesced
|
|
129
|
-
* into one delivery.
|
|
130
|
-
*/
|
|
131
|
-
modified(): void;
|
|
132
|
-
/** Returns the component's registered display name (e.g. `"Position"`). */
|
|
133
|
-
toString(): string;
|
|
85
|
+
onSet(handler: (entity: Entity, c: Component) => void): ComponentMeta;
|
|
134
86
|
}
|
package/dist/component.js
CHANGED
|
@@ -11,11 +11,11 @@ import { BitPtr, Bitset } from "./util/bitset.js";
|
|
|
11
11
|
export class ComponentMeta {
|
|
12
12
|
constructor(Class, type, componentName) {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
* entity. Set via {@link World.setExclusiveComponents}; `undefined`
|
|
16
|
-
* no restriction.
|
|
14
|
+
* @internal Peer metas of components that cannot coexist with this one on
|
|
15
|
+
* the same entity. Set via {@link World.setExclusiveComponents}; `undefined`
|
|
16
|
+
* means no restriction.
|
|
17
17
|
*/
|
|
18
|
-
this.
|
|
18
|
+
this._exclusive = undefined;
|
|
19
19
|
this.Class = Class;
|
|
20
20
|
this.type = type;
|
|
21
21
|
this.componentName = componentName;
|
|
@@ -37,61 +37,6 @@ export class ComponentMeta {
|
|
|
37
37
|
return this;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Base class for all ECS components.
|
|
42
|
-
*
|
|
43
|
-
* Subclass `Component` to declare data that can be attached to an
|
|
44
|
-
* {@link Entity}. Instances are constructed by the world when
|
|
45
|
-
* {@link Entity.add} or {@link Entity.set} runs — never instantiate manually.
|
|
46
|
-
*
|
|
47
|
-
* ```ts
|
|
48
|
-
* class Position extends Component {
|
|
49
|
-
* x = 0;
|
|
50
|
-
* y = 0;
|
|
51
|
-
* }
|
|
52
|
-
*
|
|
53
|
-
* world.registerComponent(Position);
|
|
54
|
-
* entity.set(Position, { x: 100 });
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
|
-
* Each instance is bound to a single entity via {@link entity}; that link is
|
|
58
|
-
* permanent for the component's lifetime.
|
|
59
|
-
*/
|
|
60
|
-
export class Component {
|
|
61
|
-
constructor(
|
|
62
|
-
/** The entity this component belongs to. */
|
|
63
|
-
entity,
|
|
64
|
-
/** Registration metadata (type id, display name, bit-pointer). */
|
|
65
|
-
meta) {
|
|
66
|
-
this.entity = entity;
|
|
67
|
-
this.meta = meta;
|
|
68
|
-
/** @internal Set by {@link Entity.modified} to coalesce repeated calls until the world routes the modified command. */
|
|
69
|
-
this._dirty = false;
|
|
70
|
-
}
|
|
71
|
-
/** Numeric type id — shorthand for `this.meta.type`. */
|
|
72
|
-
get type() {
|
|
73
|
-
return this.meta.type;
|
|
74
|
-
}
|
|
75
|
-
/** Pre-computed bit-pointer — shorthand for `this.meta.bitPtr`. */
|
|
76
|
-
get bitPtr() {
|
|
77
|
-
return this.meta.bitPtr;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Notify the world that this component's data has changed.
|
|
81
|
-
*
|
|
82
|
-
* Queues a modified event that fires `update` callbacks on every system /
|
|
83
|
-
* query that watches this component type, plus the component's `onSet`
|
|
84
|
-
* hook. Repeated calls before the world drains its queue are coalesced
|
|
85
|
-
* into one delivery.
|
|
86
|
-
*/
|
|
87
|
-
modified() {
|
|
88
|
-
this.entity.modified(this);
|
|
89
|
-
}
|
|
90
|
-
/** Returns the component's registered display name (e.g. `"Position"`). */
|
|
91
|
-
toString() {
|
|
92
|
-
return this.meta.componentName;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
40
|
/**
|
|
96
41
|
* Compute a {@link Bitset} with one bit set for every component class or
|
|
97
42
|
* numeric type id in `classes`.
|
package/dist/component.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"component.js","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAoElD;;;;;;;;GAQG;AACH,MAAM,OAAO,aAAa;IAwBxB,YAAY,KAAqB,EAAE,IAAY,EAAE,aAAqB;QAdtE;;;;WAIG;QACI,eAAU,GAAgC,SAAS,CAAC;QAUzD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,kBAAkB;IACX,KAAK,CAAC,OAA+C;QAC1D,CAAC,IAAI,CAAC,cAAc,KAAnB,IAAI,CAAC,cAAc,GAAK,EAAE,EAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kBAAkB;IACX,QAAQ,CAAC,OAA+C;QAC7D,CAAC,IAAI,CAAC,iBAAiB,KAAtB,IAAI,CAAC,iBAAiB,GAAK,EAAE,EAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kBAAkB;IACX,KAAK,CAAC,OAA+C;QAC1D,CAAC,IAAI,CAAC,cAAc,KAAnB,IAAI,CAAC,cAAc,GAAK,EAAE,EAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AASD;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAA4B,EAAE,KAAY;IACnF,MAAM,OAAO,GAAG,IAAI,MAAM,EAAE,CAAC;IAC7B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/dsl.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ComponentClass, ComponentClassArray, ComponentClassOrType } from "./component.js";
|
|
2
2
|
import type { Entity } from "./entity.js";
|
|
3
3
|
/**
|
|
4
4
|
* A predicate that decides whether a given entity belongs to a query.
|
|
@@ -26,9 +26,13 @@ export type EntityTestFunc = (e: Entity) => boolean;
|
|
|
26
26
|
* ```
|
|
27
27
|
*
|
|
28
28
|
* Short forms recognized by `query` / `filter`:
|
|
29
|
-
* - A
|
|
29
|
+
* - A registered component class or numeric type id is shorthand for `{ HAS: [C] }`.
|
|
30
30
|
* - An array `[A, B]` is shorthand for `{ HAS: [A, B] }`.
|
|
31
31
|
* - An {@link EntityTestFunc} is invoked directly for fully custom logic.
|
|
32
|
+
*
|
|
33
|
+
* Function values are treated as component classes only when the world already
|
|
34
|
+
* has registered metadata for that class. Register component classes before
|
|
35
|
+
* using them in query DSL expressions.
|
|
32
36
|
*/
|
|
33
37
|
export type QueryDSL = ComponentClassArray | ComponentClassOrType | EntityTestFunc | {
|
|
34
38
|
HAS: ComponentClassArray | ComponentClassOrType;
|
|
@@ -53,13 +57,13 @@ export type QueryDSL = ComponentClassArray | ComponentClassOrType | EntityTestFu
|
|
|
53
57
|
* @typeParam C - Component class being injected.
|
|
54
58
|
* @typeParam R - Tuple of component classes guaranteed present.
|
|
55
59
|
*/
|
|
56
|
-
export type MaybeRequired<C, R extends
|
|
60
|
+
export type MaybeRequired<C, R extends ComponentClass[]> = C extends ComponentClass ? C extends R[number] ? InstanceType<C> : InstanceType<C> | undefined : never;
|
|
57
61
|
/**
|
|
58
62
|
* Statically extract the component classes that are **guaranteed present** on
|
|
59
63
|
* every entity matched by a {@link QueryDSL} expression.
|
|
60
64
|
*
|
|
61
65
|
* Rules:
|
|
62
|
-
* - Plain class `C` → `[C]`
|
|
66
|
+
* - Plain component class `C` → `[C]`
|
|
63
67
|
* - Plain array `[A, B]` → `[A, B]`
|
|
64
68
|
* - `{ HAS: ... }` / `{ HAS_ONLY: ... }` → recurse into the payload
|
|
65
69
|
* - `{ AND: [q1, q2, ...] }` → concatenate each branch's extraction
|
|
@@ -68,7 +72,7 @@ export type MaybeRequired<C, R extends (typeof Component)[]> = C extends typeof
|
|
|
68
72
|
*
|
|
69
73
|
* @typeParam Q - Query expression to analyse.
|
|
70
74
|
*/
|
|
71
|
-
export type ExtractRequired<Q> = Q extends
|
|
75
|
+
export type ExtractRequired<Q> = Q extends ComponentClass ? [Q] : Q extends readonly ComponentClass[] ? Q : Q extends {
|
|
72
76
|
HAS: infer H;
|
|
73
77
|
} ? ExtractRequired<H> : Q extends {
|
|
74
78
|
HAS_ONLY: infer H;
|
package/dist/dsl.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _calculateComponentBitmask, } from "./component.js";
|
|
2
2
|
/**
|
|
3
3
|
* Build a predicate that returns `true` when an entity has every component
|
|
4
4
|
* type in `components` set on its archetype.
|
|
@@ -43,11 +43,14 @@ function _PARENT(func) {
|
|
|
43
43
|
* @internal Used by `Query`, `System`, and `Filter` to translate user-supplied
|
|
44
44
|
* DSL expressions into the predicate stored on `Query._belongs`.
|
|
45
45
|
*
|
|
46
|
-
* @param world - World used to resolve component classes to type ids.
|
|
46
|
+
* @param world - World used to resolve registered component classes to type ids.
|
|
47
47
|
* @param q - Query expression.
|
|
48
48
|
*/
|
|
49
49
|
export function _buildEntityTest(world, q) {
|
|
50
|
-
if (typeof q === "number"
|
|
50
|
+
if (typeof q === "number") {
|
|
51
|
+
return _HAS(world, q);
|
|
52
|
+
}
|
|
53
|
+
if (typeof q === "function" && world._tryGetComponentMeta(q)) {
|
|
51
54
|
return _HAS(world, q);
|
|
52
55
|
}
|
|
53
56
|
else if (typeof q === "function") {
|
package/dist/dsl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dsl.js","sourceRoot":"","sources":["../src/dsl.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"dsl.js","sourceRoot":"","sources":["../src/dsl.ts"],"names":[],"mappings":"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"}
|