@latticexyz/recs 2.0.12-main-9be2bb86 → 2.0.12-main-e43c0938

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.
@@ -1,288 +0,0 @@
1
- import {
2
- defineComponent,
3
- setComponent,
4
- removeComponent,
5
- getComponentValue,
6
- hasComponent,
7
- withValue,
8
- componentValueEquals,
9
- getEntitiesWithValue,
10
- overridableComponent,
11
- } from "./Component";
12
- import { createIndexer } from "./Indexer";
13
- import { Type } from "./constants";
14
- import { createEntity, getEntitySymbol } from "./Entity";
15
- import { AnyComponent, Entity, World } from "./types";
16
- import { createWorld } from "./World";
17
-
18
- describe("Indexer", () => {
19
- let world: World;
20
-
21
- beforeEach(() => {
22
- world = createWorld();
23
- });
24
-
25
- it("emit changes to its stream", () => {
26
- const entity = createEntity(world);
27
- const component = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
28
-
29
- const mock = jest.fn();
30
- component.update$.subscribe((update) => {
31
- mock(update);
32
- });
33
-
34
- setComponent(component, entity, { x: 1, y: 2 });
35
- setComponent(component, entity, { x: 7, y: 2 });
36
- setComponent(component, entity, { x: 7, y: 2 });
37
- removeComponent(component, entity);
38
-
39
- expect(mock).toHaveBeenNthCalledWith(1, { entity, value: [{ x: 1, y: 2 }, undefined], component });
40
- expect(mock).toHaveBeenNthCalledWith(2, {
41
- entity,
42
- component,
43
- value: [
44
- { x: 7, y: 2 },
45
- { x: 1, y: 2 },
46
- ],
47
- });
48
- expect(mock).toHaveBeenNthCalledWith(3, {
49
- entity,
50
- component,
51
- value: [
52
- { x: 7, y: 2 },
53
- { x: 7, y: 2 },
54
- ],
55
- });
56
- expect(mock).toHaveBeenNthCalledWith(4, { entity, component, value: [undefined, { x: 7, y: 2 }] });
57
- });
58
-
59
- describe("setComponent", () => {
60
- let component: AnyComponent;
61
- let entity: Entity;
62
- let value: number;
63
-
64
- beforeEach(() => {
65
- component = defineComponent(world, { value: Type.Number }, { indexed: true });
66
- entity = createEntity(world);
67
- value = 1;
68
- setComponent(component, entity, { value });
69
- });
70
-
71
- it("should store the component value", () => {
72
- expect(component.values.value.get(getEntitySymbol(entity))).toBe(value);
73
- });
74
-
75
- it("should store the entity", () => {
76
- expect(hasComponent(component, entity)).toBe(true);
77
- });
78
-
79
- it.todo("should store the value array");
80
- });
81
-
82
- describe("removeComponent", () => {
83
- let component: AnyComponent;
84
- let entity: Entity;
85
- let value: number;
86
-
87
- beforeEach(() => {
88
- component = defineComponent(world, { value: Type.Number }, { indexed: true });
89
- entity = createEntity(world);
90
- value = 1;
91
- setComponent(component, entity, { value });
92
- removeComponent(component, entity);
93
- });
94
-
95
- it("should remove the component value", () => {
96
- expect(component.values.value.get(getEntitySymbol(entity))).toBe(undefined);
97
- });
98
-
99
- it("should remove the entity", () => {
100
- expect(hasComponent(component, entity)).toBe(false);
101
- });
102
-
103
- // it("shouldremove the component from the entity's component set", () => {
104
- // expect(world.entities.get(entity)?.has(component)).toBe(false);
105
- // });
106
- });
107
-
108
- describe("hasComponent", () => {
109
- it("should return true if the entity has the component", () => {
110
- const component = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
111
- const entity = createEntity(world);
112
- const value = { x: 1, y: 2 };
113
- setComponent(component, entity, value);
114
-
115
- expect(hasComponent(component, entity)).toEqual(true);
116
- });
117
- });
118
-
119
- describe("getComponentValue", () => {
120
- it("should return the correct component value", () => {
121
- const component = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
122
- const entity = createEntity(world);
123
- const value = { x: 1, y: 2 };
124
- setComponent(component, entity, value);
125
-
126
- const receivedValue = getComponentValue(component, entity);
127
- expect(receivedValue).toEqual(value);
128
- });
129
- });
130
-
131
- describe("getComponentValueStrict", () => {
132
- it.todo("should return the correct component value");
133
- it.todo("should error if the component value does not exist");
134
- });
135
-
136
- describe("componentValueEquals", () => {
137
- it("values should equal equal values", () => {
138
- const value1 = { x: 1, y: 2, z: "x" };
139
- const value2 = { x: 1, y: 2, z: "x" };
140
- const value3 = { x: "1", y: 2, z: "x" };
141
-
142
- expect(componentValueEquals(value1, value2)).toBe(true);
143
- expect(componentValueEquals(value2, value3)).toBe(false);
144
- });
145
- });
146
-
147
- describe("withValue", () => {
148
- it("should return a ComponentWithValue", () => {
149
- const component = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
150
- const value = { x: 1, y: 2 };
151
- const componentWithValue = withValue(component, value);
152
- expect(componentWithValue).toEqual([component, value]);
153
- });
154
- });
155
-
156
- describe("getEntitiesWithValue", () => {
157
- it("Should return all and only entities with this value", () => {
158
- const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
159
- const entity1 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
160
- createEntity(world, [withValue(Position, { x: 2, y: 1 })]);
161
- createEntity(world);
162
- const entity4 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
163
-
164
- expect(getEntitiesWithValue(Position, { x: 1, y: 2 })).toEqual(new Set([entity1, entity4]));
165
- });
166
-
167
- it("Should keep the entities with value up to date", () => {
168
- const Position = defineComponent(world, { x: Type.Number, y: Type.Number });
169
- const entity1 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
170
- const entity2 = createEntity(world, [withValue(Position, { x: 2, y: 1 })]);
171
- createEntity(world);
172
- const PositionIndexer = createIndexer(Position);
173
- expect(getEntitiesWithValue(PositionIndexer, { x: 1, y: 2 })).toEqual(new Set([entity1]));
174
-
175
- const entity3 = createEntity(world, [withValue(Position, { x: 1, y: 2 })]);
176
- expect(getEntitiesWithValue(PositionIndexer, { x: 1, y: 2 })).toEqual(new Set([entity1, entity3]));
177
-
178
- setComponent(Position, entity2, { x: 1, y: 2 });
179
- expect(getEntitiesWithValue(PositionIndexer, { x: 1, y: 2 })).toEqual(new Set([entity1, entity2, entity3]));
180
-
181
- setComponent(PositionIndexer, entity1, { x: 2, y: 2 });
182
- expect(getEntitiesWithValue(PositionIndexer, { x: 1, y: 2 })).toEqual(new Set([entity2, entity3]));
183
- });
184
- });
185
-
186
- describe("overridableComponent", () => {
187
- it("should return a overridable component", () => {
188
- const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
189
- const OverridablePosition = overridableComponent(Position);
190
- expect("addOverride" in OverridablePosition).toBe(true);
191
- expect("addOverride" in OverridablePosition).toBe(true);
192
- });
193
-
194
- it("should mirror all values of the source component", () => {
195
- const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
196
- const entity1 = createEntity(world);
197
- setComponent(Position, entity1, { x: 1, y: 2 });
198
-
199
- const OverridablePosition = overridableComponent(Position);
200
- expect(getComponentValue(OverridablePosition, entity1)).toEqual({ x: 1, y: 2 });
201
- });
202
-
203
- it("the overridable component should be updated if the original component is updated", () => {
204
- const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
205
- const entity1 = createEntity(world);
206
- setComponent(Position, entity1, { x: 1, y: 2 });
207
-
208
- const OverridableComponent = overridableComponent(Position);
209
-
210
- setComponent(Position, entity1, { x: 2, y: 2 });
211
- expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 2 });
212
-
213
- const entity2 = createEntity(world, [withValue(Position, { x: 3, y: 3 })]);
214
- expect(getComponentValue(OverridableComponent, entity2)).toEqual({ x: 3, y: 3 });
215
- });
216
-
217
- it("should return the updated component value if there is a relevant update for the given entity", () => {
218
- const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
219
- const entity1 = createEntity(world);
220
- const entity2 = createEntity(world);
221
- setComponent(Position, entity1, { x: 1, y: 2 });
222
- setComponent(Position, entity2, { x: 5, y: 6 });
223
-
224
- const OverridableComponent = overridableComponent(Position);
225
- OverridableComponent.addOverride("firstOverride", { entity: entity1, value: { x: 2, y: 3 } });
226
- expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
227
- expect(getComponentValue(OverridableComponent, entity2)).toEqual({ x: 5, y: 6 });
228
-
229
- OverridableComponent.addOverride("secondOverride", { entity: entity1, value: { x: 3, y: 3 } });
230
- expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 3, y: 3 });
231
-
232
- OverridableComponent.removeOverride("secondOverride");
233
- expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
234
-
235
- setComponent(Position, entity1, { x: 10, y: 20 });
236
- expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 2, y: 3 });
237
-
238
- OverridableComponent.removeOverride("firstOverride");
239
- expect(getComponentValue(OverridableComponent, entity1)).toEqual({ x: 10, y: 20 });
240
- });
241
-
242
- it("adding an override should trigger reactions depending on the getComponentValue of the overriden component", () => {
243
- const Position = defineComponent(world, { x: Type.Number, y: Type.Number }, { indexed: true });
244
- const entity1 = createEntity(world);
245
- setComponent(Position, entity1, { x: 1, y: 2 });
246
-
247
- const OverridablePosition = overridableComponent(Position);
248
-
249
- const spy = jest.fn();
250
- OverridablePosition.update$.subscribe(spy);
251
-
252
- expect(spy).toHaveBeenCalledTimes(0);
253
-
254
- OverridablePosition.addOverride("firstOverride", { entity: entity1, value: { x: 3, y: 3 } });
255
- expect(spy).toHaveBeenCalledTimes(1);
256
- expect(spy).toHaveBeenLastCalledWith({
257
- entity: entity1,
258
- component: OverridablePosition,
259
- value: [
260
- { x: 3, y: 3 },
261
- { x: 1, y: 2 },
262
- ],
263
- });
264
-
265
- OverridablePosition.removeOverride("firstOverride");
266
- expect(spy).toHaveBeenCalledTimes(2);
267
- expect(spy).toHaveBeenLastCalledWith({
268
- entity: entity1,
269
- component: OverridablePosition,
270
- value: [
271
- { x: 1, y: 2 },
272
- { x: 3, y: 3 },
273
- ],
274
- });
275
-
276
- OverridablePosition.addOverride("secondOverride", {
277
- entity: "42" as Entity,
278
- value: { x: 2, y: 3 },
279
- });
280
- expect(spy).toHaveBeenLastCalledWith({
281
- entity: "42" as Entity,
282
- component: OverridablePosition,
283
- value: [{ x: 2, y: 3 }, undefined],
284
- });
285
- expect(spy).toHaveBeenCalledTimes(3);
286
- });
287
- });
288
- });
package/src/Indexer.ts DELETED
@@ -1,71 +0,0 @@
1
- import { getComponentEntities, getComponentValue } from "./Component";
2
- import { getEntityString, getEntitySymbol } from "./Entity";
3
- import { Component, ComponentValue, Entity, EntitySymbol, Indexer, Metadata, Schema } from "./types";
4
-
5
- /**
6
- * Create an indexed component from a given component.
7
- *
8
- * @remarks
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
11
- * Set per entity (if every entity has a different component value).
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).
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).
14
- *
15
- * @dev This could be made more (memory) efficient by using a hash of the component value as key, but would require handling hash collisions.
16
- *
17
- * @param component {@link defineComponent Component} to index.
18
- * @returns Indexed version of the component.
19
- */
20
- export function createIndexer<S extends Schema, M extends Metadata, T = unknown>(
21
- component: Component<S, M, T>,
22
- ): Indexer<S, M, T> {
23
- const valueToEntities = new Map<string, Set<EntitySymbol>>();
24
-
25
- function getEntitiesWithValue(value: ComponentValue<S, T>) {
26
- const entities = valueToEntities.get(getValueKey(value));
27
- return entities ? new Set([...entities].map(getEntityString)) : new Set<Entity>();
28
- }
29
-
30
- function getValueKey(value: ComponentValue<S, T>): string {
31
- return Object.values(value).join("/");
32
- }
33
-
34
- function add(entity: EntitySymbol, value: ComponentValue<S, T> | undefined) {
35
- if (!value) return;
36
- const valueKey = getValueKey(value);
37
- let entitiesWithValue = valueToEntities.get(valueKey);
38
- if (!entitiesWithValue) {
39
- entitiesWithValue = new Set<EntitySymbol>();
40
- valueToEntities.set(valueKey, entitiesWithValue);
41
- }
42
- entitiesWithValue.add(entity);
43
- }
44
-
45
- function remove(entity: EntitySymbol, value: ComponentValue<S, T> | undefined) {
46
- if (!value) return;
47
- const valueKey = getValueKey(value);
48
- const entitiesWithValue = valueToEntities.get(valueKey);
49
- if (!entitiesWithValue) return;
50
- entitiesWithValue.delete(entity);
51
- }
52
-
53
- // Initial indexing
54
- for (const entity of getComponentEntities(component)) {
55
- const value = getComponentValue(component, entity);
56
- add(getEntitySymbol(entity), value);
57
- }
58
-
59
- // Keeping index up to date
60
- const subscription = component.update$.subscribe(({ entity, value }) => {
61
- // Remove from previous location
62
- remove(getEntitySymbol(entity), value[1]);
63
-
64
- // Add to new location
65
- add(getEntitySymbol(entity), value[0]);
66
- });
67
-
68
- component.world.registerDisposer(() => subscription?.unsubscribe());
69
-
70
- return { ...component, getEntitiesWithValue };
71
- }
@@ -1,153 +0,0 @@
1
- import { defineComponent as defineComponentV2, setComponent as setComponentV2 } from "./Component";
2
- import { createWorld as createWorldV2 } from "./World";
3
- import { createEntity as createEntityV2 } from "./Entity";
4
- import { Type as TypeV2 } from "./constants";
5
- import { HasValue as HasValueV2 } from "./Query";
6
- import { defineSystem } from "./System";
7
-
8
- export function timeIt(fn: () => unknown) {
9
- const start = Date.now();
10
- fn();
11
- const end = Date.now();
12
- const duration = end - start;
13
- console.log("Duration:", duration);
14
- return duration;
15
- }
16
-
17
- describe("V2", () => {
18
- const size = 1000000;
19
- it("measure creation of 1000000 entities", () => {
20
- console.log("V2");
21
- const world = createWorldV2();
22
- const Position = defineComponentV2(world, { x: TypeV2.Number, y: TypeV2.Number });
23
-
24
- timeIt(() => {
25
- for (let i = 0; i < size; i++) {
26
- const entity = createEntityV2(world);
27
- setComponentV2(Position, entity, { x: 1, y: 1 });
28
- }
29
- });
30
- });
31
-
32
- it("measure creation of 1000000 entities and reacting to it", () => {
33
- console.log("V2");
34
- const world = createWorldV2();
35
- const Position = defineComponentV2(world, { x: TypeV2.Number, y: TypeV2.Number });
36
-
37
- defineSystem(world, [HasValueV2(Position, { x: 1, y: 1 })], (update) => {
38
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
39
- const e = update;
40
- });
41
-
42
- timeIt(() => {
43
- for (let i = 0; i < size; i++) {
44
- const entity = createEntityV2(world);
45
- setComponentV2(Position, entity, { x: 1, y: 1 });
46
- }
47
- });
48
- });
49
- });
50
-
51
- describe("TypedArray", () => {
52
- let array: number[];
53
- let typedArray: Uint32Array;
54
- const size = 10000000;
55
-
56
- beforeAll(() => {
57
- // Set up array
58
- array = [];
59
- const randomOtherArr: number[] = [];
60
- for (let i = 0; i < size; i++) {
61
- array.push(i);
62
- randomOtherArr.push(i);
63
- }
64
-
65
- // Set up buffer
66
- const buffer = new ArrayBuffer(4 * size);
67
- typedArray = new Uint32Array(buffer);
68
-
69
- for (let i = 0; i < size; i++) {
70
- typedArray[i] = i;
71
- }
72
- });
73
-
74
- it.skip("measure time to create 10 million entite", () => {
75
- console.log("Sparse array");
76
- const sparse: number[] = [];
77
- timeIt(() => {
78
- for (let i = 0; i < size * 100; i += 100) {
79
- sparse[i] = 0;
80
- }
81
- });
82
-
83
- console.log("Dense array push");
84
- const dense1: number[] = [];
85
- timeIt(() => {
86
- for (let i = 0; i < size * 100; i += 100) {
87
- dense1.push(i);
88
- }
89
- });
90
-
91
- console.log("Dense array set");
92
- const dense2: number[] = [];
93
- timeIt(() => {
94
- for (let i = 0; i < size; i++) {
95
- dense2[i] = i;
96
- }
97
- });
98
-
99
- console.log("Map");
100
- const map = new Map<number, number>();
101
- timeIt(() => {
102
- for (let i = 0; i < size * 100; i += 100) {
103
- map.set(i, i);
104
- }
105
- });
106
-
107
- console.log("Object");
108
- const obj: { [key: number]: number } = {};
109
- timeIt(() => {
110
- for (let i = 0; i < size * 100; i += 100) {
111
- obj[i] = i;
112
- }
113
- });
114
- });
115
-
116
- it.skip("measure time to iterate through regular array with 10 million entries", () => {
117
- console.log("Regular array:");
118
- timeIt(() => {
119
- let sum = 0;
120
- for (let i = 0; i < size; i++) {
121
- sum += i;
122
- }
123
- console.log("Sum", sum);
124
- });
125
-
126
- console.log("Regular array iterator:");
127
- timeIt(() => {
128
- let sum = 0;
129
- for (const i of array) {
130
- sum += i;
131
- }
132
- console.log("Sum", sum);
133
- });
134
-
135
- console.log("Typed array:");
136
- timeIt(() => {
137
- let sum = 0;
138
- for (let i = 0; i < size; i++) {
139
- sum += typedArray[i];
140
- }
141
- console.log("Sum", sum);
142
- });
143
-
144
- console.log("Typed array iterator:");
145
- timeIt(() => {
146
- let sum = 0;
147
- for (const i of typedArray) {
148
- sum += i;
149
- }
150
- console.log("Sum", sum);
151
- });
152
- });
153
- });