@vworlds/vecs 1.0.3 → 1.0.5
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 +153 -14
- package/dist/component.d.ts +5 -0
- package/dist/component.js +5 -0
- package/dist/component.js.map +1 -1
- package/dist/dsl.d.ts +71 -0
- package/dist/dsl.js +58 -0
- package/dist/dsl.js.map +1 -0
- package/dist/entity.d.ts +29 -10
- package/dist/entity.js +50 -24
- package/dist/entity.js.map +1 -1
- package/dist/filter.d.ts +59 -0
- package/dist/filter.js +53 -0
- package/dist/filter.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/query.d.ts +217 -0
- package/dist/query.js +238 -0
- package/dist/query.js.map +1 -0
- package/dist/system.d.ts +16 -169
- package/dist/system.js +49 -198
- package/dist/system.js.map +1 -1
- package/dist/world.d.ts +88 -29
- package/dist/world.js +87 -54
- package/dist/world.js.map +1 -1
- package/package.json +1 -1
package/dist/filter.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Component } from "./component.js";
|
|
2
|
+
import type { Entity } from "./entity.js";
|
|
3
|
+
import type { World } from "./world.js";
|
|
4
|
+
import { type MaybeRequired, type QueryDSL } from "./dsl.js";
|
|
5
|
+
/**
|
|
6
|
+
* A non-reactive, one-shot entity filter.
|
|
7
|
+
*
|
|
8
|
+
* Unlike {@link Query}, a `Filter` holds no tracked entity set and registers
|
|
9
|
+
* nothing with the world. Every {@link forEach} call walks all world entities
|
|
10
|
+
* and invokes the callback for those that match the predicate captured at
|
|
11
|
+
* construction time.
|
|
12
|
+
*
|
|
13
|
+
* Create via {@link World.filter}:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const f = world.filter([Position, Velocity]);
|
|
17
|
+
*
|
|
18
|
+
* // Entity only:
|
|
19
|
+
* f.forEach((e) => console.log(e.eid));
|
|
20
|
+
*
|
|
21
|
+
* // With component injection:
|
|
22
|
+
* f.forEach([Position, Velocity], (e, [pos, vel]) => {
|
|
23
|
+
* pos.x += vel.vx;
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* ### Type parameter `R`
|
|
28
|
+
* Tracks which component classes are guaranteed present on every matched
|
|
29
|
+
* entity — inferred automatically from the DSL by {@link World.filter}, or
|
|
30
|
+
* supplied manually via the optional `_guaranteed` argument. Components in `R`
|
|
31
|
+
* appear as non-nullable in `forEach` callback tuples.
|
|
32
|
+
*/
|
|
33
|
+
export declare class Filter<R extends (typeof Component)[] = []> {
|
|
34
|
+
private readonly world;
|
|
35
|
+
private readonly belongs;
|
|
36
|
+
constructor(world: World, dsl: QueryDSL);
|
|
37
|
+
/**
|
|
38
|
+
* Iterate all world entities and call `callback` for each one that matches
|
|
39
|
+
* the DSL this filter was created with.
|
|
40
|
+
*
|
|
41
|
+
* @param callback - Receives only the entity.
|
|
42
|
+
*/
|
|
43
|
+
forEach(callback: (e: Entity) => void): void;
|
|
44
|
+
/**
|
|
45
|
+
* Iterate all world entities and call `callback` for each one that matches
|
|
46
|
+
* the DSL, with component injection.
|
|
47
|
+
*
|
|
48
|
+
* Components declared via {@link World.filter}'s DSL (or `_guaranteed`) are
|
|
49
|
+
* non-nullable in the resolved tuple; any other requested component may be
|
|
50
|
+
* `undefined` if the entity lacks it.
|
|
51
|
+
*
|
|
52
|
+
* @param components - Component classes to resolve from each matching entity.
|
|
53
|
+
* @param callback - Receives the entity and a tuple of resolved component
|
|
54
|
+
* instances.
|
|
55
|
+
*/
|
|
56
|
+
forEach<J extends (typeof Component)[]>(components: readonly [...J], callback: (e: Entity, resolved: {
|
|
57
|
+
[K in keyof J]: MaybeRequired<J[K], R>;
|
|
58
|
+
}) => void): void;
|
|
59
|
+
}
|
package/dist/filter.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { buildEntityTest, } from "./dsl.js";
|
|
2
|
+
/**
|
|
3
|
+
* A non-reactive, one-shot entity filter.
|
|
4
|
+
*
|
|
5
|
+
* Unlike {@link Query}, a `Filter` holds no tracked entity set and registers
|
|
6
|
+
* nothing with the world. Every {@link forEach} call walks all world entities
|
|
7
|
+
* and invokes the callback for those that match the predicate captured at
|
|
8
|
+
* construction time.
|
|
9
|
+
*
|
|
10
|
+
* Create via {@link World.filter}:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const f = world.filter([Position, Velocity]);
|
|
14
|
+
*
|
|
15
|
+
* // Entity only:
|
|
16
|
+
* f.forEach((e) => console.log(e.eid));
|
|
17
|
+
*
|
|
18
|
+
* // With component injection:
|
|
19
|
+
* f.forEach([Position, Velocity], (e, [pos, vel]) => {
|
|
20
|
+
* pos.x += vel.vx;
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ### Type parameter `R`
|
|
25
|
+
* Tracks which component classes are guaranteed present on every matched
|
|
26
|
+
* entity — inferred automatically from the DSL by {@link World.filter}, or
|
|
27
|
+
* supplied manually via the optional `_guaranteed` argument. Components in `R`
|
|
28
|
+
* appear as non-nullable in `forEach` callback tuples.
|
|
29
|
+
*/
|
|
30
|
+
export class Filter {
|
|
31
|
+
constructor(world, dsl) {
|
|
32
|
+
this.world = world;
|
|
33
|
+
this.belongs = buildEntityTest(world, dsl);
|
|
34
|
+
}
|
|
35
|
+
forEach(componentsOrCallback, callback) {
|
|
36
|
+
if (typeof componentsOrCallback === "function") {
|
|
37
|
+
this.world._forEachEntity((e) => {
|
|
38
|
+
if (this.belongs(e))
|
|
39
|
+
componentsOrCallback(e);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const types = componentsOrCallback.map((C) => this.world.getComponentType(C));
|
|
44
|
+
this.world._forEachEntity((e) => {
|
|
45
|
+
if (!this.belongs(e))
|
|
46
|
+
return;
|
|
47
|
+
const resolved = types.map((t) => e.get(t));
|
|
48
|
+
callback(e, resolved);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,eAAe,GAIhB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,OAAO,MAAM;IAGjB,YACmB,KAAY,EAC7B,GAAa;QADI,UAAK,GAAL,KAAK,CAAO;QAG7B,IAAI,CAAC,OAAO,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC7C,CAAC;IA8BM,OAAO,CACZ,oBAEyB,EACzB,QAGS;QAET,IAAI,OAAO,oBAAoB,KAAK,UAAU,EAAE;YAC9C,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC9B,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;oBAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC;YAC/C,CAAC,CAAC,CAAC;SACJ;aAAM;YACL,MAAM,KAAK,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3C,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAC/B,CAAC;YACF,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;oBAAE,OAAO;gBAC7B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5C,QAAS,CAAC,CAAC,EAAE,QAAe,CAAC,CAAC;YAChC,CAAC,CAAC,CAAC;SACJ;IACH,CAAC;CACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { type System } from "./system.js";
|
|
2
|
+
export { Query } from "./query.js";
|
|
2
3
|
export { World } from "./world.js";
|
|
4
|
+
export { Filter } from "./filter.js";
|
|
3
5
|
export { Component, type ComponentMeta } from "./component.js";
|
|
4
6
|
export { type Entity } from "./entity.js";
|
|
5
7
|
export { type IPhase } from "./phase.js";
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAsB,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,SAAS,EAAsB,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/package.json
CHANGED
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { Component } from "./component.js";
|
|
2
|
+
import type { Entity } from "./entity.js";
|
|
3
|
+
import { type World } from "./world.js";
|
|
4
|
+
import { type EntityTestFunc, type QueryDSL, type MaybeRequired } from "./dsl.js";
|
|
5
|
+
export type { EntityTestFunc, QueryDSL, MaybeRequired };
|
|
6
|
+
type EntityCallback = (e: Entity) => void;
|
|
7
|
+
type ComponentOrParent = typeof Component | {
|
|
8
|
+
parent: typeof Component;
|
|
9
|
+
};
|
|
10
|
+
type ComponentInstance<T> = T extends {
|
|
11
|
+
parent: typeof Component;
|
|
12
|
+
} ? InstanceType<T["parent"]> : T extends typeof Component ? InstanceType<T> : never;
|
|
13
|
+
/**
|
|
14
|
+
* A reactive, always-updated list of entities that match a given query
|
|
15
|
+
* expression.
|
|
16
|
+
*
|
|
17
|
+
* `Query` tracks every entity whose component set satisfies the predicate set
|
|
18
|
+
* via {@link requires} or {@link query}. Entry and exit callbacks fire
|
|
19
|
+
* automatically as entities gain or lose matching components. The tracked set
|
|
20
|
+
* is exposed via {@link entities} and {@link forEach}.
|
|
21
|
+
*
|
|
22
|
+
* {@link System} extends `Query` and adds per-tick runtime concerns
|
|
23
|
+
* (`update`, `each`, `run`). Use `Query` directly when you only need the
|
|
24
|
+
* reactive entity set without pipeline integration.
|
|
25
|
+
*
|
|
26
|
+
* ### Type parameter `R`
|
|
27
|
+
* Tracks which component classes are "required" (declared via {@link requires}
|
|
28
|
+
* or the `_guaranteed` hint of {@link query}). Those components appear as
|
|
29
|
+
* non-nullable in {@link sort} callbacks.
|
|
30
|
+
*/
|
|
31
|
+
export declare class Query<R extends (typeof Component)[] = []> {
|
|
32
|
+
/** Unique name for this query, used in logs and debug output. */
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/** The world that owns this query. */
|
|
35
|
+
readonly world: World;
|
|
36
|
+
protected _entities: Set<Entity> | undefined;
|
|
37
|
+
protected _enterCallback: EntityCallback[];
|
|
38
|
+
protected _exitCallback: EntityCallback[];
|
|
39
|
+
protected _belongs: EntityTestFunc;
|
|
40
|
+
protected hasQuery: boolean;
|
|
41
|
+
constructor(
|
|
42
|
+
/** Unique name for this query, used in logs and debug output. */
|
|
43
|
+
name: string,
|
|
44
|
+
/** The world that owns this query. */
|
|
45
|
+
world: World, track?: boolean);
|
|
46
|
+
/** Returns the query name. */
|
|
47
|
+
toString(): string;
|
|
48
|
+
/**
|
|
49
|
+
* Enable entity tracking: matched entities are inserted into {@link entities}
|
|
50
|
+
* as they enter and removed as they exit.
|
|
51
|
+
*
|
|
52
|
+
* Idempotent. When called after {@link World.start}, immediately backfills all
|
|
53
|
+
* existing entities that currently satisfy the query predicate.
|
|
54
|
+
*
|
|
55
|
+
* @returns `this` for chaining.
|
|
56
|
+
*/
|
|
57
|
+
track(): this;
|
|
58
|
+
private backfill;
|
|
59
|
+
/**
|
|
60
|
+
* Read-only view of the entities currently tracked by this query.
|
|
61
|
+
*
|
|
62
|
+
* Populated as entities enter (and removed as they exit). Empty unless
|
|
63
|
+
* tracking is enabled (default for standalone queries; requires an explicit
|
|
64
|
+
* {@link track} call on {@link System | systems}).
|
|
65
|
+
*/
|
|
66
|
+
get entities(): ReadonlySet<Entity>;
|
|
67
|
+
/**
|
|
68
|
+
* Iterate over every entity currently tracked by this query.
|
|
69
|
+
*
|
|
70
|
+
* @param callback - Called once per tracked entity, in insertion order
|
|
71
|
+
* (or sort order when {@link sort} is configured).
|
|
72
|
+
*/
|
|
73
|
+
forEach(callback: (e: Entity) => void): void;
|
|
74
|
+
/**
|
|
75
|
+
* Iterate over every entity currently tracked by this query, with component
|
|
76
|
+
* injection.
|
|
77
|
+
*
|
|
78
|
+
* Components declared via {@link requires} (or the `_guaranteed` hint of
|
|
79
|
+
* {@link query}) are non-nullable in the resolved tuple; any other requested
|
|
80
|
+
* component may be `undefined` if the entity lacks it.
|
|
81
|
+
*
|
|
82
|
+
* @param components - Component classes to resolve from each entity.
|
|
83
|
+
* @param callback - Receives the entity and a tuple of resolved component
|
|
84
|
+
* instances.
|
|
85
|
+
*/
|
|
86
|
+
forEach<J extends (typeof Component)[]>(components: readonly [...J], callback: (e: Entity, resolved: {
|
|
87
|
+
[K in keyof J]: MaybeRequired<J[K], R>;
|
|
88
|
+
}) => void): void;
|
|
89
|
+
/** Returns `true` if the entity satisfies this query's predicate. */
|
|
90
|
+
belongs(e: Entity): boolean;
|
|
91
|
+
/** Hook for subclasses — called when a component on an entity in this query changes. */
|
|
92
|
+
notifyModified(_c: Component): void;
|
|
93
|
+
/** @internal Fires enter callbacks and adds entity to the tracked set. */
|
|
94
|
+
_enter(e: Entity): void;
|
|
95
|
+
/** @internal Fires exit callbacks and removes entity from the tracked set. */
|
|
96
|
+
_exit(e: Entity): void;
|
|
97
|
+
/**
|
|
98
|
+
* Register a callback that fires when an entity **enters** this query
|
|
99
|
+
* (i.e. first satisfies the predicate) with injected components.
|
|
100
|
+
*
|
|
101
|
+
* @param inject - Ordered list of component classes (or `{ parent: C }`) to
|
|
102
|
+
* resolve from the entering entity and pass to `callback`.
|
|
103
|
+
* @param callback - Receives the entity and the resolved component tuple.
|
|
104
|
+
* @returns `this` for chaining.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* query.enter([Position, Sprite], (e, [pos, sprite]) => {
|
|
109
|
+
* sprite.initialize(scene);
|
|
110
|
+
* sprite.sprite.setPosition(pos.x, pos.y);
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
enter<J extends ComponentOrParent[]>(inject: readonly [...J], callback: (e: Entity, injected: {
|
|
115
|
+
[K in keyof J]: ComponentInstance<J[K]>;
|
|
116
|
+
}) => void): this;
|
|
117
|
+
/**
|
|
118
|
+
* Register a callback that fires when an entity enters this query.
|
|
119
|
+
*
|
|
120
|
+
* @param callback - Receives only the entity (no injection).
|
|
121
|
+
* @returns `this` for chaining.
|
|
122
|
+
*/
|
|
123
|
+
enter(callback: (e: Entity) => void): this;
|
|
124
|
+
/**
|
|
125
|
+
* Register a callback that fires when an entity **exits** this query
|
|
126
|
+
* (its components no longer satisfy the predicate, or it was destroyed) with
|
|
127
|
+
* injected components.
|
|
128
|
+
*
|
|
129
|
+
* Components that were just removed are still accessible via `get_deleted`
|
|
130
|
+
* semantics — the injected tuple includes them even though they are no
|
|
131
|
+
* longer in the entity's active component set.
|
|
132
|
+
*
|
|
133
|
+
* @param inject - Component classes to resolve and inject.
|
|
134
|
+
* @param callback - Receives the entity and the resolved component tuple.
|
|
135
|
+
* @returns `this` for chaining.
|
|
136
|
+
*/
|
|
137
|
+
exit<J extends ComponentOrParent[]>(inject: readonly [...J], callback: (e: Entity, injected: {
|
|
138
|
+
[K in keyof J]: ComponentInstance<J[K]>;
|
|
139
|
+
}) => void): this;
|
|
140
|
+
/**
|
|
141
|
+
* Register a callback that fires when an entity exits this query.
|
|
142
|
+
*
|
|
143
|
+
* @param callback - Receives only the entity.
|
|
144
|
+
* @returns `this` for chaining.
|
|
145
|
+
*/
|
|
146
|
+
exit(callback: (e: Entity) => void): this;
|
|
147
|
+
/**
|
|
148
|
+
* Enable sorted entity tracking: matched entities are stored in insertion
|
|
149
|
+
* order determined by `compare`, which receives a tuple of resolved
|
|
150
|
+
* component instances for each pair of entities being ordered.
|
|
151
|
+
*
|
|
152
|
+
* @param components - Component classes to resolve and pass to `compare`.
|
|
153
|
+
* @param compare - Returns a negative number, zero, or positive number when
|
|
154
|
+
* `a` should sort before, equal to, or after `b`.
|
|
155
|
+
* @returns `this` for chaining.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* world.system("Render")
|
|
160
|
+
* .requires(Position, Sprite)
|
|
161
|
+
* .sort([Position], ([posA], [posB]) => posA.z - posB.z);
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
sort<J extends (typeof Component)[]>(components: readonly [...J], compare: (a: {
|
|
165
|
+
[K in keyof J]: MaybeRequired<J[K], R>;
|
|
166
|
+
}, b: {
|
|
167
|
+
[K in keyof J]: MaybeRequired<J[K], R>;
|
|
168
|
+
}) => number): this;
|
|
169
|
+
/**
|
|
170
|
+
* Set the entity membership predicate using the {@link QueryDSL} DSL.
|
|
171
|
+
*
|
|
172
|
+
* Replaces any previous predicate. The optional `guaranteed` tuple is a
|
|
173
|
+
* pure type-level hint: it tells {@link sort} callbacks which components are
|
|
174
|
+
* guaranteed present on every matched entity, eliminating `| undefined` from
|
|
175
|
+
* those positions. It has no effect at runtime.
|
|
176
|
+
*
|
|
177
|
+
* @param q - A {@link QueryDSL} expression.
|
|
178
|
+
* @param _guaranteed - Component classes guaranteed present on every matched
|
|
179
|
+
* entity (type hint only — not validated at runtime).
|
|
180
|
+
* @returns `this` for chaining.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* world.system("Move")
|
|
185
|
+
* .query({ AND: [{ HAS: Position }, { HAS: Velocity }] }, [Position, Velocity])
|
|
186
|
+
* .each([Position, Velocity], (e, [pos, vel]) => {
|
|
187
|
+
* pos.x += vel.vx; // no ! needed
|
|
188
|
+
* });
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
query<T extends (typeof Component)[] = []>(q: QueryDSL, _guaranteed?: readonly [...T]): Query<T>;
|
|
192
|
+
/**
|
|
193
|
+
* Remove this query from the world and all entities.
|
|
194
|
+
*
|
|
195
|
+
* Every entity that currently belongs to this query has the query silently
|
|
196
|
+
* removed (no exit callbacks are fired). After this call the query is
|
|
197
|
+
* unregistered from its world and `world` is set to `undefined` by force.
|
|
198
|
+
*
|
|
199
|
+
* Calling any method on the query after `destroy()` is **undefined behavior**.
|
|
200
|
+
*/
|
|
201
|
+
destroy(): void;
|
|
202
|
+
/**
|
|
203
|
+
* Shorthand for `query([...components])` — tracks entities that have
|
|
204
|
+
* **all** of the listed component types.
|
|
205
|
+
*
|
|
206
|
+
* Equivalent to `query({ HAS: components })`. Unlike `query`, passing
|
|
207
|
+
* component classes here also informs the types of {@link sort} callbacks:
|
|
208
|
+
* listed components will be non-nullable in those tuples.
|
|
209
|
+
*
|
|
210
|
+
* @param components - One or more component classes.
|
|
211
|
+
* @returns `this` for chaining.
|
|
212
|
+
*/
|
|
213
|
+
requires<T extends (typeof Component)[]>(...components: [...T]): Query<T>;
|
|
214
|
+
private getComponent;
|
|
215
|
+
private getInjected;
|
|
216
|
+
private mapInjectedClassToTypes;
|
|
217
|
+
}
|
package/dist/query.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { OrderedSet } from "./util/ordered_set.js";
|
|
2
|
+
import { buildEntityTest, } from "./dsl.js";
|
|
3
|
+
const EMPTY_ENTITIES = new Set();
|
|
4
|
+
/**
|
|
5
|
+
* A reactive, always-updated list of entities that match a given query
|
|
6
|
+
* expression.
|
|
7
|
+
*
|
|
8
|
+
* `Query` tracks every entity whose component set satisfies the predicate set
|
|
9
|
+
* via {@link requires} or {@link query}. Entry and exit callbacks fire
|
|
10
|
+
* automatically as entities gain or lose matching components. The tracked set
|
|
11
|
+
* is exposed via {@link entities} and {@link forEach}.
|
|
12
|
+
*
|
|
13
|
+
* {@link System} extends `Query` and adds per-tick runtime concerns
|
|
14
|
+
* (`update`, `each`, `run`). Use `Query` directly when you only need the
|
|
15
|
+
* reactive entity set without pipeline integration.
|
|
16
|
+
*
|
|
17
|
+
* ### Type parameter `R`
|
|
18
|
+
* Tracks which component classes are "required" (declared via {@link requires}
|
|
19
|
+
* or the `_guaranteed` hint of {@link query}). Those components appear as
|
|
20
|
+
* non-nullable in {@link sort} callbacks.
|
|
21
|
+
*/
|
|
22
|
+
export class Query {
|
|
23
|
+
constructor(
|
|
24
|
+
/** Unique name for this query, used in logs and debug output. */
|
|
25
|
+
name,
|
|
26
|
+
/** The world that owns this query. */
|
|
27
|
+
world, track = true) {
|
|
28
|
+
this.name = name;
|
|
29
|
+
this.world = world;
|
|
30
|
+
this._enterCallback = [];
|
|
31
|
+
this._exitCallback = [];
|
|
32
|
+
this._belongs = (_e) => false;
|
|
33
|
+
this.hasQuery = false;
|
|
34
|
+
world._addQuery(this);
|
|
35
|
+
if (track)
|
|
36
|
+
this.track();
|
|
37
|
+
}
|
|
38
|
+
/** Returns the query name. */
|
|
39
|
+
toString() {
|
|
40
|
+
return this.name;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Enable entity tracking: matched entities are inserted into {@link entities}
|
|
44
|
+
* as they enter and removed as they exit.
|
|
45
|
+
*
|
|
46
|
+
* Idempotent. When called after {@link World.start}, immediately backfills all
|
|
47
|
+
* existing entities that currently satisfy the query predicate.
|
|
48
|
+
*
|
|
49
|
+
* @returns `this` for chaining.
|
|
50
|
+
*/
|
|
51
|
+
track() {
|
|
52
|
+
this._entities ?? (this._entities = new Set());
|
|
53
|
+
this.backfill();
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
backfill() {
|
|
57
|
+
if (this._entities === undefined)
|
|
58
|
+
return;
|
|
59
|
+
this.world._forEachEntity((e) => {
|
|
60
|
+
if (this.belongs(e) && !this._entities.has(e)) {
|
|
61
|
+
e._addQuery(this);
|
|
62
|
+
e._updateQueries();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Read-only view of the entities currently tracked by this query.
|
|
68
|
+
*
|
|
69
|
+
* Populated as entities enter (and removed as they exit). Empty unless
|
|
70
|
+
* tracking is enabled (default for standalone queries; requires an explicit
|
|
71
|
+
* {@link track} call on {@link System | systems}).
|
|
72
|
+
*/
|
|
73
|
+
get entities() {
|
|
74
|
+
return this._entities ?? EMPTY_ENTITIES;
|
|
75
|
+
}
|
|
76
|
+
forEach(componentsOrCallback, callback) {
|
|
77
|
+
if (typeof componentsOrCallback === "function") {
|
|
78
|
+
this._entities?.forEach(componentsOrCallback);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
if (!this._entities || !this.world)
|
|
82
|
+
return;
|
|
83
|
+
const types = componentsOrCallback.map((C) => this.world.getComponentType(C));
|
|
84
|
+
this._entities.forEach((e) => {
|
|
85
|
+
const resolved = types.map((t) => e.get(t));
|
|
86
|
+
callback(e, resolved);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Returns `true` if the entity satisfies this query's predicate. */
|
|
91
|
+
belongs(e) {
|
|
92
|
+
return this._belongs(e);
|
|
93
|
+
}
|
|
94
|
+
/** Hook for subclasses — called when a component on an entity in this query changes. */
|
|
95
|
+
notifyModified(_c) { }
|
|
96
|
+
/** @internal Fires enter callbacks and adds entity to the tracked set. */
|
|
97
|
+
_enter(e) {
|
|
98
|
+
this._enterCallback.forEach((cb) => cb(e));
|
|
99
|
+
this._entities?.add(e);
|
|
100
|
+
}
|
|
101
|
+
/** @internal Fires exit callbacks and removes entity from the tracked set. */
|
|
102
|
+
_exit(e) {
|
|
103
|
+
this._exitCallback.forEach((cb) => cb(e));
|
|
104
|
+
this._entities?.delete(e);
|
|
105
|
+
}
|
|
106
|
+
enter(injectOrCallback, callback) {
|
|
107
|
+
if (typeof injectOrCallback === "function") {
|
|
108
|
+
this._enterCallback.push(injectOrCallback);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const inject = this.mapInjectedClassToTypes(injectOrCallback);
|
|
112
|
+
this._enterCallback.push((e) => {
|
|
113
|
+
callback(e, this.getInjected(e, inject));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
exit(injectOrCallback, callback) {
|
|
119
|
+
if (typeof injectOrCallback === "function") {
|
|
120
|
+
this._exitCallback.push(injectOrCallback);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const inject = this.mapInjectedClassToTypes(injectOrCallback);
|
|
124
|
+
this._exitCallback.push((e) => {
|
|
125
|
+
callback(e, this.getInjected(e, inject, true));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Enable sorted entity tracking: matched entities are stored in insertion
|
|
132
|
+
* order determined by `compare`, which receives a tuple of resolved
|
|
133
|
+
* component instances for each pair of entities being ordered.
|
|
134
|
+
*
|
|
135
|
+
* @param components - Component classes to resolve and pass to `compare`.
|
|
136
|
+
* @param compare - Returns a negative number, zero, or positive number when
|
|
137
|
+
* `a` should sort before, equal to, or after `b`.
|
|
138
|
+
* @returns `this` for chaining.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* world.system("Render")
|
|
143
|
+
* .requires(Position, Sprite)
|
|
144
|
+
* .sort([Position], ([posA], [posB]) => posA.z - posB.z);
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
sort(components, compare) {
|
|
148
|
+
const types = components.map((C) => this.world.getComponentType(C));
|
|
149
|
+
this._entities = new OrderedSet((a, b) => compare(types.map((t) => a.get(t, true)), types.map((t) => b.get(t, true))));
|
|
150
|
+
this.backfill();
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Set the entity membership predicate using the {@link QueryDSL} DSL.
|
|
155
|
+
*
|
|
156
|
+
* Replaces any previous predicate. The optional `guaranteed` tuple is a
|
|
157
|
+
* pure type-level hint: it tells {@link sort} callbacks which components are
|
|
158
|
+
* guaranteed present on every matched entity, eliminating `| undefined` from
|
|
159
|
+
* those positions. It has no effect at runtime.
|
|
160
|
+
*
|
|
161
|
+
* @param q - A {@link QueryDSL} expression.
|
|
162
|
+
* @param _guaranteed - Component classes guaranteed present on every matched
|
|
163
|
+
* entity (type hint only — not validated at runtime).
|
|
164
|
+
* @returns `this` for chaining.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* world.system("Move")
|
|
169
|
+
* .query({ AND: [{ HAS: Position }, { HAS: Velocity }] }, [Position, Velocity])
|
|
170
|
+
* .each([Position, Velocity], (e, [pos, vel]) => {
|
|
171
|
+
* pos.x += vel.vx; // no ! needed
|
|
172
|
+
* });
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
query(q, _guaranteed) {
|
|
176
|
+
this._belongs = buildEntityTest(this.world, q);
|
|
177
|
+
this.hasQuery = true;
|
|
178
|
+
this.backfill();
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Remove this query from the world and all entities.
|
|
183
|
+
*
|
|
184
|
+
* Every entity that currently belongs to this query has the query silently
|
|
185
|
+
* removed (no exit callbacks are fired). After this call the query is
|
|
186
|
+
* unregistered from its world and `world` is set to `undefined` by force.
|
|
187
|
+
*
|
|
188
|
+
* Calling any method on the query after `destroy()` is **undefined behavior**.
|
|
189
|
+
*/
|
|
190
|
+
destroy() {
|
|
191
|
+
this.world._removeQuery(this);
|
|
192
|
+
this._entities?.clear();
|
|
193
|
+
this.world = undefined;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Shorthand for `query([...components])` — tracks entities that have
|
|
197
|
+
* **all** of the listed component types.
|
|
198
|
+
*
|
|
199
|
+
* Equivalent to `query({ HAS: components })`. Unlike `query`, passing
|
|
200
|
+
* component classes here also informs the types of {@link sort} callbacks:
|
|
201
|
+
* listed components will be non-nullable in those tuples.
|
|
202
|
+
*
|
|
203
|
+
* @param components - One or more component classes.
|
|
204
|
+
* @returns `this` for chaining.
|
|
205
|
+
*/
|
|
206
|
+
requires(...components) {
|
|
207
|
+
this.query(components);
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
getComponent(e, C, considerDeleted) {
|
|
211
|
+
let c;
|
|
212
|
+
if (typeof C === "number") {
|
|
213
|
+
c = e.get(C, considerDeleted);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
c = e.parent && e.parent.get(C.parent, considerDeleted);
|
|
217
|
+
}
|
|
218
|
+
return c;
|
|
219
|
+
}
|
|
220
|
+
getInjected(e, inject, considerDeleted = false) {
|
|
221
|
+
const injected = [];
|
|
222
|
+
inject.forEach((C) => {
|
|
223
|
+
const c = this.getComponent(e, C, considerDeleted);
|
|
224
|
+
if (!c)
|
|
225
|
+
throw "query does not contain component";
|
|
226
|
+
injected.push(c);
|
|
227
|
+
});
|
|
228
|
+
return injected;
|
|
229
|
+
}
|
|
230
|
+
mapInjectedClassToTypes(inject) {
|
|
231
|
+
return inject.map((C) => {
|
|
232
|
+
if (typeof C === "function")
|
|
233
|
+
return this.world.getComponentType(C);
|
|
234
|
+
return { parent: this.world.getComponentType(C.parent) };
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|