@vworlds/vecs 1.0.9 → 1.0.11
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/.husky/pre-commit +1 -0
- package/README.md +218 -229
- package/dist/command.d.ts +1 -0
- package/dist/command.js +2 -0
- package/dist/command.js.map +1 -0
- package/dist/component.d.ts +51 -59
- package/dist/component.js +31 -25
- package/dist/component.js.map +1 -1
- package/dist/dsl.d.ts +34 -26
- package/dist/dsl.js +46 -20
- package/dist/dsl.js.map +1 -1
- package/dist/entity.d.ts +110 -127
- package/dist/entity.js +323 -164
- package/dist/entity.js.map +1 -1
- package/dist/filter.d.ts +31 -23
- package/dist/filter.js +41 -32
- package/dist/filter.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/package.json +3 -1
- package/dist/phase.d.ts +5 -28
- package/dist/phase.js +11 -10
- package/dist/phase.js.map +1 -1
- package/dist/query.d.ts +128 -94
- package/dist/query.js +254 -145
- package/dist/query.js.map +1 -1
- package/dist/system.d.ts +64 -128
- package/dist/system.js +156 -149
- package/dist/system.js.map +1 -1
- package/dist/util/array_map.d.ts +4 -55
- package/dist/util/array_map.js +35 -37
- package/dist/util/array_map.js.map +1 -1
- package/dist/util/bitset.d.ts +40 -50
- package/dist/util/bitset.js +76 -62
- package/dist/util/bitset.js.map +1 -1
- package/dist/util/events.d.ts +14 -18
- package/dist/util/events.js +24 -3
- package/dist/util/events.js.map +1 -1
- package/dist/util/ordered_set.d.ts +1 -17
- package/dist/util/ordered_set.js +74 -25
- package/dist/util/ordered_set.js.map +1 -1
- package/dist/world.d.ts +222 -201
- package/dist/world.js +394 -323
- package/dist/world.js.map +1 -1
- package/eslint-rules/internal-underscore.js +60 -0
- package/eslint.config.js +5 -0
- package/package.json +3 -1
package/dist/system.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import { ArrayMap } from "./util/array_map.js";
|
|
2
|
-
import { Bitset } from "./util/bitset.js";
|
|
3
1
|
import { Component } from "./component.js";
|
|
4
2
|
import { Query } from "./query.js";
|
|
5
3
|
import { type QueryDSL, type MaybeRequired } from "./dsl.js";
|
|
6
4
|
import type { Entity } from "./entity.js";
|
|
7
|
-
import {
|
|
5
|
+
import { type IPhase } from "./phase.js";
|
|
8
6
|
import { type World } from "./world.js";
|
|
9
7
|
export type { QueryDSL as SystemQuery, EntityTestFunc } from "./dsl.js";
|
|
10
|
-
type ComponentCallback = (c: Component) => void;
|
|
11
8
|
type RunCallback = (now: number, delta: number) => void;
|
|
12
9
|
/**
|
|
13
|
-
* A reactive processor
|
|
10
|
+
* A reactive processor running over a filtered subset of world entities.
|
|
14
11
|
*
|
|
15
|
-
* Systems are created and
|
|
12
|
+
* Systems are created and configured through {@link World.system}:
|
|
16
13
|
*
|
|
17
14
|
* ```ts
|
|
18
15
|
* world.system("Move")
|
|
@@ -23,127 +20,77 @@ type RunCallback = (now: number, delta: number) => void;
|
|
|
23
20
|
* .exit((e) => { console.log("entity left", e.eid); });
|
|
24
21
|
* ```
|
|
25
22
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* {@link World.runPhase}.
|
|
23
|
+
* Every builder method returns `this` for chaining. After registering systems
|
|
24
|
+
* call {@link World.start} once, then drive the loop with
|
|
25
|
+
* {@link World.runPhase} or {@link World.progress}.
|
|
26
|
+
*
|
|
27
|
+
* Internally each system holds a single ordered **inbox** of routed events
|
|
28
|
+
* (`enter`, `exit`, `update`). The world appends to it during command-queue
|
|
29
|
+
* routing; the system replays the inbox at the top of every `_run` so
|
|
30
|
+
* callbacks observe events in arrival order.
|
|
29
31
|
*
|
|
30
32
|
* ### Component injection and type inference
|
|
31
33
|
*
|
|
32
|
-
* `enter`, `exit`, `update`, `each`, and `sort`
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* `enter`, `exit`, `update`, `each`, and `sort` accept an array of component
|
|
35
|
+
* classes resolved from the entity and passed as a typed tuple to the
|
|
36
|
+
* callback. Use `{ parent: SomeComponent }` to resolve from the entity's
|
|
37
|
+
* parent instead of the entity itself.
|
|
38
|
+
*
|
|
39
|
+
* Components declared via {@link requires} (or the `_guaranteed` argument of
|
|
40
|
+
* {@link query}) are tracked as the type parameter `R`. Inside `sort`,
|
|
41
|
+
* `each`, and `update` injection callbacks they are non-nullable; any other
|
|
42
|
+
* component remains `Type | undefined`.
|
|
36
43
|
*
|
|
37
|
-
*
|
|
38
|
-
* {@link query}) are tracked as a type parameter `R` on the system. In
|
|
39
|
-
* `sort`, `each`, and `update` inject callbacks, those components appear as
|
|
40
|
-
* non-nullable; any component not in `R` remains `Type | undefined`.
|
|
44
|
+
* @typeParam R - Component classes guaranteed present on every matched entity.
|
|
41
45
|
*/
|
|
42
46
|
export declare class System<R extends (typeof Component)[] = []> extends Query<R> {
|
|
43
|
-
protected componentUpdateCallbacks: ArrayMap<ComponentCallback>;
|
|
44
|
-
protected eachCallback: ((e: Entity) => void) | undefined;
|
|
45
|
-
private _runCallback;
|
|
46
|
-
private readonly updateQueue;
|
|
47
|
-
/** @internal */
|
|
48
|
-
_phase: string | Phase | undefined;
|
|
49
|
-
protected watchlistBitmask: Bitset;
|
|
50
47
|
constructor(name: string, world: World);
|
|
51
48
|
/**
|
|
52
49
|
* Assign this system to a pipeline phase.
|
|
53
50
|
*
|
|
54
|
-
*
|
|
55
|
-
* {@link
|
|
56
|
-
*
|
|
57
|
-
* are placed in the built-in `"update"` phase.
|
|
51
|
+
* Pass either a phase name (resolved at {@link World.start}) or an
|
|
52
|
+
* {@link IPhase} reference returned from {@link World.addPhase}. Systems
|
|
53
|
+
* with no explicit phase fall into the built-in `"update"` phase.
|
|
58
54
|
*
|
|
59
55
|
* @param p - Phase name or `IPhase` reference.
|
|
60
|
-
* @returns
|
|
56
|
+
* @returns This system, for chaining.
|
|
57
|
+
* @throws When the phase reference is not a `Phase`, or belongs to a
|
|
58
|
+
* different world.
|
|
61
59
|
*/
|
|
62
60
|
phase(p: string | IPhase): this;
|
|
63
|
-
/** @internal Delivers a component-modified notification to this system. */
|
|
64
|
-
notifyModified(c: Component): void;
|
|
65
|
-
/** @internal Fires enter callbacks, adds entity to tracked set, queues component updates. */
|
|
66
|
-
_enter(e: Entity): void;
|
|
67
|
-
/** @internal Fires exit callbacks, removes entity from tracked set, drains update queue. */
|
|
68
|
-
_exit(e: Entity): void;
|
|
69
|
-
/** @internal Execute one tick: run `run`, fire `each`, then drain the update queue. */
|
|
70
|
-
_run(now: number, delta: number): void;
|
|
71
61
|
/**
|
|
72
|
-
* Register a per-tick callback
|
|
73
|
-
*
|
|
62
|
+
* Register a per-tick callback fired every time this system's phase runs,
|
|
63
|
+
* regardless of entity membership.
|
|
74
64
|
*
|
|
75
|
-
* Use
|
|
65
|
+
* Use it for logic that is not driven by component changes — polling,
|
|
76
66
|
* network flushing, global timers, etc.
|
|
77
67
|
*
|
|
78
68
|
* @param callback - Receives `now` (absolute timestamp in ms) and `delta`
|
|
79
|
-
* (ms since the
|
|
80
|
-
* @returns
|
|
69
|
+
* (ms since the previous tick).
|
|
70
|
+
* @returns This system, for chaining.
|
|
81
71
|
*/
|
|
82
72
|
run(callback: RunCallback): this;
|
|
83
73
|
/**
|
|
84
|
-
* Register a callback
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* The system will automatically begin tracking entities that have this
|
|
88
|
-
* component type (equivalent to adding it to a `requires` / `HAS` query)
|
|
89
|
-
* unless a custom {@link query} was already set.
|
|
90
|
-
*
|
|
91
|
-
* @param ComponentClass - The component class to watch.
|
|
92
|
-
* @param callback - Receives the modified component instance.
|
|
93
|
-
* @returns `this` for chaining.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* ```ts
|
|
97
|
-
* world.system("RenderPosition")
|
|
98
|
-
* .update(Position, (pos) => {
|
|
99
|
-
* sprite.setPosition(pos.x, pos.y);
|
|
100
|
-
* });
|
|
101
|
-
* ```
|
|
102
|
-
*/
|
|
103
|
-
update<C extends typeof Component>(ComponentClass: C, callback: (c: InstanceType<C>) => void): this;
|
|
104
|
-
/**
|
|
105
|
-
* Register a callback that fires when `ComponentClass` is modified, with
|
|
106
|
-
* additional components injected from the same entity.
|
|
107
|
-
*
|
|
108
|
-
* @param ComponentClass - The component class to watch.
|
|
109
|
-
* @param inject - Additional component classes to resolve from the entity.
|
|
110
|
-
* @param callback - Receives the modified component and the injected tuple.
|
|
111
|
-
* @returns `this` for chaining.
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```ts
|
|
115
|
-
* world.system("SyncSprite")
|
|
116
|
-
* .update(Position, [Sprite], (pos, [sprite]) => {
|
|
117
|
-
* sprite.sprite.setPosition(pos.x, pos.y);
|
|
118
|
-
* });
|
|
119
|
-
* ```
|
|
120
|
-
*/
|
|
121
|
-
update<C extends typeof Component, J extends (typeof Component)[]>(ComponentClass: C, inject: readonly [...J], callback: (c: InstanceType<C>, injected: {
|
|
122
|
-
[K in keyof J]: MaybeRequired<J[K], R>;
|
|
123
|
-
}) => void): this;
|
|
124
|
-
/**
|
|
125
|
-
* Register a callback that fires **every tick** for every entity currently
|
|
126
|
-
* tracked by this system, with the listed components resolved from each
|
|
127
|
-
* entity.
|
|
74
|
+
* Register a callback fired **every tick** for **every tracked entity**,
|
|
75
|
+
* unconditionally, with the listed components resolved from each entity.
|
|
128
76
|
*
|
|
129
77
|
* Unlike {@link update} (which only fires when `component.modified()` is
|
|
130
|
-
* called), `each` fires
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* may be `undefined` if the entity lacks it.
|
|
78
|
+
* called), `each` fires every tick the system runs, once per tracked entity.
|
|
79
|
+
* Components declared via {@link requires} are non-nullable in the resolved
|
|
80
|
+
* tuple; any other component class may be `undefined` if the entity lacks it.
|
|
134
81
|
*
|
|
135
82
|
* `each` does **not** modify the system's query — define membership with
|
|
136
83
|
* {@link requires} or {@link query} as usual. It does, however, implicitly
|
|
137
84
|
* enable {@link track}, so matched entities are exposed via {@link entities}.
|
|
138
85
|
*
|
|
139
|
-
* Only
|
|
140
|
-
*
|
|
86
|
+
* Only one `each` callback may be registered per system; calling `each` a
|
|
87
|
+
* second time throws.
|
|
141
88
|
*
|
|
142
89
|
* @param components - Component classes to resolve from each entity.
|
|
143
90
|
* @param callback - Receives the entity and a tuple of resolved component
|
|
144
|
-
* instances (`undefined` for
|
|
145
|
-
* @returns
|
|
146
|
-
* @throws
|
|
91
|
+
* instances (`undefined` for any not covered by {@link requires}).
|
|
92
|
+
* @returns This system, for chaining.
|
|
93
|
+
* @throws When `each` has already been registered on this system.
|
|
147
94
|
*
|
|
148
95
|
* @example
|
|
149
96
|
* ```ts
|
|
@@ -159,50 +106,39 @@ export declare class System<R extends (typeof Component)[] = []> extends Query<R
|
|
|
159
106
|
[K in keyof J]: MaybeRequired<J[K], R>;
|
|
160
107
|
}) => void): this;
|
|
161
108
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* Systems are owned by the world for the duration of the session. If you
|
|
165
|
-
* need a temporary reactive set, use a standalone {@link Query} instead.
|
|
166
|
-
*/
|
|
167
|
-
destroy(): never;
|
|
168
|
-
/**
|
|
169
|
-
* Set the entity membership predicate using the {@link QueryDSL} DSL.
|
|
109
|
+
* Set the entity-membership predicate using a {@link QueryDSL} expression.
|
|
170
110
|
*
|
|
171
111
|
* Replaces any implicit query derived from `update` watchlists and any
|
|
172
|
-
* previous `requires` call. After calling `query`, auto-
|
|
173
|
-
* `update`
|
|
112
|
+
* previous `requires` call. After calling `query`, watchlist auto-expansion
|
|
113
|
+
* via `update` is disabled.
|
|
174
114
|
*
|
|
175
|
-
* The optional `
|
|
176
|
-
*
|
|
177
|
-
* to be present on every matched entity, eliminating `| undefined` from
|
|
178
|
-
* those positions. It has no effect at runtime.
|
|
115
|
+
* The optional `_guaranteed` tuple is a pure type-level hint — see
|
|
116
|
+
* {@link Query.query} for details.
|
|
179
117
|
*
|
|
180
|
-
* @param q -
|
|
118
|
+
* @param q - Query expression.
|
|
181
119
|
* @param _guaranteed - Component classes guaranteed present on every matched
|
|
182
120
|
* entity (type hint only — not validated at runtime).
|
|
183
|
-
* @returns
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
* ```ts
|
|
187
|
-
* world.system("Move")
|
|
188
|
-
* .query({ AND: [{ HAS: Position }, { HAS: Velocity }] }, [Position, Velocity])
|
|
189
|
-
* .each([Position, Velocity], (e, [pos, vel]) => {
|
|
190
|
-
* pos.x += vel.vx; // no ! needed
|
|
191
|
-
* });
|
|
192
|
-
* ```
|
|
121
|
+
* @returns This system, retyped with the guaranteed tuple as its `R`.
|
|
193
122
|
*/
|
|
194
123
|
query<T extends (typeof Component)[] = []>(q: QueryDSL, _guaranteed?: readonly [...T]): System<T>;
|
|
195
124
|
/**
|
|
196
|
-
* Shorthand for `query([...components])` —
|
|
197
|
-
*
|
|
125
|
+
* Shorthand for `query([...components])` — tracks entities that have **all**
|
|
126
|
+
* of the listed component types.
|
|
198
127
|
*
|
|
199
|
-
* Equivalent to `query({ HAS: components })`.
|
|
200
|
-
*
|
|
201
|
-
* {@link
|
|
202
|
-
* tuples.
|
|
128
|
+
* Equivalent to `query({ HAS: components })`. The listed components are also
|
|
129
|
+
* recorded in the type parameter `R`, so {@link sort}, {@link each}, and
|
|
130
|
+
* {@link update} callbacks treat them as non-nullable.
|
|
203
131
|
*
|
|
204
|
-
* @param components -
|
|
205
|
-
* @returns
|
|
132
|
+
* @param components - Component classes to require.
|
|
133
|
+
* @returns This system, retyped with the required tuple as its `R`.
|
|
206
134
|
*/
|
|
207
135
|
requires<T extends (typeof Component)[]>(...components: [...T]): System<T>;
|
|
136
|
+
/**
|
|
137
|
+
* Not supported on `System`. Throws unconditionally.
|
|
138
|
+
*
|
|
139
|
+
* Systems are owned by the world for the duration of the session; if you
|
|
140
|
+
* need a temporary reactive set use a standalone {@link Query} via
|
|
141
|
+
* {@link World.query}.
|
|
142
|
+
*/
|
|
143
|
+
destroy(): never;
|
|
208
144
|
}
|
package/dist/system.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { ArrayMap } from "./util/array_map.js";
|
|
2
|
-
import { Bitset } from "./util/bitset.js";
|
|
3
1
|
import { Query } from "./query.js";
|
|
4
|
-
import { HAS } from "./dsl.js";
|
|
5
2
|
import { Phase } from "./phase.js";
|
|
6
3
|
/**
|
|
7
|
-
* A reactive processor
|
|
4
|
+
* A reactive processor running over a filtered subset of world entities.
|
|
8
5
|
*
|
|
9
|
-
* Systems are created and
|
|
6
|
+
* Systems are created and configured through {@link World.system}:
|
|
10
7
|
*
|
|
11
8
|
* ```ts
|
|
12
9
|
* world.system("Move")
|
|
@@ -17,39 +14,133 @@ import { Phase } from "./phase.js";
|
|
|
17
14
|
* .exit((e) => { console.log("entity left", e.eid); });
|
|
18
15
|
* ```
|
|
19
16
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* {@link World.runPhase}.
|
|
17
|
+
* Every builder method returns `this` for chaining. After registering systems
|
|
18
|
+
* call {@link World.start} once, then drive the loop with
|
|
19
|
+
* {@link World.runPhase} or {@link World.progress}.
|
|
20
|
+
*
|
|
21
|
+
* Internally each system holds a single ordered **inbox** of routed events
|
|
22
|
+
* (`enter`, `exit`, `update`). The world appends to it during command-queue
|
|
23
|
+
* routing; the system replays the inbox at the top of every `_run` so
|
|
24
|
+
* callbacks observe events in arrival order.
|
|
23
25
|
*
|
|
24
26
|
* ### Component injection and type inference
|
|
25
27
|
*
|
|
26
|
-
* `enter`, `exit`, `update`, `each`, and `sort`
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
* `enter`, `exit`, `update`, `each`, and `sort` accept an array of component
|
|
29
|
+
* classes resolved from the entity and passed as a typed tuple to the
|
|
30
|
+
* callback. Use `{ parent: SomeComponent }` to resolve from the entity's
|
|
31
|
+
* parent instead of the entity itself.
|
|
32
|
+
*
|
|
33
|
+
* Components declared via {@link requires} (or the `_guaranteed` argument of
|
|
34
|
+
* {@link query}) are tracked as the type parameter `R`. Inside `sort`,
|
|
35
|
+
* `each`, and `update` injection callbacks they are non-nullable; any other
|
|
36
|
+
* component remains `Type | undefined`.
|
|
30
37
|
*
|
|
31
|
-
*
|
|
32
|
-
* {@link query}) are tracked as a type parameter `R` on the system. In
|
|
33
|
-
* `sort`, `each`, and `update` inject callbacks, those components appear as
|
|
34
|
-
* non-nullable; any component not in `R` remains `Type | undefined`.
|
|
38
|
+
* @typeParam R - Component classes guaranteed present on every matched entity.
|
|
35
39
|
*/
|
|
36
40
|
export class System extends Query {
|
|
37
41
|
constructor(name, world) {
|
|
38
42
|
super(name, world, false);
|
|
39
|
-
|
|
40
|
-
this.
|
|
41
|
-
|
|
43
|
+
/** @internal Single inbox replayed in arrival order on every `_run`. */
|
|
44
|
+
this._inbox = [];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @internal Routing entry: register query membership for `e`, push an
|
|
48
|
+
* inbox `enter` event when an `enter` callback is registered, and bridge
|
|
49
|
+
* watched components through {@link _notifyModified} to surface them as
|
|
50
|
+
* inbox `update` events on entry.
|
|
51
|
+
*/
|
|
52
|
+
_enter(e) {
|
|
53
|
+
this._entities?.add(e);
|
|
54
|
+
e._addQueryMembership(this);
|
|
55
|
+
if (this._enterCallback !== undefined) {
|
|
56
|
+
this._inbox.push({ kind: 0 /* InboxCommand.Enter */, entity: e });
|
|
57
|
+
}
|
|
58
|
+
e.components.forEach((c) => {
|
|
59
|
+
if (this._watchlistBitmask.hasBit(c.bitPtr)) {
|
|
60
|
+
this._notifyModified(c);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* @internal Routing entry: deregister query membership for `e` and push an
|
|
66
|
+
* inbox `exit` event when an `exit` callback is registered, capturing a
|
|
67
|
+
* snapshot of the components the callback wants to inject so they remain
|
|
68
|
+
* resolvable after the underlying components are removed.
|
|
69
|
+
*/
|
|
70
|
+
_exit(e) {
|
|
71
|
+
this._entities?.delete(e);
|
|
72
|
+
e._removeQueryMembership(this);
|
|
73
|
+
if (this._exitCallback !== undefined) {
|
|
74
|
+
let snapshot;
|
|
75
|
+
if (this._exitSnapshotTypes && this._exitSnapshotTypes.length > 0) {
|
|
76
|
+
snapshot = new Map();
|
|
77
|
+
for (const type of this._exitSnapshotTypes) {
|
|
78
|
+
const c = e._get(type);
|
|
79
|
+
if (c) {
|
|
80
|
+
snapshot.set(type, c);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
this._inbox.push({ kind: 1 /* InboxCommand.Exit */, entity: e, snapshot });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* @internal Routing entry: push an inbox `update` event when the modified
|
|
89
|
+
* component matches the watchlist.
|
|
90
|
+
*/
|
|
91
|
+
_notifyModified(c) {
|
|
92
|
+
if (!this._watchlistBitmask.hasBit(c.bitPtr)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this._inbox.push({ kind: 2 /* InboxCommand.Update */, component: c });
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* @internal Execute one tick: drain the inbox in arrival order, then run
|
|
99
|
+
* the `run` callback, then the `each` callback for every tracked entity.
|
|
100
|
+
*
|
|
101
|
+
* The whole body executes inside a `World.defer` scope; mutations made by
|
|
102
|
+
* callbacks land in the world queue and are processed when `_run` returns.
|
|
103
|
+
*/
|
|
104
|
+
_run(now, delta) {
|
|
105
|
+
this.world.defer(() => {
|
|
106
|
+
for (let i = 0; i < this._inbox.length; i++) {
|
|
107
|
+
const event = this._inbox[i];
|
|
108
|
+
switch (event.kind) {
|
|
109
|
+
case 0 /* InboxCommand.Enter */:
|
|
110
|
+
this._enterCallback(event.entity);
|
|
111
|
+
break;
|
|
112
|
+
case 1 /* InboxCommand.Exit */:
|
|
113
|
+
this._exitCallback(event.entity, event.snapshot);
|
|
114
|
+
break;
|
|
115
|
+
case 2 /* InboxCommand.Update */:
|
|
116
|
+
const callback = this._componentUpdateCallbacks.get(event.component.type);
|
|
117
|
+
if (callback) {
|
|
118
|
+
callback(event.component);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this._inbox.length = 0;
|
|
124
|
+
if (this._runCallback) {
|
|
125
|
+
this._runCallback(now, delta);
|
|
126
|
+
}
|
|
127
|
+
if (this._eachCallback) {
|
|
128
|
+
const cb = this._eachCallback;
|
|
129
|
+
this.forEach((e) => cb(e));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
42
132
|
}
|
|
43
133
|
/**
|
|
44
134
|
* Assign this system to a pipeline phase.
|
|
45
135
|
*
|
|
46
|
-
*
|
|
47
|
-
* {@link
|
|
48
|
-
*
|
|
49
|
-
* are placed in the built-in `"update"` phase.
|
|
136
|
+
* Pass either a phase name (resolved at {@link World.start}) or an
|
|
137
|
+
* {@link IPhase} reference returned from {@link World.addPhase}. Systems
|
|
138
|
+
* with no explicit phase fall into the built-in `"update"` phase.
|
|
50
139
|
*
|
|
51
140
|
* @param p - Phase name or `IPhase` reference.
|
|
52
|
-
* @returns
|
|
141
|
+
* @returns This system, for chaining.
|
|
142
|
+
* @throws When the phase reference is not a `Phase`, or belongs to a
|
|
143
|
+
* different world.
|
|
53
144
|
*/
|
|
54
145
|
phase(p) {
|
|
55
146
|
if (typeof p !== "string") {
|
|
@@ -63,115 +154,42 @@ export class System extends Query {
|
|
|
63
154
|
this._phase = p;
|
|
64
155
|
return this;
|
|
65
156
|
}
|
|
66
|
-
/** @internal Delivers a component-modified notification to this system. */
|
|
67
|
-
notifyModified(c) {
|
|
68
|
-
if (!this.watchlistBitmask.hasBit(c.bitPtr)) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
this.updateQueue.push(c);
|
|
72
|
-
}
|
|
73
|
-
/** @internal Fires enter callbacks, adds entity to tracked set, queues component updates. */
|
|
74
|
-
_enter(e) {
|
|
75
|
-
super._enter(e);
|
|
76
|
-
e.forEachComponent((c) => this.notifyModified(c));
|
|
77
|
-
}
|
|
78
|
-
/** @internal Fires exit callbacks, removes entity from tracked set, drains update queue. */
|
|
79
|
-
_exit(e) {
|
|
80
|
-
super._exit(e);
|
|
81
|
-
this.updateQueue.forEach((c, i) => {
|
|
82
|
-
if (!c) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (c.entity === e) {
|
|
86
|
-
this.updateQueue[i] = undefined;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
/** @internal Execute one tick: run `run`, fire `each`, then drain the update queue. */
|
|
91
|
-
_run(now, delta) {
|
|
92
|
-
if (this._runCallback) {
|
|
93
|
-
this._runCallback(now, delta);
|
|
94
|
-
}
|
|
95
|
-
if (this.eachCallback) {
|
|
96
|
-
const cb = this.eachCallback;
|
|
97
|
-
this.forEach((e) => cb(e));
|
|
98
|
-
}
|
|
99
|
-
this.updateQueue.forEach((c) => {
|
|
100
|
-
if (!c) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const callback = this.componentUpdateCallbacks.get(c.type);
|
|
104
|
-
if (callback) {
|
|
105
|
-
callback(c);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
this.updateQueue.length = 0;
|
|
109
|
-
}
|
|
110
157
|
/**
|
|
111
|
-
* Register a per-tick callback
|
|
112
|
-
*
|
|
158
|
+
* Register a per-tick callback fired every time this system's phase runs,
|
|
159
|
+
* regardless of entity membership.
|
|
113
160
|
*
|
|
114
|
-
* Use
|
|
161
|
+
* Use it for logic that is not driven by component changes — polling,
|
|
115
162
|
* network flushing, global timers, etc.
|
|
116
163
|
*
|
|
117
164
|
* @param callback - Receives `now` (absolute timestamp in ms) and `delta`
|
|
118
|
-
* (ms since the
|
|
119
|
-
* @returns
|
|
165
|
+
* (ms since the previous tick).
|
|
166
|
+
* @returns This system, for chaining.
|
|
120
167
|
*/
|
|
121
168
|
run(callback) {
|
|
122
169
|
this._runCallback = callback;
|
|
123
170
|
return this;
|
|
124
171
|
}
|
|
125
|
-
update(ComponentClass, injectOrCallback, callback) {
|
|
126
|
-
const type = this.world.getComponentType(ComponentClass);
|
|
127
|
-
if (typeof injectOrCallback === "function") {
|
|
128
|
-
callback = injectOrCallback;
|
|
129
|
-
this.componentUpdateCallbacks.set(type, callback);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
const inject = injectOrCallback;
|
|
133
|
-
const injectedComponentTypes = inject.map((C) => this.world.getComponentType(C));
|
|
134
|
-
const cb = (c) => {
|
|
135
|
-
const injected = [];
|
|
136
|
-
injectedComponentTypes.forEach((InjectedComponentType) => {
|
|
137
|
-
injected.push(c.entity.get(InjectedComponentType));
|
|
138
|
-
});
|
|
139
|
-
if (callback) {
|
|
140
|
-
callback(c, injected);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
this.componentUpdateCallbacks.set(type, cb);
|
|
144
|
-
}
|
|
145
|
-
this.watchlistBitmask.add(type);
|
|
146
|
-
if (!this.hasQuery) {
|
|
147
|
-
const watchlist = this.watchlistBitmask.indices();
|
|
148
|
-
this._belongs = HAS(this.world, ...watchlist);
|
|
149
|
-
}
|
|
150
|
-
return this;
|
|
151
|
-
}
|
|
152
172
|
/**
|
|
153
|
-
* Register a callback
|
|
154
|
-
*
|
|
155
|
-
* entity.
|
|
173
|
+
* Register a callback fired **every tick** for **every tracked entity**,
|
|
174
|
+
* unconditionally, with the listed components resolved from each entity.
|
|
156
175
|
*
|
|
157
176
|
* Unlike {@link update} (which only fires when `component.modified()` is
|
|
158
|
-
* called), `each` fires
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* may be `undefined` if the entity lacks it.
|
|
177
|
+
* called), `each` fires every tick the system runs, once per tracked entity.
|
|
178
|
+
* Components declared via {@link requires} are non-nullable in the resolved
|
|
179
|
+
* tuple; any other component class may be `undefined` if the entity lacks it.
|
|
162
180
|
*
|
|
163
181
|
* `each` does **not** modify the system's query — define membership with
|
|
164
182
|
* {@link requires} or {@link query} as usual. It does, however, implicitly
|
|
165
183
|
* enable {@link track}, so matched entities are exposed via {@link entities}.
|
|
166
184
|
*
|
|
167
|
-
* Only
|
|
168
|
-
*
|
|
185
|
+
* Only one `each` callback may be registered per system; calling `each` a
|
|
186
|
+
* second time throws.
|
|
169
187
|
*
|
|
170
188
|
* @param components - Component classes to resolve from each entity.
|
|
171
189
|
* @param callback - Receives the entity and a tuple of resolved component
|
|
172
|
-
* instances (`undefined` for
|
|
173
|
-
* @returns
|
|
174
|
-
* @throws
|
|
190
|
+
* instances (`undefined` for any not covered by {@link requires}).
|
|
191
|
+
* @returns This system, for chaining.
|
|
192
|
+
* @throws When `each` has already been registered on this system.
|
|
175
193
|
*
|
|
176
194
|
* @example
|
|
177
195
|
* ```ts
|
|
@@ -184,71 +202,60 @@ export class System extends Query {
|
|
|
184
202
|
* ```
|
|
185
203
|
*/
|
|
186
204
|
each(components, callback) {
|
|
187
|
-
if (this.
|
|
205
|
+
if (this._eachCallback) {
|
|
188
206
|
throw `each already registered for system '${this.name}'`;
|
|
189
207
|
}
|
|
190
208
|
this.track();
|
|
191
209
|
const types = components.map((C) => this.world.getComponentType(C));
|
|
192
|
-
this.
|
|
210
|
+
this._eachCallback = (e) => {
|
|
193
211
|
const resolved = types.map((t) => e.get(t));
|
|
194
212
|
callback(e, resolved);
|
|
195
213
|
};
|
|
196
214
|
return this;
|
|
197
215
|
}
|
|
198
216
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* Systems are owned by the world for the duration of the session. If you
|
|
202
|
-
* need a temporary reactive set, use a standalone {@link Query} instead.
|
|
203
|
-
*/
|
|
204
|
-
destroy() {
|
|
205
|
-
throw `destroy() is not supported on System '${this.name}'`;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Set the entity membership predicate using the {@link QueryDSL} DSL.
|
|
217
|
+
* Set the entity-membership predicate using a {@link QueryDSL} expression.
|
|
209
218
|
*
|
|
210
219
|
* Replaces any implicit query derived from `update` watchlists and any
|
|
211
|
-
* previous `requires` call. After calling `query`, auto-
|
|
212
|
-
* `update`
|
|
220
|
+
* previous `requires` call. After calling `query`, watchlist auto-expansion
|
|
221
|
+
* via `update` is disabled.
|
|
213
222
|
*
|
|
214
|
-
* The optional `
|
|
215
|
-
*
|
|
216
|
-
* to be present on every matched entity, eliminating `| undefined` from
|
|
217
|
-
* those positions. It has no effect at runtime.
|
|
223
|
+
* The optional `_guaranteed` tuple is a pure type-level hint — see
|
|
224
|
+
* {@link Query.query} for details.
|
|
218
225
|
*
|
|
219
|
-
* @param q -
|
|
226
|
+
* @param q - Query expression.
|
|
220
227
|
* @param _guaranteed - Component classes guaranteed present on every matched
|
|
221
228
|
* entity (type hint only — not validated at runtime).
|
|
222
|
-
* @returns
|
|
223
|
-
*
|
|
224
|
-
* @example
|
|
225
|
-
* ```ts
|
|
226
|
-
* world.system("Move")
|
|
227
|
-
* .query({ AND: [{ HAS: Position }, { HAS: Velocity }] }, [Position, Velocity])
|
|
228
|
-
* .each([Position, Velocity], (e, [pos, vel]) => {
|
|
229
|
-
* pos.x += vel.vx; // no ! needed
|
|
230
|
-
* });
|
|
231
|
-
* ```
|
|
229
|
+
* @returns This system, retyped with the guaranteed tuple as its `R`.
|
|
232
230
|
*/
|
|
233
231
|
query(q, _guaranteed) {
|
|
234
232
|
super.query(q, _guaranteed);
|
|
235
233
|
return this;
|
|
236
234
|
}
|
|
237
235
|
/**
|
|
238
|
-
* Shorthand for `query([...components])` —
|
|
239
|
-
*
|
|
236
|
+
* Shorthand for `query([...components])` — tracks entities that have **all**
|
|
237
|
+
* of the listed component types.
|
|
240
238
|
*
|
|
241
|
-
* Equivalent to `query({ HAS: components })`.
|
|
242
|
-
*
|
|
243
|
-
* {@link
|
|
244
|
-
* tuples.
|
|
239
|
+
* Equivalent to `query({ HAS: components })`. The listed components are also
|
|
240
|
+
* recorded in the type parameter `R`, so {@link sort}, {@link each}, and
|
|
241
|
+
* {@link update} callbacks treat them as non-nullable.
|
|
245
242
|
*
|
|
246
|
-
* @param components -
|
|
247
|
-
* @returns
|
|
243
|
+
* @param components - Component classes to require.
|
|
244
|
+
* @returns This system, retyped with the required tuple as its `R`.
|
|
248
245
|
*/
|
|
249
246
|
requires(...components) {
|
|
250
247
|
super.requires(...components);
|
|
251
248
|
return this;
|
|
252
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Not supported on `System`. Throws unconditionally.
|
|
252
|
+
*
|
|
253
|
+
* Systems are owned by the world for the duration of the session; if you
|
|
254
|
+
* need a temporary reactive set use a standalone {@link Query} via
|
|
255
|
+
* {@link World.query}.
|
|
256
|
+
*/
|
|
257
|
+
destroy() {
|
|
258
|
+
throw `destroy() is not supported on System '${this.name}'`;
|
|
259
|
+
}
|
|
253
260
|
}
|
|
254
261
|
//# sourceMappingURL=system.js.map
|