@codehz/ecs 0.7.2 → 0.7.3

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.
Files changed (81) hide show
  1. package/examples/advanced-scheduling.ts +96 -0
  2. package/examples/collision-detection.ts +229 -0
  3. package/examples/inventory-system-relations.ts +108 -0
  4. package/examples/parent-child-hierarchy.ts +206 -0
  5. package/examples/serialization.ts +337 -0
  6. package/examples/simple.ts +96 -0
  7. package/examples/spatial-grid.ts +276 -0
  8. package/examples/state-machine.ts +273 -0
  9. package/examples/tag-filtering.ts +266 -0
  10. package/package.json +58 -12
  11. package/src/__tests__/commands/buffer-limits.test.ts +72 -0
  12. package/src/__tests__/commands/buffer.test.ts +195 -0
  13. package/src/__tests__/component/singleton.test.ts +148 -0
  14. package/src/__tests__/core/archetype.test.ts +247 -0
  15. package/src/__tests__/core/bitset.test.ts +171 -0
  16. package/src/__tests__/core/changeset.test.ts +254 -0
  17. package/src/__tests__/core/multi-map.test.ts +74 -0
  18. package/src/__tests__/entity/component-registry.test.ts +66 -0
  19. package/src/__tests__/entity/entity.test.ts +520 -0
  20. package/src/__tests__/entity/id-manager.test.ts +157 -0
  21. package/src/__tests__/entity/id-system.test.ts +260 -0
  22. package/src/__tests__/perf/comprehensive.perf.test.ts +300 -0
  23. package/src/__tests__/perf/sync-hotpath.perf.test.ts +79 -0
  24. package/src/__tests__/query/basic.test.ts +341 -0
  25. package/src/__tests__/query/caching.test.ts +112 -0
  26. package/src/__tests__/query/filter.test.ts +111 -0
  27. package/src/__tests__/query/optional.test.ts +231 -0
  28. package/src/__tests__/query/perf.test.ts +99 -0
  29. package/src/__tests__/relations/dont-fragment/basic.test.ts +496 -0
  30. package/src/__tests__/relations/dont-fragment/query-notification.test.ts +125 -0
  31. package/src/__tests__/relations/wildcard.test.ts +179 -0
  32. package/src/__tests__/serialization/bounds.test.ts +237 -0
  33. package/src/__tests__/testing/assertions.test.ts +224 -0
  34. package/src/__tests__/testing/entity-builder.test.ts +84 -0
  35. package/src/__tests__/testing/snapshot.test.ts +150 -0
  36. package/src/__tests__/testing/world-fixture.test.ts +73 -0
  37. package/src/__tests__/world/component-hooks.test.ts +185 -0
  38. package/src/__tests__/world/component-management.test.ts +447 -0
  39. package/src/__tests__/world/entity-management.test.ts +86 -0
  40. package/src/__tests__/world/get-optional.test.ts +96 -0
  41. package/src/__tests__/world/multi-component-hooks.test.ts +502 -0
  42. package/src/__tests__/world/perf.test.ts +93 -0
  43. package/src/__tests__/world/query.test.ts +223 -0
  44. package/src/__tests__/world/serialize.test.ts +83 -0
  45. package/src/__tests__/world/wildcard-relation-hooks.test.ts +332 -0
  46. package/src/archetype/archetype.ts +472 -0
  47. package/src/archetype/helpers.ts +186 -0
  48. package/src/archetype/store.ts +33 -0
  49. package/src/commands/buffer.ts +110 -0
  50. package/src/commands/changeset.ts +104 -0
  51. package/src/component/entity-store.ts +223 -0
  52. package/src/component/registry.ts +657 -0
  53. package/src/component/type-utils.ts +9 -0
  54. package/src/entity/index.ts +63 -0
  55. package/src/entity/manager.ts +115 -0
  56. package/src/entity/relation.ts +319 -0
  57. package/src/entity/types.ts +135 -0
  58. package/src/index.ts +41 -0
  59. package/src/query/filter.ts +75 -0
  60. package/src/query/query.ts +313 -0
  61. package/src/query/registry.ts +101 -0
  62. package/src/storage/serialization.ts +130 -0
  63. package/src/testing/index.ts +634 -0
  64. package/src/types/index.ts +99 -0
  65. package/src/utils/bit-set.ts +133 -0
  66. package/src/utils/multi-map.ts +96 -0
  67. package/src/utils/utils.ts +19 -0
  68. package/src/world/builder.ts +100 -0
  69. package/src/world/commands.ts +378 -0
  70. package/src/world/hooks.ts +358 -0
  71. package/src/world/references.ts +38 -0
  72. package/src/world/serialization.ts +122 -0
  73. package/src/world/world.ts +1201 -0
  74. /package/{builder.d.mts → dist/builder.d.mts} +0 -0
  75. /package/{index.d.mts → dist/index.d.mts} +0 -0
  76. /package/{index.mjs → dist/index.mjs} +0 -0
  77. /package/{testing.d.mts → dist/testing.d.mts} +0 -0
  78. /package/{testing.mjs → dist/testing.mjs} +0 -0
  79. /package/{testing.mjs.map → dist/testing.mjs.map} +0 -0
  80. /package/{world.mjs → dist/world.mjs} +0 -0
  81. /package/{world.mjs.map → dist/world.mjs.map} +0 -0
@@ -0,0 +1,341 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { component, relation, type EntityId } from "../../entity";
3
+ import { World } from "../../world/world";
4
+
5
+ describe("Query", () => {
6
+ describe("Query Creation and Basic Functionality", () => {
7
+ type Position = { x: number; y: number };
8
+ type Velocity = { x: number; y: number };
9
+ type Health = { value: number };
10
+
11
+ const positionComponent = component<Position>();
12
+ const velocityComponent = component<Velocity>();
13
+ const healthComponent = component<Health>();
14
+
15
+ it("should create a query and return matching entities", () => {
16
+ const world = new World();
17
+ const query = world.createQuery([positionComponent]);
18
+
19
+ const entity1 = world.new();
20
+ const entity2 = world.new();
21
+ const entity3 = world.new();
22
+
23
+ world.set(entity1, positionComponent, { x: 1, y: 2 });
24
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
25
+ // entity3 has no components
26
+
27
+ world.sync(); // Execute deferred commands
28
+
29
+ const entities = query.getEntities();
30
+ expect(entities).toContain(entity1);
31
+ expect(entities).toContain(entity2);
32
+ expect(entities).not.toContain(entity3);
33
+ });
34
+
35
+ it("should update cache when new archetypes are created", () => {
36
+ const world = new World();
37
+ const query = world.createQuery([positionComponent, velocityComponent]);
38
+
39
+ const entity1 = world.new();
40
+ world.set(entity1, positionComponent, { x: 1, y: 2 });
41
+ world.set(entity1, velocityComponent, { x: 0.1, y: 0.2 });
42
+
43
+ world.sync();
44
+
45
+ // Initially should have entity1
46
+ expect(query.getEntities()).toEqual([entity1]);
47
+
48
+ // Create entity2 with same components (should reuse archetype)
49
+ const entity2 = world.new();
50
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
51
+ world.set(entity2, velocityComponent, { x: 0.2, y: 0.3 });
52
+
53
+ world.sync();
54
+
55
+ // Should still work (archetype reused, no new archetype created)
56
+ expect(query.getEntities()).toContain(entity1);
57
+ expect(query.getEntities()).toContain(entity2);
58
+
59
+ // Create entity3 with only position (creates new archetype)
60
+ const entity3 = world.new();
61
+ world.set(entity3, positionComponent, { x: 5, y: 6 });
62
+
63
+ // Query should still only return entities with both components
64
+ const entities = query.getEntities();
65
+ expect(entities).toContain(entity1);
66
+ expect(entities).toContain(entity2);
67
+ expect(entities).not.toContain(entity3);
68
+ });
69
+
70
+ it("should handle empty results", () => {
71
+ const world = new World();
72
+ const query = world.createQuery([velocityComponent]);
73
+
74
+ const entity = world.new();
75
+ world.set(entity, positionComponent, { x: 1, y: 2 });
76
+
77
+ const entities = query.getEntities();
78
+ expect(entities).toEqual([]);
79
+ });
80
+
81
+ it("should dispose properly", () => {
82
+ const world = new World();
83
+ const query = world.createQuery([positionComponent]);
84
+
85
+ const entity = world.new();
86
+ world.set(entity, positionComponent, { x: 1, y: 2 });
87
+
88
+ world.sync();
89
+
90
+ expect(query.disposed).toBe(false);
91
+ expect(query.getEntities()).toEqual([entity]);
92
+
93
+ query.dispose();
94
+ expect(query.disposed).toBe(true);
95
+
96
+ // Should throw after dispose
97
+ expect(() => query.getEntities()).toThrow("Query has been disposed");
98
+ // iterate should also throw
99
+ expect(() => {
100
+ // use spread to attempt to consume iterator
101
+ [...query.iterate([positionComponent])];
102
+ }).toThrow("Query has been disposed");
103
+ });
104
+
105
+ it("should handle multiple queries", () => {
106
+ const world = new World();
107
+ const positionQuery = world.createQuery([positionComponent]);
108
+ const velocityQuery = world.createQuery([velocityComponent]);
109
+ const bothQuery = world.createQuery([positionComponent, velocityComponent]);
110
+
111
+ const entity1 = world.new();
112
+ const entity2 = world.new();
113
+
114
+ world.set(entity1, positionComponent, { x: 1, y: 2 });
115
+ world.set(entity1, velocityComponent, { x: 0.1, y: 0.2 });
116
+
117
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
118
+
119
+ world.sync();
120
+
121
+ const positionEntities = positionQuery.getEntities();
122
+ expect(positionEntities).toContain(entity1);
123
+ expect(positionEntities).toContain(entity2);
124
+ expect(positionEntities.length).toBe(2);
125
+ expect(velocityQuery.getEntities()).toEqual([entity1]);
126
+ expect(bothQuery.getEntities()).toEqual([entity1]);
127
+ });
128
+
129
+ it("should handle query disposal without affecting other queries", () => {
130
+ const world = new World();
131
+ const query1 = world.createQuery([positionComponent]);
132
+ const query2 = world.createQuery([velocityComponent]);
133
+
134
+ const entity = world.new();
135
+ world.set(entity, positionComponent, { x: 1, y: 2 });
136
+ world.set(entity, velocityComponent, { x: 0.1, y: 0.2 });
137
+
138
+ world.sync();
139
+
140
+ expect(query1.getEntities()).toEqual([entity]);
141
+ expect(query2.getEntities()).toEqual([entity]);
142
+
143
+ query1.dispose();
144
+
145
+ // query1 should be disposed
146
+ expect(query1.disposed).toBe(true);
147
+ expect(() => query1.getEntities()).toThrow("Query has been disposed");
148
+
149
+ // query2 should still work
150
+ expect(query2.disposed).toBe(false);
151
+ expect(query2.getEntities()).toEqual([entity]);
152
+ });
153
+
154
+ it("should get entities with component data", () => {
155
+ const world = new World();
156
+ const query = world.createQuery([positionComponent, velocityComponent]);
157
+
158
+ const entity1 = world.new();
159
+ const entity2 = world.new();
160
+
161
+ const pos1: Position = { x: 1, y: 2 };
162
+ const vel1: Velocity = { x: 0.1, y: 0.2 };
163
+ const pos2: Position = { x: 3, y: 4 };
164
+ const vel2: Velocity = { x: 0.3, y: 0.4 };
165
+
166
+ world.set(entity1, positionComponent, pos1);
167
+ world.set(entity1, velocityComponent, vel1);
168
+ world.set(entity2, positionComponent, pos2);
169
+ world.set(entity2, velocityComponent, vel2);
170
+
171
+ world.sync();
172
+
173
+ const results = query.getEntitiesWithComponents([positionComponent, velocityComponent]);
174
+
175
+ expect(results.length).toBe(2);
176
+
177
+ // Find results for each entity
178
+ const result1 = results.find((r) => r.entity === entity1);
179
+ const result2 = results.find((r) => r.entity === entity2);
180
+
181
+ expect(result1).toBeDefined();
182
+ expect(result2).toBeDefined();
183
+
184
+ expect(result1!.components[0]).toEqual(pos1);
185
+ expect(result1!.components[1]).toEqual(vel1);
186
+ expect(result2!.components[0]).toEqual(pos2);
187
+ expect(result2!.components[1]).toEqual(vel2);
188
+ });
189
+
190
+ it("should iterate over entities with forEach", () => {
191
+ const world = new World();
192
+ const query = world.createQuery([positionComponent]);
193
+
194
+ const entity1 = world.new();
195
+ const entity2 = world.new();
196
+
197
+ const pos1: Position = { x: 1, y: 2 };
198
+ const pos2: Position = { x: 3, y: 4 };
199
+
200
+ world.set(entity1, positionComponent, pos1);
201
+ world.set(entity2, positionComponent, pos2);
202
+
203
+ world.sync();
204
+
205
+ const visitedEntities: EntityId[] = [];
206
+ const visitedPositions: Position[] = [];
207
+
208
+ query.forEach([positionComponent], (entity, position) => {
209
+ visitedEntities.push(entity);
210
+ visitedPositions.push(position);
211
+ });
212
+
213
+ expect(visitedEntities.length).toBe(2);
214
+ expect(visitedPositions.length).toBe(2);
215
+ expect(visitedEntities).toContain(entity1);
216
+ expect(visitedEntities).toContain(entity2);
217
+ expect(visitedPositions).toContainEqual(pos1);
218
+ expect(visitedPositions).toContainEqual(pos2);
219
+ });
220
+
221
+ it("should iterate over entities with iterate", () => {
222
+ const world = new World();
223
+ const query = world.createQuery([positionComponent]);
224
+
225
+ const entity1 = world.new();
226
+ const entity2 = world.new();
227
+
228
+ const pos1: Position = { x: 1, y: 2 };
229
+ const pos2: Position = { x: 3, y: 4 };
230
+
231
+ world.set(entity1, positionComponent, pos1);
232
+ world.set(entity2, positionComponent, pos2);
233
+
234
+ world.sync();
235
+
236
+ const visitedEntities: EntityId[] = [];
237
+ const visitedPositions: Position[] = [];
238
+
239
+ for (const [entity, position] of query.iterate([positionComponent])) {
240
+ visitedEntities.push(entity);
241
+ visitedPositions.push(position);
242
+ }
243
+
244
+ expect(visitedEntities.length).toBe(2);
245
+ expect(visitedPositions.length).toBe(2);
246
+ expect(visitedEntities).toContain(entity1);
247
+ expect(visitedEntities).toContain(entity2);
248
+ expect(visitedPositions).toContainEqual(pos1);
249
+ expect(visitedPositions).toContainEqual(pos2);
250
+ });
251
+
252
+ it("should get component data arrays", () => {
253
+ const world = new World();
254
+ const query = world.createQuery([positionComponent]);
255
+
256
+ const entity1 = world.new();
257
+ const entity2 = world.new();
258
+
259
+ const pos1: Position = { x: 1, y: 2 };
260
+ const pos2: Position = { x: 3, y: 4 };
261
+
262
+ world.set(entity1, positionComponent, pos1);
263
+ world.set(entity2, positionComponent, pos2);
264
+
265
+ world.sync();
266
+
267
+ const positions = query.getComponentData(positionComponent);
268
+
269
+ expect(positions.length).toBe(2);
270
+ expect(positions).toContainEqual(pos1);
271
+ expect(positions).toContainEqual(pos2);
272
+ });
273
+
274
+ it("should support negative components to exclude entities", () => {
275
+ const world = new World();
276
+ const query = world.createQuery([positionComponent], { negativeComponentTypes: [healthComponent] });
277
+
278
+ const entity1 = world.new();
279
+ const entity2 = world.new();
280
+ const entity3 = world.new();
281
+
282
+ world.set(entity1, positionComponent, { x: 1, y: 2 });
283
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
284
+ world.set(entity2, healthComponent, { value: 100 }); // entity2 has health, should be excluded
285
+ world.set(entity3, healthComponent, { value: 50 }); // entity3 has no position, already excluded
286
+
287
+ world.sync();
288
+
289
+ const entities = query.getEntities();
290
+ expect(entities).toContain(entity1);
291
+ expect(entities).not.toContain(entity2);
292
+ expect(entities).not.toContain(entity3);
293
+ });
294
+
295
+ it("should support wildcard relations in queries", () => {
296
+ const world = new World();
297
+
298
+ const tag = component();
299
+ // Create a wildcard relation for tag component
300
+ const wildcardTagRelation = relation(tag, "*");
301
+ const query = world.createQuery([wildcardTagRelation]);
302
+
303
+ const entity1 = world.new();
304
+ const entity2 = world.new();
305
+ const entity3 = world.new();
306
+
307
+ world.set(entity1, relation(tag, positionComponent), { x: 1, y: 2 });
308
+ world.set(entity1, relation(tag, velocityComponent), { x: 0.1, y: 0.2 });
309
+
310
+ world.set(entity2, relation(tag, positionComponent), { x: 3, y: 4 });
311
+
312
+ // entity3 has no position component
313
+
314
+ world.sync();
315
+
316
+ const entities = query.getEntities();
317
+ expect(entities).toContain(entity1);
318
+ expect(entities).toContain(entity2);
319
+ expect(entities).not.toContain(entity3);
320
+ });
321
+
322
+ it("should support mixed queries with components and wildcard relations", () => {
323
+ const world = new World();
324
+
325
+ const entity1 = world.new();
326
+ const entity2 = world.new();
327
+ const entity3 = world.new();
328
+
329
+ world.set(entity1, positionComponent, { x: 1, y: 2 });
330
+ world.set(entity1, velocityComponent, { x: 0.1, y: 0.2 });
331
+
332
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
333
+ // entity2 doesn't have velocity
334
+
335
+ world.set(entity3, velocityComponent, { x: 0.5, y: 0.6 });
336
+ // entity3 doesn't have position
337
+
338
+ world.sync();
339
+ });
340
+ });
341
+ });
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { component } from "../../entity";
3
+ import { World } from "../../world/world";
4
+
5
+ describe("Query", () => {
6
+ describe("Query Caching and Reference Counting", () => {
7
+ type Position = { x: number; y: number };
8
+ type Velocity = { x: number; y: number };
9
+
10
+ const positionComponent = component<Position>();
11
+ const velocityComponent = component<Velocity>();
12
+
13
+ it("should cache queries and return the same instance for identical queries", () => {
14
+ const world = new World();
15
+
16
+ // Create two queries with the same component types
17
+ const query1 = world.createQuery([positionComponent]);
18
+ const query2 = world.createQuery([positionComponent]);
19
+
20
+ // Should return the same cached instance
21
+ expect(query1).toBe(query2);
22
+ });
23
+
24
+ it("should cache queries with different component orders as the same query", () => {
25
+ const world = new World();
26
+
27
+ // Create queries with same components but different order
28
+ const query1 = world.createQuery([positionComponent, velocityComponent]);
29
+ const query2 = world.createQuery([velocityComponent, positionComponent]);
30
+
31
+ // Should return the same cached instance (sorted internally)
32
+ expect(query1).toBe(query2);
33
+ });
34
+
35
+ it("should create different queries for different component combinations", () => {
36
+ const world = new World();
37
+
38
+ const query1 = world.createQuery([positionComponent]);
39
+ const query2 = world.createQuery([velocityComponent]);
40
+ const query3 = world.createQuery([positionComponent, velocityComponent]);
41
+
42
+ // All should be different instances
43
+ expect(query1).not.toBe(query2);
44
+ expect(query1).not.toBe(query3);
45
+ expect(query2).not.toBe(query3);
46
+ });
47
+
48
+ it("should properly handle reference counting", () => {
49
+ const world = new World();
50
+
51
+ // Create multiple references to the same query
52
+ const query1 = world.createQuery([positionComponent]);
53
+ const query2 = world.createQuery([positionComponent]);
54
+ const query3 = world.createQuery([positionComponent]);
55
+
56
+ // All should be the same instance
57
+ expect(query1).toBe(query2);
58
+ expect(query2).toBe(query3);
59
+
60
+ // Release all three references
61
+ world.releaseQuery(query1);
62
+ world.releaseQuery(query2);
63
+ world.releaseQuery(query3);
64
+
65
+ // Now create a new query - should be a new instance since cache was cleared
66
+ const query4 = world.createQuery([positionComponent]);
67
+ expect(query4).not.toBe(query1); // Should be a new instance
68
+ });
69
+
70
+ it("should handle releaseQuery on non-cached queries gracefully", () => {
71
+ const world = new World();
72
+
73
+ // Create a query and immediately release it
74
+ const query = world.createQuery([positionComponent]);
75
+ world.releaseQuery(query);
76
+
77
+ // Should not throw and should create a new instance next time
78
+ const query2 = world.createQuery([positionComponent]);
79
+ expect(query2).not.toBe(query);
80
+ });
81
+
82
+ it("should cache queries with filters separately", () => {
83
+ const world = new World();
84
+ type Health = { value: number };
85
+ const healthComponent = component<Health>();
86
+
87
+ // Create queries with and without filters
88
+ const query1 = world.createQuery([positionComponent]);
89
+ const query2 = world.createQuery([positionComponent], { negativeComponentTypes: [healthComponent] });
90
+
91
+ // Should be different instances due to different filters
92
+ expect(query1).not.toBe(query2);
93
+ });
94
+
95
+ it("should maintain separate caches for queries with different filters", () => {
96
+ const world = new World();
97
+ type Health = { value: number };
98
+ const healthComponent = component<Health>();
99
+
100
+ // Create multiple queries with the same filter
101
+ const query1 = world.createQuery([positionComponent], { negativeComponentTypes: [healthComponent] });
102
+ const query2 = world.createQuery([positionComponent], { negativeComponentTypes: [healthComponent] });
103
+
104
+ // Should return the same cached instance
105
+ expect(query1).toBe(query2);
106
+
107
+ // Create queries with different filters
108
+ const query3 = world.createQuery([positionComponent], { negativeComponentTypes: [velocityComponent] });
109
+ expect(query1).not.toBe(query3);
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,111 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { Archetype } from "../../archetype/archetype";
3
+ import type { ComponentId, EntityId } from "../../entity";
4
+ import { relation } from "../../entity";
5
+ import { matchesComponentTypes, matchesFilter, type QueryFilter } from "../../query/filter";
6
+
7
+ // Mock component IDs for testing
8
+ const positionComponent = 1 as ComponentId<{ x: number; y: number }>;
9
+ const velocityComponent = 2 as ComponentId<{ dx: number; dy: number }>;
10
+ const healthComponent = 3 as ComponentId<{ value: number }>;
11
+ const relationComponent = 4 as ComponentId<{ strength: number }>;
12
+
13
+ // Helper function to create a dontFragmentRelations map for testing
14
+ const createDontFragmentRelations = () => new Map<EntityId, Map<EntityId<any>, any>>();
15
+
16
+ describe("Query Filter Functions", () => {
17
+ describe("matchesComponentTypes", () => {
18
+ it("should return true when archetype contains all required component types", () => {
19
+ const archetype = new Archetype([positionComponent, velocityComponent], createDontFragmentRelations());
20
+ const componentTypes = [positionComponent, velocityComponent];
21
+ expect(matchesComponentTypes(archetype, componentTypes)).toBe(true);
22
+ });
23
+
24
+ it("should return true when archetype contains required component types and more", () => {
25
+ const archetype = new Archetype(
26
+ [positionComponent, velocityComponent, healthComponent],
27
+ createDontFragmentRelations(),
28
+ );
29
+ const componentTypes = [positionComponent, velocityComponent];
30
+ expect(matchesComponentTypes(archetype, componentTypes)).toBe(true);
31
+ });
32
+
33
+ it("should return false when archetype is missing a required component type", () => {
34
+ const archetype = new Archetype([positionComponent], createDontFragmentRelations());
35
+ const componentTypes = [positionComponent, velocityComponent];
36
+ expect(matchesComponentTypes(archetype, componentTypes)).toBe(false);
37
+ });
38
+
39
+ it("should return true for empty component types array", () => {
40
+ const archetype = new Archetype([positionComponent], createDontFragmentRelations());
41
+ const componentTypes: EntityId<any>[] = [];
42
+ expect(matchesComponentTypes(archetype, componentTypes)).toBe(true);
43
+ });
44
+ });
45
+
46
+ describe("matchesFilter", () => {
47
+ it("should return true when no negative component types are specified", () => {
48
+ const archetype = new Archetype([positionComponent, velocityComponent], createDontFragmentRelations());
49
+ const filter: QueryFilter = {};
50
+ expect(matchesFilter(archetype, filter)).toBe(true);
51
+ });
52
+
53
+ it("should return true when archetype does not contain any negative component types", () => {
54
+ const archetype = new Archetype([positionComponent, velocityComponent], createDontFragmentRelations());
55
+ const filter: QueryFilter = { negativeComponentTypes: [healthComponent] };
56
+ expect(matchesFilter(archetype, filter)).toBe(true);
57
+ });
58
+
59
+ it("should return false when archetype contains a negative component type", () => {
60
+ const archetype = new Archetype(
61
+ [positionComponent, velocityComponent, healthComponent],
62
+ createDontFragmentRelations(),
63
+ );
64
+ const filter: QueryFilter = { negativeComponentTypes: [healthComponent] };
65
+ expect(matchesFilter(archetype, filter)).toBe(false);
66
+ });
67
+
68
+ it("should return false when archetype contains any of multiple negative component types", () => {
69
+ const archetype = new Archetype([positionComponent, healthComponent], createDontFragmentRelations());
70
+ const filter: QueryFilter = { negativeComponentTypes: [velocityComponent, healthComponent] };
71
+ expect(matchesFilter(archetype, filter)).toBe(false);
72
+ });
73
+
74
+ it("should return true when archetype contains none of multiple negative component types", () => {
75
+ const archetype = new Archetype([positionComponent], createDontFragmentRelations());
76
+ const filter: QueryFilter = { negativeComponentTypes: [velocityComponent, healthComponent] };
77
+ expect(matchesFilter(archetype, filter)).toBe(true);
78
+ });
79
+
80
+ it("should return false when archetype contains a negative wildcard relation component", () => {
81
+ const wildcardRelation = relation(relationComponent, "*");
82
+ const archetype = new Archetype([positionComponent, wildcardRelation], createDontFragmentRelations());
83
+ const filter: QueryFilter = { negativeComponentTypes: [wildcardRelation] };
84
+ expect(matchesFilter(archetype, filter)).toBe(false);
85
+ });
86
+
87
+ it("should return false when archetype contains a specific relation matching negative wildcard filter", () => {
88
+ const wildcardRelation = relation(relationComponent, "*");
89
+ const otherRelation = relation(relationComponent, 1025 as EntityId);
90
+ const archetype = new Archetype([positionComponent, otherRelation], createDontFragmentRelations());
91
+ const filter: QueryFilter = { negativeComponentTypes: [wildcardRelation] };
92
+ expect(matchesFilter(archetype, filter)).toBe(false);
93
+ });
94
+
95
+ it("should return true when archetype does not contain any relations with the wildcard component", () => {
96
+ const wildcardRelation = relation(relationComponent, "*");
97
+ const otherComponent = 5 as EntityId<{ other: number }>;
98
+ const archetype = new Archetype([positionComponent, otherComponent], createDontFragmentRelations());
99
+ const filter: QueryFilter = { negativeComponentTypes: [wildcardRelation] };
100
+ expect(matchesFilter(archetype, filter)).toBe(true);
101
+ });
102
+
103
+ it("should return false when archetype contains wildcard relation matching negative filter", () => {
104
+ const wildcardRelation = relation(relationComponent, "*");
105
+ const matchingRelation = relation(relationComponent, 1026 as EntityId);
106
+ const archetype = new Archetype([positionComponent, matchingRelation], createDontFragmentRelations());
107
+ const filter: QueryFilter = { negativeComponentTypes: [wildcardRelation] };
108
+ expect(matchesFilter(archetype, filter)).toBe(false);
109
+ });
110
+ });
111
+ });