@latticexyz/recs 2.0.0-snapshot-test-32d38619 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -15
- package/dist/chunk-X4WOMEVS.js +2 -0
- package/dist/chunk-X4WOMEVS.js.map +1 -0
- package/dist/deprecated/index.d.ts +51 -0
- package/dist/deprecated/index.js +1 -1
- package/dist/deprecated/index.js.map +1 -1
- package/dist/index.d.ts +606 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types-0972fad2.d.ts +224 -0
- package/package.json +3 -4
- package/src/Component.ts +34 -21
- package/src/Entity.ts +1 -1
- package/src/Indexer.ts +3 -2
- package/src/Performance.spec.ts +1 -0
- package/src/Query.spec.ts +29 -29
- package/src/Query.ts +12 -13
- package/src/System.spec.ts +4 -4
- package/src/System.ts +7 -7
- package/src/deprecated/createActionSystem.spec.ts +1 -1
- package/src/deprecated/createActionSystem.ts +3 -3
- package/src/deprecated/defineActionComponent.ts +1 -1
- package/src/deprecated/waitForActionCompletion.ts +1 -1
- package/src/deprecated/waitForComponentValueIn.ts +3 -3
- package/src/utils.ts +2 -2
- package/dist/chunk-EL5Z72NP.js +0 -2
- package/dist/chunk-EL5Z72NP.js.map +0 -1
package/src/Component.ts
CHANGED
@@ -18,6 +18,12 @@ import {
|
|
18
18
|
import { isFullComponentValue, isIndexer } from "./utils";
|
19
19
|
import { getEntityString, getEntitySymbol } from "./Entity";
|
20
20
|
|
21
|
+
export type ComponentMutationOptions = {
|
22
|
+
/** Skip publishing this mutation to the component's update stream. Mostly used internally during initial hydration. */
|
23
|
+
skipUpdateStream?: boolean;
|
24
|
+
};
|
25
|
+
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
21
27
|
function getComponentName(component: Component<any, any, any>) {
|
22
28
|
return (
|
23
29
|
component.metadata?.componentName ??
|
@@ -50,7 +56,7 @@ function getComponentName(component: Component<any, any, any>) {
|
|
50
56
|
export function defineComponent<S extends Schema, M extends Metadata, T = unknown>(
|
51
57
|
world: World,
|
52
58
|
schema: S,
|
53
|
-
options?: { id?: string; metadata?: M; indexed?: boolean }
|
59
|
+
options?: { id?: string; metadata?: M; indexed?: boolean },
|
54
60
|
) {
|
55
61
|
if (Object.keys(schema).length === 0) throw new Error("Component schema must have at least one key");
|
56
62
|
const id = options?.id ?? uuid();
|
@@ -80,7 +86,8 @@ export function defineComponent<S extends Schema, M extends Metadata, T = unknow
|
|
80
86
|
export function setComponent<S extends Schema, T = unknown>(
|
81
87
|
component: Component<S, Metadata, T>,
|
82
88
|
entity: Entity,
|
83
|
-
value: ComponentValue<S, T
|
89
|
+
value: ComponentValue<S, T>,
|
90
|
+
options: ComponentMutationOptions = {},
|
84
91
|
) {
|
85
92
|
const entitySymbol = getEntitySymbol(entity);
|
86
93
|
const prevValue = getComponentValue(component, entity);
|
@@ -104,12 +111,14 @@ export function setComponent<S extends Schema, T = unknown>(
|
|
104
111
|
"for entity",
|
105
112
|
entity,
|
106
113
|
". Existing keys: ",
|
107
|
-
Object.keys(component.values)
|
114
|
+
Object.keys(component.values),
|
108
115
|
);
|
109
116
|
}
|
110
117
|
}
|
111
118
|
}
|
112
|
-
|
119
|
+
if (!options.skipUpdateStream) {
|
120
|
+
component.update$.next({ entity, value: [value, prevValue], component });
|
121
|
+
}
|
113
122
|
}
|
114
123
|
|
115
124
|
/**
|
@@ -132,16 +141,17 @@ export function updateComponent<S extends Schema, T = unknown>(
|
|
132
141
|
component: Component<S, Metadata, T>,
|
133
142
|
entity: Entity,
|
134
143
|
value: Partial<ComponentValue<S, T>>,
|
135
|
-
initialValue?: ComponentValue<S, T
|
144
|
+
initialValue?: ComponentValue<S, T>,
|
145
|
+
options: ComponentMutationOptions = {},
|
136
146
|
) {
|
137
147
|
const currentValue = getComponentValue(component, entity);
|
138
148
|
if (currentValue === undefined) {
|
139
149
|
if (initialValue === undefined) {
|
140
150
|
throw new Error(`Can't update component ${getComponentName(component)} without a current value or initial value`);
|
141
151
|
}
|
142
|
-
setComponent(component, entity, { ...initialValue, ...value });
|
152
|
+
setComponent(component, entity, { ...initialValue, ...value }, options);
|
143
153
|
} else {
|
144
|
-
setComponent(component, entity, { ...currentValue, ...value });
|
154
|
+
setComponent(component, entity, { ...currentValue, ...value }, options);
|
145
155
|
}
|
146
156
|
}
|
147
157
|
|
@@ -153,14 +163,17 @@ export function updateComponent<S extends Schema, T = unknown>(
|
|
153
163
|
*/
|
154
164
|
export function removeComponent<S extends Schema, M extends Metadata, T = unknown>(
|
155
165
|
component: Component<S, M, T>,
|
156
|
-
entity: Entity
|
166
|
+
entity: Entity,
|
167
|
+
options: ComponentMutationOptions = {},
|
157
168
|
) {
|
158
169
|
const entitySymbol = getEntitySymbol(entity);
|
159
170
|
const prevValue = getComponentValue(component, entity);
|
160
171
|
for (const key of Object.keys(component.values)) {
|
161
172
|
component.values[key].delete(entitySymbol);
|
162
173
|
}
|
163
|
-
|
174
|
+
if (!options.skipUpdateStream) {
|
175
|
+
component.update$.next({ entity, value: [undefined, prevValue], component });
|
176
|
+
}
|
164
177
|
}
|
165
178
|
|
166
179
|
/**
|
@@ -172,7 +185,7 @@ export function removeComponent<S extends Schema, M extends Metadata, T = unknow
|
|
172
185
|
*/
|
173
186
|
export function hasComponent<S extends Schema, T = unknown>(
|
174
187
|
component: Component<S, Metadata, T>,
|
175
|
-
entity: Entity
|
188
|
+
entity: Entity,
|
176
189
|
): boolean {
|
177
190
|
const entitySymbol = getEntitySymbol(entity);
|
178
191
|
const map = Object.values(component.values)[0];
|
@@ -189,7 +202,7 @@ export function hasComponent<S extends Schema, T = unknown>(
|
|
189
202
|
*/
|
190
203
|
export function getComponentValue<S extends Schema, T = unknown>(
|
191
204
|
component: Component<S, Metadata, T>,
|
192
|
-
entity: Entity
|
205
|
+
entity: Entity,
|
193
206
|
): ComponentValue<S, T> | undefined {
|
194
207
|
const value: Record<string, unknown> = {};
|
195
208
|
const entitySymbol = getEntitySymbol(entity);
|
@@ -218,7 +231,7 @@ export function getComponentValue<S extends Schema, T = unknown>(
|
|
218
231
|
*/
|
219
232
|
export function getComponentValueStrict<S extends Schema, T = unknown>(
|
220
233
|
component: Component<S, Metadata, T>,
|
221
|
-
entity: Entity
|
234
|
+
entity: Entity,
|
222
235
|
): ComponentValue<S, T> {
|
223
236
|
const value = getComponentValue(component, entity);
|
224
237
|
if (!value) throw new Error(`No value for component ${getComponentName(component)} on entity ${entity}`);
|
@@ -241,7 +254,7 @@ export function getComponentValueStrict<S extends Schema, T = unknown>(
|
|
241
254
|
*/
|
242
255
|
export function componentValueEquals<S extends Schema, T = unknown>(
|
243
256
|
a?: Partial<ComponentValue<S, T>>,
|
244
|
-
b?: ComponentValue<S, T
|
257
|
+
b?: ComponentValue<S, T>,
|
245
258
|
): boolean {
|
246
259
|
if (!a && !b) return true;
|
247
260
|
if (!a || !b) return false;
|
@@ -264,7 +277,7 @@ export function componentValueEquals<S extends Schema, T = unknown>(
|
|
264
277
|
*/
|
265
278
|
export function withValue<S extends Schema, T = unknown>(
|
266
279
|
component: Component<S, Metadata, T>,
|
267
|
-
value: ComponentValue<S, T
|
280
|
+
value: ComponentValue<S, T>,
|
268
281
|
): [Component<S, Metadata, T>, ComponentValue<S, T>] {
|
269
282
|
return [component, value];
|
270
283
|
}
|
@@ -278,7 +291,7 @@ export function withValue<S extends Schema, T = unknown>(
|
|
278
291
|
*/
|
279
292
|
export function getEntitiesWithValue<S extends Schema>(
|
280
293
|
component: Component<S> | Indexer<S>,
|
281
|
-
value: Partial<ComponentValue<S
|
294
|
+
value: Partial<ComponentValue<S>>,
|
282
295
|
): Set<Entity> {
|
283
296
|
// Shortcut for indexers
|
284
297
|
if (isIndexer(component) && isFullComponentValue(component, value)) {
|
@@ -303,7 +316,7 @@ export function getEntitiesWithValue<S extends Schema>(
|
|
303
316
|
* @returns Set of all entities in the given component.
|
304
317
|
*/
|
305
318
|
export function getComponentEntities<S extends Schema, T = unknown>(
|
306
|
-
component: Component<S, Metadata, T
|
319
|
+
component: Component<S, Metadata, T>,
|
307
320
|
): IterableIterator<Entity> {
|
308
321
|
return component.entities();
|
309
322
|
}
|
@@ -323,7 +336,7 @@ export function getComponentEntities<S extends Schema, T = unknown>(
|
|
323
336
|
* @returns overridable component
|
324
337
|
*/
|
325
338
|
export function overridableComponent<S extends Schema, M extends Metadata, T = unknown>(
|
326
|
-
component: Component<S, M, T
|
339
|
+
component: Component<S, M, T>,
|
327
340
|
): OverridableComponent<S, M, T> {
|
328
341
|
let nonce = 0;
|
329
342
|
|
@@ -445,7 +458,7 @@ export function overridableComponent<S extends Schema, M extends Metadata, T = u
|
|
445
458
|
component.update$
|
446
459
|
.pipe(
|
447
460
|
filter((e) => !overriddenEntityValues.get(getEntitySymbol(e.entity))),
|
448
|
-
map((update) => ({ ...update, component: overriddenComponent }))
|
461
|
+
map((update) => ({ ...update, component: overriddenComponent })),
|
449
462
|
)
|
450
463
|
.subscribe(update$);
|
451
464
|
|
@@ -463,7 +476,7 @@ export function clearLocalCache(component: Component, uniqueWorldIdentifier?: st
|
|
463
476
|
// Note: Only proof of concept for now - use this only for component that do not update frequently
|
464
477
|
export function createLocalCache<S extends Schema, M extends Metadata, T = unknown>(
|
465
478
|
component: Component<S, M, T>,
|
466
|
-
uniqueWorldIdentifier?: string
|
479
|
+
uniqueWorldIdentifier?: string,
|
467
480
|
): Component<S, M, T> {
|
468
481
|
const { world, update$, values } = component;
|
469
482
|
const cacheId = getLocalCacheId(component as Component, uniqueWorldIdentifier);
|
@@ -497,7 +510,7 @@ export function createLocalCache<S extends Schema, M extends Metadata, T = unkno
|
|
497
510
|
const updateSub = update$.subscribe(() => {
|
498
511
|
numUpdates++;
|
499
512
|
const encoded = JSON.stringify(
|
500
|
-
Object.entries(mapObject(values, (m) => [...m.entries()].map((e) => [getEntityString(e[0]), e[1]])))
|
513
|
+
Object.entries(mapObject(values, (m) => [...m.entries()].map((e) => [getEntityString(e[0]), e[1]]))),
|
501
514
|
);
|
502
515
|
localStorage.setItem(cacheId, encoded);
|
503
516
|
if (numUpdates > 200) {
|
@@ -508,7 +521,7 @@ export function createLocalCache<S extends Schema, M extends Metadata, T = unkno
|
|
508
521
|
numUpdates,
|
509
522
|
"times since",
|
510
523
|
new Date(creation).toLocaleTimeString(),
|
511
|
-
"- the local cache is in an alpha state and should not be used with components that update frequently yet"
|
524
|
+
"- the local cache is in an alpha state and should not be used with components that update frequently yet",
|
512
525
|
);
|
513
526
|
}
|
514
527
|
});
|
package/src/Entity.ts
CHANGED
@@ -17,7 +17,7 @@ import { Component, ComponentValue, Entity, EntitySymbol, World } from "./types"
|
|
17
17
|
export function createEntity(
|
18
18
|
world: World,
|
19
19
|
components?: [Component, ComponentValue][],
|
20
|
-
options?: { id?: string } | { idSuffix?: string }
|
20
|
+
options?: { id?: string } | { idSuffix?: string },
|
21
21
|
): Entity {
|
22
22
|
const entity = world.registerEntity(options ?? {});
|
23
23
|
|
package/src/Indexer.ts
CHANGED
@@ -7,7 +7,8 @@ import { Component, ComponentValue, Entity, EntitySymbol, Indexer, Metadata, Sch
|
|
7
7
|
*
|
8
8
|
* @remarks
|
9
9
|
* An indexed component keeps a "reverse mapping" from {@link ComponentValue} to the Set of {@link createEntity Entities} with this value.
|
10
|
-
* This adds a performance overhead to modifying component values and a memory overhead since in the worst case there is one
|
10
|
+
* This adds a performance overhead to modifying component values and a memory overhead since in the worst case there is one
|
11
|
+
* Set per entity (if every entity has a different component value).
|
11
12
|
* In return the performance for querying for entities with a given component value is close to O(1) (instead of O(#entities) in a regular non-indexed component).
|
12
13
|
* As a rule of thumb only components that are added to many entities and are queried with {@link HasValue} a lot should be indexed (eg. the Position component).
|
13
14
|
*
|
@@ -17,7 +18,7 @@ import { Component, ComponentValue, Entity, EntitySymbol, Indexer, Metadata, Sch
|
|
17
18
|
* @returns Indexed version of the component.
|
18
19
|
*/
|
19
20
|
export function createIndexer<S extends Schema, M extends Metadata, T = unknown>(
|
20
|
-
component: Component<S, M, T
|
21
|
+
component: Component<S, M, T>,
|
21
22
|
): Indexer<S, M, T> {
|
22
23
|
const valueToEntities = new Map<string, Set<EntitySymbol>>();
|
23
24
|
|
package/src/Performance.spec.ts
CHANGED
@@ -35,6 +35,7 @@ describe("V2", () => {
|
|
35
35
|
const Position = defineComponentV2(world, { x: TypeV2.Number, y: TypeV2.Number });
|
36
36
|
|
37
37
|
defineSystem(world, [HasValueV2(Position, { x: 1, y: 1 })], (update) => {
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
38
39
|
const e = update;
|
39
40
|
});
|
40
41
|
|
package/src/Query.spec.ts
CHANGED
@@ -98,27 +98,27 @@ describe("Query", () => {
|
|
98
98
|
expect(runQuery([HasValue(OwnedByEntity, { value: Player })])).toEqual(new Set([Depth1]));
|
99
99
|
|
100
100
|
expect(runQuery([ProxyExpand(OwnedByEntity, 0), HasValue(OwnedByEntity, { value: Player })])).toEqual(
|
101
|
-
new Set([Depth1])
|
101
|
+
new Set([Depth1]),
|
102
102
|
);
|
103
103
|
|
104
104
|
expect(runQuery([ProxyExpand(OwnedByEntity, 1), HasValue(OwnedByEntity, { value: Player })])).toEqual(
|
105
|
-
new Set([Depth1, Depth2])
|
105
|
+
new Set([Depth1, Depth2]),
|
106
106
|
);
|
107
107
|
|
108
108
|
expect(runQuery([ProxyExpand(OwnedByEntity, 2), HasValue(OwnedByEntity, { value: Player })])).toEqual(
|
109
|
-
new Set([Depth1, Depth2, Depth3])
|
109
|
+
new Set([Depth1, Depth2, Depth3]),
|
110
110
|
);
|
111
111
|
|
112
112
|
expect(runQuery([ProxyExpand(OwnedByEntity, 3), HasValue(OwnedByEntity, { value: Player })])).toEqual(
|
113
|
-
new Set([Depth1, Depth2, Depth3, Depth4])
|
113
|
+
new Set([Depth1, Depth2, Depth3, Depth4]),
|
114
114
|
);
|
115
115
|
|
116
116
|
expect(runQuery([ProxyExpand(OwnedByEntity, 4), HasValue(OwnedByEntity, { value: Player })])).toEqual(
|
117
|
-
new Set([Depth1, Depth2, Depth3, Depth4, Depth5])
|
117
|
+
new Set([Depth1, Depth2, Depth3, Depth4, Depth5]),
|
118
118
|
);
|
119
119
|
|
120
120
|
expect(
|
121
|
-
runQuery([ProxyExpand(OwnedByEntity, Number.MAX_SAFE_INTEGER), HasValue(OwnedByEntity, { value: Player })])
|
121
|
+
runQuery([ProxyExpand(OwnedByEntity, Number.MAX_SAFE_INTEGER), HasValue(OwnedByEntity, { value: Player })]),
|
122
122
|
).toEqual(new Set([Depth1, Depth2, Depth3, Depth4, Depth5]));
|
123
123
|
});
|
124
124
|
|
@@ -132,8 +132,8 @@ describe("Query", () => {
|
|
132
132
|
expect(
|
133
133
|
runQuery(
|
134
134
|
[ProxyRead(OwnedByEntity, 1), HasValue(Name, { name: "Alice" })],
|
135
|
-
new Set([Depth1, Depth2, Depth3]) // Provide an initial set of entities
|
136
|
-
)
|
135
|
+
new Set([Depth1, Depth2, Depth3]), // Provide an initial set of entities
|
136
|
+
),
|
137
137
|
).toEqual(new Set([Depth1]));
|
138
138
|
|
139
139
|
expect(
|
@@ -142,7 +142,7 @@ describe("Query", () => {
|
|
142
142
|
HasValue(Name, { name: "Alice" }), // Get all entities with name Alice or owned by Alice
|
143
143
|
ProxyExpand(OwnedByEntity, 0), // Turn off proxy expand
|
144
144
|
NotValue(Name, { name: "Alice" }), // Filter Alice, only keep entities owned by Alice
|
145
|
-
])
|
145
|
+
]),
|
146
146
|
).toEqual(new Set([Depth1]));
|
147
147
|
|
148
148
|
expect(
|
@@ -151,15 +151,15 @@ describe("Query", () => {
|
|
151
151
|
HasValue(Name, { name: "Alice" }), // Get all child entities of Alice (including alice)
|
152
152
|
ProxyExpand(OwnedByEntity, 0), // Turn off proxy expand
|
153
153
|
NotValue(Name, { name: "Alice" }), // Filter Alice, only keep entities owned by Alice
|
154
|
-
])
|
154
|
+
]),
|
155
155
|
).toEqual(new Set([Depth1, Depth2, Depth3, Depth4]));
|
156
156
|
|
157
157
|
// Get all entities from the initial set [Depth3] that have an indirect owner called Alice
|
158
158
|
expect(
|
159
159
|
runQuery(
|
160
160
|
[ProxyRead(OwnedByEntity, Number.MAX_SAFE_INTEGER), HasValue(Name, { name: "Alice" })],
|
161
|
-
new Set([Depth3]) // Provide an initial set of entities
|
162
|
-
)
|
161
|
+
new Set([Depth3]), // Provide an initial set of entities
|
162
|
+
),
|
163
163
|
).toEqual(new Set([Depth3]));
|
164
164
|
|
165
165
|
// Get all entities that have an indirect owner called Alice
|
@@ -171,8 +171,8 @@ describe("Query", () => {
|
|
171
171
|
ProxyRead(OwnedByEntity, 0),
|
172
172
|
NotValue(Name, { name: "Alice" }),
|
173
173
|
],
|
174
|
-
new Set([Player, Depth1, Depth2, Depth3, Depth4]) // Provide an initial set of entities
|
175
|
-
)
|
174
|
+
new Set([Player, Depth1, Depth2, Depth3, Depth4]), // Provide an initial set of entities
|
175
|
+
),
|
176
176
|
).toEqual(new Set([Depth1, Depth2, Depth3, Depth4]));
|
177
177
|
|
178
178
|
// Get all entities from the initial set [Depth3] that have an indirect owner called Alice and their direct child
|
@@ -183,8 +183,8 @@ describe("Query", () => {
|
|
183
183
|
ProxyExpand(OwnedByEntity, 1),
|
184
184
|
HasValue(Name, { name: "Alice" }),
|
185
185
|
],
|
186
|
-
new Set([Depth2]) // Provide an initial set of entities
|
187
|
-
)
|
186
|
+
new Set([Depth2]), // Provide an initial set of entities
|
187
|
+
),
|
188
188
|
).toEqual(new Set([Depth2, Depth3]));
|
189
189
|
});
|
190
190
|
|
@@ -204,15 +204,15 @@ describe("Query", () => {
|
|
204
204
|
createEntity(world, [withValue(Position, { x: 1, y: 1 })]);
|
205
205
|
|
206
206
|
expect(runQuery([ProxyExpand(FromPrototype, 1), Has(CanMove), Not(Prototype)])).toEqual(
|
207
|
-
new Set([instance1, instance2])
|
207
|
+
new Set([instance1, instance2]),
|
208
208
|
);
|
209
209
|
|
210
210
|
expect(runQuery([Has(Position), ProxyRead(FromPrototype, 1), Has(CanMove)])).toEqual(
|
211
|
-
new Set([instance1, instance2])
|
211
|
+
new Set([instance1, instance2]),
|
212
212
|
);
|
213
213
|
|
214
214
|
expect(runQuery([ProxyRead(FromPrototype, 1), Has(Position), Has(CanMove)])).toEqual(
|
215
|
-
new Set([instance1, instance2])
|
215
|
+
new Set([instance1, instance2]),
|
216
216
|
);
|
217
217
|
});
|
218
218
|
|
@@ -287,7 +287,7 @@ describe("Query", () => {
|
|
287
287
|
Has(CanMove), // ...have the CanMove component...
|
288
288
|
ProxyRead(OwnedByEntity, Number.MAX_SAFE_INTEGER), // ...and for whose owner holds...
|
289
289
|
NotValue(Name, { name: "Alice" }), // ...their name is not Alice
|
290
|
-
])
|
290
|
+
]),
|
291
291
|
).toEqual(new Set([Instance3, Entity8]));
|
292
292
|
});
|
293
293
|
|
@@ -343,14 +343,14 @@ describe("Query", () => {
|
|
343
343
|
entity: entities[0],
|
344
344
|
component: CanMove,
|
345
345
|
value: [{ value: true }, undefined],
|
346
|
-
})
|
346
|
+
}),
|
347
347
|
);
|
348
348
|
expect(mock).toHaveBeenCalledWith(
|
349
349
|
expect.objectContaining({
|
350
350
|
entity: entities[1],
|
351
351
|
component: CanMove,
|
352
352
|
value: [{ value: true }, undefined],
|
353
|
-
})
|
353
|
+
}),
|
354
354
|
);
|
355
355
|
expect(mock).toBeCalledTimes(2);
|
356
356
|
|
@@ -360,7 +360,7 @@ describe("Query", () => {
|
|
360
360
|
entity: entities[2],
|
361
361
|
component: CanMove,
|
362
362
|
value: [{ value: true }, undefined],
|
363
|
-
})
|
363
|
+
}),
|
364
364
|
);
|
365
365
|
expect(mock).toHaveBeenCalledTimes(3);
|
366
366
|
});
|
@@ -388,7 +388,7 @@ describe("Query", () => {
|
|
388
388
|
entity: entity1,
|
389
389
|
component: CanMove,
|
390
390
|
value: [undefined, { value: true }],
|
391
|
-
})
|
391
|
+
}),
|
392
392
|
);
|
393
393
|
|
394
394
|
removeComponent(CanMove, entity2);
|
@@ -398,7 +398,7 @@ describe("Query", () => {
|
|
398
398
|
entity: entity2,
|
399
399
|
component: CanMove,
|
400
400
|
value: [undefined, { value: true }],
|
401
|
-
})
|
401
|
+
}),
|
402
402
|
);
|
403
403
|
});
|
404
404
|
});
|
@@ -617,7 +617,7 @@ describe("Query", () => {
|
|
617
617
|
|
618
618
|
const query1 = defineQuery(
|
619
619
|
[ProxyRead(OwnedByEntity, 1), HasValue(Name, { name: "Alice" })],
|
620
|
-
{ initialSet: new Set([Depth1, Depth2, Depth3]) } // Provide an initial set of entities
|
620
|
+
{ initialSet: new Set([Depth1, Depth2, Depth3]) }, // Provide an initial set of entities
|
621
621
|
);
|
622
622
|
query1.update$.subscribe();
|
623
623
|
|
@@ -639,7 +639,7 @@ describe("Query", () => {
|
|
639
639
|
|
640
640
|
const query4 = defineQuery(
|
641
641
|
[ProxyRead(OwnedByEntity, Number.MAX_SAFE_INTEGER), HasValue(Name, { name: "Alice" })],
|
642
|
-
{ initialSet: new Set([Depth3]) } // Provide an initial set of entities
|
642
|
+
{ initialSet: new Set([Depth3]) }, // Provide an initial set of entities
|
643
643
|
);
|
644
644
|
query4.update$.subscribe();
|
645
645
|
|
@@ -650,7 +650,7 @@ describe("Query", () => {
|
|
650
650
|
ProxyRead(OwnedByEntity, 0),
|
651
651
|
NotValue(Name, { name: "Alice" }),
|
652
652
|
],
|
653
|
-
{ initialSet: new Set([Player, Depth1, Depth2, Depth3, Depth4]) } // Provide an initial set of entities
|
653
|
+
{ initialSet: new Set([Player, Depth1, Depth2, Depth3, Depth4]) }, // Provide an initial set of entities
|
654
654
|
);
|
655
655
|
query5.update$.subscribe();
|
656
656
|
|
@@ -660,7 +660,7 @@ describe("Query", () => {
|
|
660
660
|
ProxyExpand(OwnedByEntity, 1),
|
661
661
|
HasValue(Name, { name: "Alice" }),
|
662
662
|
],
|
663
|
-
{ initialSet: new Set([Depth2]) } // Provide an initial set of entities
|
663
|
+
{ initialSet: new Set([Depth2]) }, // Provide an initial set of entities
|
664
664
|
);
|
665
665
|
query6.update$.subscribe();
|
666
666
|
|
package/src/Query.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { filterNullish } from "@latticexyz/utils";
|
2
2
|
import { observable, ObservableSet } from "mobx";
|
3
|
-
import { concat, concatMap, filter, from, map, merge, Observable, of, share
|
3
|
+
import { concat, concatMap, filter, from, map, merge, Observable, of, share } from "rxjs";
|
4
4
|
import {
|
5
5
|
componentValueEquals,
|
6
6
|
getComponentEntities,
|
@@ -15,7 +15,6 @@ import {
|
|
15
15
|
ComponentValue,
|
16
16
|
Entity,
|
17
17
|
EntityQueryFragment,
|
18
|
-
EntitySymbol,
|
19
18
|
HasQueryFragment,
|
20
19
|
HasValueQueryFragment,
|
21
20
|
NotQueryFragment,
|
@@ -88,7 +87,7 @@ export function Not<T extends Schema>(component: Component<T>): NotQueryFragment
|
|
88
87
|
*/
|
89
88
|
export function HasValue<T extends Schema>(
|
90
89
|
component: Component<T>,
|
91
|
-
value: Partial<ComponentValue<T
|
90
|
+
value: Partial<ComponentValue<T>>,
|
92
91
|
): HasValueQueryFragment<T> {
|
93
92
|
return { type: QueryFragmentType.HasValue, component, value };
|
94
93
|
}
|
@@ -112,7 +111,7 @@ export function HasValue<T extends Schema>(
|
|
112
111
|
*/
|
113
112
|
export function NotValue<T extends Schema>(
|
114
113
|
component: Component<T>,
|
115
|
-
value: Partial<ComponentValue<T
|
114
|
+
value: Partial<ComponentValue<T>>,
|
116
115
|
): NotValueQueryFragment<T> {
|
117
116
|
return { type: QueryFragmentType.NotValue, component, value };
|
118
117
|
}
|
@@ -199,7 +198,7 @@ function passesQueryFragment<T extends Schema>(entity: Entity, fragment: EntityQ
|
|
199
198
|
* @returns True if the query fragment is positive, else false.
|
200
199
|
*/
|
201
200
|
function isPositiveFragment<T extends Schema>(
|
202
|
-
fragment: QueryFragment<T
|
201
|
+
fragment: QueryFragment<T>,
|
203
202
|
): fragment is HasQueryFragment<T> | HasValueQueryFragment<T> {
|
204
203
|
return fragment.type === QueryFragmentType.Has || fragment.type == QueryFragmentType.HasValue;
|
205
204
|
}
|
@@ -211,7 +210,7 @@ function isPositiveFragment<T extends Schema>(
|
|
211
210
|
* @returns True if the query fragment is negative, else false.
|
212
211
|
*/
|
213
212
|
function isNegativeFragment<T extends Schema>(
|
214
|
-
fragment: QueryFragment<T
|
213
|
+
fragment: QueryFragment<T>,
|
215
214
|
): fragment is NotQueryFragment<T> | NotValueQueryFragment<T> {
|
216
215
|
return fragment.type === QueryFragmentType.Not || fragment.type == QueryFragmentType.NotValue;
|
217
216
|
}
|
@@ -253,7 +252,7 @@ function isBreakingPassState(passes: boolean, fragment: EntityQueryFragment<Sche
|
|
253
252
|
function passesQueryFragmentProxy<T extends Schema>(
|
254
253
|
entity: Entity,
|
255
254
|
fragment: EntityQueryFragment<T>,
|
256
|
-
proxyRead: ProxyReadQueryFragment
|
255
|
+
proxyRead: ProxyReadQueryFragment,
|
257
256
|
): boolean | null {
|
258
257
|
let proxyEntity = entity;
|
259
258
|
let passes = false;
|
@@ -288,7 +287,7 @@ function passesQueryFragmentProxy<T extends Schema>(
|
|
288
287
|
export function getChildEntities(
|
289
288
|
entity: Entity,
|
290
289
|
component: Component<{ value: Type.Entity }>,
|
291
|
-
depth: number
|
290
|
+
depth: number,
|
292
291
|
): Set<Entity> {
|
293
292
|
if (depth === 0) return new Set();
|
294
293
|
|
@@ -416,7 +415,7 @@ export function runQuery(fragments: QueryFragment[], initialSet?: Set<Entity>):
|
|
416
415
|
*/
|
417
416
|
export function defineQuery(
|
418
417
|
fragments: QueryFragment[],
|
419
|
-
options?: { runOnInit?: boolean; initialSet?: Set<Entity> }
|
418
|
+
options?: { runOnInit?: boolean; initialSet?: Set<Entity> },
|
420
419
|
): {
|
421
420
|
update$: Observable<ComponentUpdate & { type: UpdateType }>;
|
422
421
|
matching: ObservableSet<Entity>;
|
@@ -503,7 +502,7 @@ export function defineQuery(
|
|
503
502
|
return { ...update, type: UpdateType.Enter };
|
504
503
|
}
|
505
504
|
}),
|
506
|
-
filterNullish()
|
505
|
+
filterNullish(),
|
507
506
|
);
|
508
507
|
|
509
508
|
return {
|
@@ -521,7 +520,7 @@ export function defineQuery(
|
|
521
520
|
*/
|
522
521
|
export function defineUpdateQuery(
|
523
522
|
fragments: QueryFragment[],
|
524
|
-
options?: { runOnInit?: boolean }
|
523
|
+
options?: { runOnInit?: boolean },
|
525
524
|
): Observable<ComponentUpdate & { type: UpdateType }> {
|
526
525
|
return defineQuery(fragments, options).update$.pipe(filter((e) => e.type === UpdateType.Update));
|
527
526
|
}
|
@@ -535,7 +534,7 @@ export function defineUpdateQuery(
|
|
535
534
|
*/
|
536
535
|
export function defineEnterQuery(
|
537
536
|
fragments: QueryFragment[],
|
538
|
-
options?: { runOnInit?: boolean }
|
537
|
+
options?: { runOnInit?: boolean },
|
539
538
|
): Observable<ComponentUpdate> {
|
540
539
|
return defineQuery(fragments, options).update$.pipe(filter((e) => e.type === UpdateType.Enter));
|
541
540
|
}
|
@@ -549,7 +548,7 @@ export function defineEnterQuery(
|
|
549
548
|
*/
|
550
549
|
export function defineExitQuery(
|
551
550
|
fragments: QueryFragment[],
|
552
|
-
options?: { runOnInit?: boolean }
|
551
|
+
options?: { runOnInit?: boolean },
|
553
552
|
): Observable<ComponentUpdate> {
|
554
553
|
return defineQuery(fragments, options).update$.pipe(filter((e) => e.type === UpdateType.Exit));
|
555
554
|
}
|
package/src/System.spec.ts
CHANGED
@@ -100,13 +100,13 @@ describe("System", () => {
|
|
100
100
|
|
101
101
|
expect(mock).toHaveBeenCalledTimes(1);
|
102
102
|
expect(mock).toHaveBeenCalledWith(
|
103
|
-
expect.objectContaining({ entity: entity1, component: CanMove, value: [{ value: true }, undefined] })
|
103
|
+
expect.objectContaining({ entity: entity1, component: CanMove, value: [{ value: true }, undefined] }),
|
104
104
|
);
|
105
105
|
|
106
106
|
const entity2 = createEntity(world, [withValue(CanMove, { value: true })]);
|
107
107
|
expect(mock).toHaveBeenCalledTimes(2);
|
108
108
|
expect(mock).toHaveBeenCalledWith(
|
109
|
-
expect.objectContaining({ entity: entity2, component: CanMove, value: [{ value: true }, undefined] })
|
109
|
+
expect.objectContaining({ entity: entity2, component: CanMove, value: [{ value: true }, undefined] }),
|
110
110
|
);
|
111
111
|
});
|
112
112
|
|
@@ -126,13 +126,13 @@ describe("System", () => {
|
|
126
126
|
|
127
127
|
expect(mock).toHaveBeenCalledTimes(1);
|
128
128
|
expect(mock).toHaveBeenCalledWith(
|
129
|
-
expect.objectContaining({ entity: entity1, component: CanMove, value: [undefined, { value: true }] })
|
129
|
+
expect.objectContaining({ entity: entity1, component: CanMove, value: [undefined, { value: true }] }),
|
130
130
|
);
|
131
131
|
|
132
132
|
removeComponent(CanMove, entity2);
|
133
133
|
expect(mock).toHaveBeenCalledTimes(2);
|
134
134
|
expect(mock).toHaveBeenCalledWith(
|
135
|
-
expect.objectContaining({ entity: entity2, component: CanMove, value: [undefined, { value: true }] })
|
135
|
+
expect.objectContaining({ entity: entity2, component: CanMove, value: [undefined, { value: true }] }),
|
136
136
|
);
|
137
137
|
});
|
138
138
|
});
|
package/src/System.ts
CHANGED
@@ -36,7 +36,7 @@ export function defineUpdateSystem(
|
|
36
36
|
world: World,
|
37
37
|
query: QueryFragment[],
|
38
38
|
system: (update: ComponentUpdate) => void,
|
39
|
-
options: { runOnInit?: boolean } = { runOnInit: true }
|
39
|
+
options: { runOnInit?: boolean } = { runOnInit: true },
|
40
40
|
) {
|
41
41
|
defineRxSystem(world, defineUpdateQuery(query, options), system);
|
42
42
|
}
|
@@ -56,7 +56,7 @@ export function defineEnterSystem(
|
|
56
56
|
world: World,
|
57
57
|
query: QueryFragment[],
|
58
58
|
system: (update: ComponentUpdate) => void,
|
59
|
-
options: { runOnInit?: boolean } = { runOnInit: true }
|
59
|
+
options: { runOnInit?: boolean } = { runOnInit: true },
|
60
60
|
) {
|
61
61
|
defineRxSystem(world, defineEnterQuery(query, options), system);
|
62
62
|
}
|
@@ -76,7 +76,7 @@ export function defineExitSystem(
|
|
76
76
|
world: World,
|
77
77
|
query: QueryFragment[],
|
78
78
|
system: (update: ComponentUpdate) => void,
|
79
|
-
options: { runOnInit?: boolean } = { runOnInit: true }
|
79
|
+
options: { runOnInit?: boolean } = { runOnInit: true },
|
80
80
|
) {
|
81
81
|
defineRxSystem(world, defineExitQuery(query, options), system);
|
82
82
|
}
|
@@ -96,7 +96,7 @@ export function defineSystem(
|
|
96
96
|
world: World,
|
97
97
|
query: QueryFragment[],
|
98
98
|
system: (update: ComponentUpdate & { type: UpdateType }) => void,
|
99
|
-
options: { runOnInit?: boolean } = { runOnInit: true }
|
99
|
+
options: { runOnInit?: boolean } = { runOnInit: true },
|
100
100
|
) {
|
101
101
|
defineRxSystem(world, defineQuery(query, options).update$, system);
|
102
102
|
}
|
@@ -116,7 +116,7 @@ export function defineComponentSystem<S extends Schema>(
|
|
116
116
|
world: World,
|
117
117
|
component: Component<S>,
|
118
118
|
system: (update: ComponentUpdate<S>) => void,
|
119
|
-
options: { runOnInit?: boolean } = { runOnInit: true }
|
119
|
+
options: { runOnInit?: boolean } = { runOnInit: true },
|
120
120
|
) {
|
121
121
|
const initial$ = options?.runOnInit ? from(getComponentEntities(component)).pipe(toUpdateStream(component)) : EMPTY;
|
122
122
|
defineRxSystem(world, concat(initial$, component.update$), system);
|
@@ -135,7 +135,7 @@ export function defineSyncSystem<T extends Schema>(
|
|
135
135
|
query: QueryFragment[],
|
136
136
|
component: (entity: Entity) => Component<T>,
|
137
137
|
value: (entity: Entity) => ComponentValue<T>,
|
138
|
-
options: { update?: boolean; runOnInit?: boolean } = { update: false, runOnInit: true }
|
138
|
+
options: { update?: boolean; runOnInit?: boolean } = { update: false, runOnInit: true },
|
139
139
|
) {
|
140
140
|
defineSystem(
|
141
141
|
world,
|
@@ -145,6 +145,6 @@ export function defineSyncSystem<T extends Schema>(
|
|
145
145
|
if (type === UpdateType.Exit) removeComponent(component(entity), entity);
|
146
146
|
if (options?.update && type === UpdateType.Update) setComponent(component(entity), entity, value(entity));
|
147
147
|
},
|
148
|
-
options
|
148
|
+
options,
|
149
149
|
);
|
150
150
|
}
|
@@ -177,7 +177,7 @@ describe("ActionSystem", () => {
|
|
177
177
|
expect(runQuery([HasValue(Action, { on: settlement1 })])).toEqual(new Set([entity1]));
|
178
178
|
expect(runQuery([HasValue(Action, { on: settlement2 })])).toEqual(new Set([entity2]));
|
179
179
|
expect(runQuery([HasValue(Action, { state: ActionState.Requested })])).toEqual(
|
180
|
-
new Set([entity1, entity2, entity3])
|
180
|
+
new Set([entity1, entity2, entity3]),
|
181
181
|
);
|
182
182
|
});
|
183
183
|
|
@@ -15,7 +15,7 @@ export type ActionSystem = ReturnType<typeof createActionSystem>;
|
|
15
15
|
export function createActionSystem<M = unknown>(
|
16
16
|
world: World,
|
17
17
|
txReduced$: Observable<string>,
|
18
|
-
waitForTransaction?: (tx: string) => Promise<void
|
18
|
+
waitForTransaction?: (tx: string) => Promise<void>,
|
19
19
|
) {
|
20
20
|
// Action component
|
21
21
|
const Action = defineActionComponent<M>(world);
|
@@ -40,7 +40,7 @@ export function createActionSystem<M = unknown>(
|
|
40
40
|
* @returns Components including pending updates
|
41
41
|
*/
|
42
42
|
function withOptimisticUpdates<S extends Schema, M extends Metadata, T>(
|
43
|
-
component: Component<S, M, T
|
43
|
+
component: Component<S, M, T>,
|
44
44
|
): OverridableComponent<S, M, T> {
|
45
45
|
const optimisticComponent = componentsWithOptimisticUpdates[component.id] || overridableComponent(component);
|
46
46
|
|
@@ -99,7 +99,7 @@ export function createActionSystem<M = unknown>(
|
|
99
99
|
// This subscriotion makes sure the action requirement is checked again every time
|
100
100
|
// one of the referenced components changes or the pending updates map changes
|
101
101
|
const subscription = merge(
|
102
|
-
...Object.values(action.componentsWithOptimisticUpdates).map((c) => c.update$)
|
102
|
+
...Object.values(action.componentsWithOptimisticUpdates).map((c) => c.update$),
|
103
103
|
).subscribe(() => checkRequirement(action));
|
104
104
|
checkRequirement(action);
|
105
105
|
disposer.set(action.id, { dispose: () => subscription?.unsubscribe() });
|