@latticexyz/recs 2.0.0-next.0 → 2.0.0-next.10
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 +319 -0
- package/dist/chunk-EL5Z72NP.js +2 -0
- package/dist/chunk-EL5Z72NP.js.map +1 -0
- package/dist/deprecated/index.js +2 -0
- package/dist/deprecated/index.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +17 -6
- package/src/Component.spec.ts +275 -0
- package/src/Component.ts +20 -14
- package/src/Entity.spec.ts +45 -0
- package/src/Entity.ts +1 -1
- package/src/Indexer.spec.ts +288 -0
- package/src/Indexer.ts +1 -1
- package/src/Performance.spec.ts +152 -0
- package/src/Query.spec.ts +811 -0
- package/src/Query.ts +2 -15
- package/src/System.spec.ts +139 -0
- package/src/World.spec.ts +79 -0
- package/src/deprecated/constants.ts +9 -0
- package/src/deprecated/createActionSystem.spec.ts +501 -0
- package/src/deprecated/createActionSystem.ts +236 -0
- package/src/deprecated/defineActionComponent.ts +18 -0
- package/src/deprecated/index.ts +2 -0
- package/src/deprecated/types.ts +45 -0
- package/src/deprecated/waitForActionCompletion.ts +15 -0
- package/src/deprecated/waitForComponentValueIn.ts +38 -0
- package/src/types.ts +8 -9
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, Subject } from "rxjs";
|
3
|
+
import { concat, concatMap, filter, from, map, merge, Observable, of, share, Subject } from "rxjs";
|
4
4
|
import {
|
5
5
|
componentValueEquals,
|
6
6
|
getComponentEntities,
|
@@ -506,22 +506,9 @@ export function defineQuery(
|
|
506
506
|
filterNullish()
|
507
507
|
);
|
508
508
|
|
509
|
-
// Create a new Subject to allow multiple observers
|
510
|
-
// but only subscribe to the internal stream when the update stream is
|
511
|
-
// subscribed to, in order to get the same behavior as if exposing the
|
512
|
-
// internal stream directly (ie only running the internal$ pipe if there are subscribers)
|
513
|
-
const update$ = new Subject<ComponentUpdate & { type: UpdateType }>();
|
514
|
-
const world = fragments[0].component.world;
|
515
|
-
const subscribe = update$.subscribe.bind(update$);
|
516
|
-
update$.subscribe = (observer) => {
|
517
|
-
const subscription = internal$.subscribe(update$);
|
518
|
-
world.registerDisposer(() => subscription?.unsubscribe());
|
519
|
-
return subscribe(observer as Parameters<typeof subscribe>[0]);
|
520
|
-
};
|
521
|
-
|
522
509
|
return {
|
523
510
|
matching,
|
524
|
-
update$: concat(initial$,
|
511
|
+
update$: concat(initial$, internal$).pipe(share()),
|
525
512
|
};
|
526
513
|
}
|
527
514
|
|
@@ -0,0 +1,139 @@
|
|
1
|
+
import { defineComponent, removeComponent, setComponent, withValue } from "./Component";
|
2
|
+
import { Type, UpdateType } from "./constants";
|
3
|
+
import { createEntity } from "./Entity";
|
4
|
+
import { Has } from "./Query";
|
5
|
+
import { defineEnterSystem, defineExitSystem, defineSystem, defineUpdateSystem } from "./System";
|
6
|
+
import { Component, Entity, World } from "./types";
|
7
|
+
import { createWorld } from "./World";
|
8
|
+
|
9
|
+
describe("System", () => {
|
10
|
+
let world: World;
|
11
|
+
|
12
|
+
beforeEach(() => {
|
13
|
+
world = createWorld();
|
14
|
+
});
|
15
|
+
|
16
|
+
describe("Systems", () => {
|
17
|
+
let Position: Component<{ x: number; y: number }>;
|
18
|
+
let entity: Entity;
|
19
|
+
|
20
|
+
beforeEach(() => {
|
21
|
+
Position = defineComponent(world, { x: Type.Number, y: Type.Number });
|
22
|
+
entity = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
|
23
|
+
});
|
24
|
+
|
25
|
+
it("defineSystem should rerun the system if the query result changes (enter, update, exit)", () => {
|
26
|
+
const mock = jest.fn();
|
27
|
+
defineSystem(world, [Has(Position)], mock);
|
28
|
+
|
29
|
+
setComponent(Position, entity, { x: 2, y: 3 });
|
30
|
+
|
31
|
+
expect(mock).toHaveBeenCalledTimes(2);
|
32
|
+
expect(mock).toHaveBeenCalledWith({
|
33
|
+
entity,
|
34
|
+
component: Position,
|
35
|
+
value: [
|
36
|
+
{ x: 2, y: 3 },
|
37
|
+
{ x: 1, y: 2 },
|
38
|
+
],
|
39
|
+
type: UpdateType.Update,
|
40
|
+
});
|
41
|
+
|
42
|
+
setComponent(Position, entity, { x: 3, y: 3 });
|
43
|
+
expect(mock).toHaveBeenCalledTimes(3);
|
44
|
+
expect(mock).toHaveBeenCalledWith({
|
45
|
+
entity,
|
46
|
+
component: Position,
|
47
|
+
value: [
|
48
|
+
{ x: 3, y: 3 },
|
49
|
+
{ x: 2, y: 3 },
|
50
|
+
],
|
51
|
+
type: UpdateType.Update,
|
52
|
+
});
|
53
|
+
|
54
|
+
removeComponent(Position, entity);
|
55
|
+
expect(mock).toHaveBeenCalledTimes(4);
|
56
|
+
expect(mock).toHaveBeenCalledWith({
|
57
|
+
entity,
|
58
|
+
component: Position,
|
59
|
+
value: [undefined, { x: 3, y: 3 }],
|
60
|
+
type: UpdateType.Exit,
|
61
|
+
});
|
62
|
+
});
|
63
|
+
|
64
|
+
it("defineUpdateSystem should rerun the system if the component value of an entity matchign the query changes", () => {
|
65
|
+
const mock = jest.fn();
|
66
|
+
defineUpdateSystem(world, [Has(Position)], mock);
|
67
|
+
|
68
|
+
// The entity already had a position when the system was created and the system runs on init,
|
69
|
+
// so this position update is an update
|
70
|
+
setComponent(Position, entity, { x: 2, y: 3 });
|
71
|
+
expect(mock).toHaveBeenCalledTimes(1);
|
72
|
+
|
73
|
+
setComponent(Position, entity, { x: 2, y: 3 });
|
74
|
+
expect(mock).toHaveBeenCalledTimes(2);
|
75
|
+
expect(mock).toHaveBeenCalledWith({
|
76
|
+
entity,
|
77
|
+
component: Position,
|
78
|
+
value: [
|
79
|
+
{ x: 2, y: 3 },
|
80
|
+
{ x: 2, y: 3 },
|
81
|
+
],
|
82
|
+
type: UpdateType.Update,
|
83
|
+
});
|
84
|
+
|
85
|
+
// Setting the same value again should rerun the system
|
86
|
+
setComponent(Position, entity, { x: 2, y: 3 });
|
87
|
+
expect(mock).toHaveBeenCalledTimes(3);
|
88
|
+
|
89
|
+
setComponent(Position, entity, { x: 3, y: 3 });
|
90
|
+
expect(mock).toHaveBeenCalledTimes(4);
|
91
|
+
});
|
92
|
+
|
93
|
+
it("defineEnterSystem should rerun once with entities matching the query for the first time", () => {
|
94
|
+
const CanMove = defineComponent(world, { value: Type.Boolean });
|
95
|
+
const mock = jest.fn();
|
96
|
+
|
97
|
+
defineEnterSystem(world, [Has(CanMove)], mock);
|
98
|
+
|
99
|
+
const entity1 = createEntity(world, [withValue(CanMove, { value: true })]);
|
100
|
+
|
101
|
+
expect(mock).toHaveBeenCalledTimes(1);
|
102
|
+
expect(mock).toHaveBeenCalledWith(
|
103
|
+
expect.objectContaining({ entity: entity1, component: CanMove, value: [{ value: true }, undefined] })
|
104
|
+
);
|
105
|
+
|
106
|
+
const entity2 = createEntity(world, [withValue(CanMove, { value: true })]);
|
107
|
+
expect(mock).toHaveBeenCalledTimes(2);
|
108
|
+
expect(mock).toHaveBeenCalledWith(
|
109
|
+
expect.objectContaining({ entity: entity2, component: CanMove, value: [{ value: true }, undefined] })
|
110
|
+
);
|
111
|
+
});
|
112
|
+
|
113
|
+
it("defineExitSystem should rerun once with entities not matching the query anymore", () => {
|
114
|
+
const CanMove = defineComponent(world, { value: Type.Boolean });
|
115
|
+
|
116
|
+
const mock = jest.fn();
|
117
|
+
defineExitSystem(world, [Has(CanMove)], mock);
|
118
|
+
|
119
|
+
const entity1 = createEntity(world, [withValue(CanMove, { value: true })]);
|
120
|
+
const entity2 = createEntity(world);
|
121
|
+
setComponent(CanMove, entity2, { value: true });
|
122
|
+
|
123
|
+
expect(mock).toHaveBeenCalledTimes(0);
|
124
|
+
|
125
|
+
removeComponent(CanMove, entity1);
|
126
|
+
|
127
|
+
expect(mock).toHaveBeenCalledTimes(1);
|
128
|
+
expect(mock).toHaveBeenCalledWith(
|
129
|
+
expect.objectContaining({ entity: entity1, component: CanMove, value: [undefined, { value: true }] })
|
130
|
+
);
|
131
|
+
|
132
|
+
removeComponent(CanMove, entity2);
|
133
|
+
expect(mock).toHaveBeenCalledTimes(2);
|
134
|
+
expect(mock).toHaveBeenCalledWith(
|
135
|
+
expect.objectContaining({ entity: entity2, component: CanMove, value: [undefined, { value: true }] })
|
136
|
+
);
|
137
|
+
});
|
138
|
+
});
|
139
|
+
});
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import { arrayToIterator } from "@latticexyz/utils";
|
2
|
+
import { Subject } from "rxjs";
|
3
|
+
import { defineComponent, setComponent } from "./Component";
|
4
|
+
import { Type } from "./constants";
|
5
|
+
import { createEntity } from "./Entity";
|
6
|
+
import { World, AnyComponent, EntitySymbol } from "./types";
|
7
|
+
import { createWorld, getEntityComponents } from "./World";
|
8
|
+
|
9
|
+
describe("World", () => {
|
10
|
+
describe("createWorld", () => {
|
11
|
+
let world: World;
|
12
|
+
|
13
|
+
beforeEach(() => {
|
14
|
+
world = createWorld();
|
15
|
+
});
|
16
|
+
|
17
|
+
describe("registerEntity", () => {
|
18
|
+
it("should add the entitiy to the world's entities", () => {
|
19
|
+
expect([...world.getEntities()].length).toBe(0);
|
20
|
+
|
21
|
+
world.registerEntity();
|
22
|
+
expect([...world.getEntities()].length).toBe(1);
|
23
|
+
expect([...world.getEntities()][0]).not.toBe(undefined);
|
24
|
+
});
|
25
|
+
});
|
26
|
+
|
27
|
+
describe("registerComponent", () => {
|
28
|
+
let rawComponent: AnyComponent;
|
29
|
+
|
30
|
+
beforeEach(() => {
|
31
|
+
rawComponent = {
|
32
|
+
id: "some-id",
|
33
|
+
values: {
|
34
|
+
value: new Map<EntitySymbol, number>(),
|
35
|
+
},
|
36
|
+
update$: new Subject(),
|
37
|
+
schema: { value: Type.Number },
|
38
|
+
metadata: {},
|
39
|
+
entities: () => arrayToIterator([]),
|
40
|
+
world,
|
41
|
+
};
|
42
|
+
});
|
43
|
+
|
44
|
+
it("should add the component to the world's components", () => {
|
45
|
+
expect(world.components.length).toBe(0);
|
46
|
+
world.registerComponent(rawComponent);
|
47
|
+
expect(world.components.length).toBe(1);
|
48
|
+
expect(world.components.includes(rawComponent)).toBe(true);
|
49
|
+
});
|
50
|
+
|
51
|
+
describe("getEntitiyComponents", () => {
|
52
|
+
it("should return the set of components of a given entity", () => {
|
53
|
+
const components = new Set<AnyComponent>([
|
54
|
+
defineComponent(world, { value: Type.Number }),
|
55
|
+
defineComponent(world, { value: Type.Number }),
|
56
|
+
defineComponent(world, { value: Type.Number }),
|
57
|
+
]);
|
58
|
+
const entity = createEntity(world);
|
59
|
+
const value = 1;
|
60
|
+
|
61
|
+
for (const component of components) {
|
62
|
+
setComponent(component, entity, { value });
|
63
|
+
}
|
64
|
+
|
65
|
+
const received = getEntityComponents(world, entity);
|
66
|
+
expect(received.length).toEqual(components.size);
|
67
|
+
|
68
|
+
for (const comp of received) {
|
69
|
+
expect(components.has(comp)).toBe(true);
|
70
|
+
}
|
71
|
+
|
72
|
+
for (const comp of components) {
|
73
|
+
expect(received.includes(comp)).toBe(true);
|
74
|
+
}
|
75
|
+
});
|
76
|
+
});
|
77
|
+
});
|
78
|
+
});
|
79
|
+
});
|