@codehz/ecs 0.7.2 → 0.7.4

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 +60 -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,231 @@
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("Optional Components in Queries", () => {
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 handle optional components in forEach", () => {
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(entity1, velocityComponent, { x: 0.1, y: 0.2 });
25
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
26
+ // entity2 has no velocity
27
+ world.set(entity3, positionComponent, { x: 5, y: 6 });
28
+ world.set(entity3, healthComponent, { value: 100 });
29
+
30
+ world.sync();
31
+
32
+ const results: Array<{ entity: EntityId; position: Position; velocity?: { value: Velocity } }> = [];
33
+
34
+ query.forEach([positionComponent, { optional: velocityComponent }], (entity, position, velocity) => {
35
+ results.push({ entity, position, velocity });
36
+ });
37
+
38
+ expect(results.length).toBe(3);
39
+
40
+ const result1 = results.find((r) => r.entity === entity1);
41
+ const result2 = results.find((r) => r.entity === entity2);
42
+ const result3 = results.find((r) => r.entity === entity3);
43
+
44
+ expect(result1).toBeDefined();
45
+ expect(result2).toBeDefined();
46
+ expect(result3).toBeDefined();
47
+
48
+ expect(result1!.position).toEqual({ x: 1, y: 2 });
49
+ expect(result1!.velocity).toEqual({ value: { x: 0.1, y: 0.2 } });
50
+
51
+ expect(result2!.position).toEqual({ x: 3, y: 4 });
52
+ expect(result2!.velocity).toBeUndefined();
53
+
54
+ expect(result3!.position).toEqual({ x: 5, y: 6 });
55
+ expect(result3!.velocity).toBeUndefined();
56
+ });
57
+
58
+ it("should handle optional components in getEntitiesWithComponents", () => {
59
+ const world = new World();
60
+ const query = world.createQuery([positionComponent]);
61
+
62
+ const entity1 = world.new();
63
+ const entity2 = world.new();
64
+
65
+ world.set(entity1, positionComponent, { x: 1, y: 2 });
66
+ world.set(entity1, velocityComponent, { x: 0.1, y: 0.2 });
67
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
68
+ // entity2 has no velocity
69
+
70
+ world.sync();
71
+
72
+ const results = query.getEntitiesWithComponents([positionComponent, { optional: velocityComponent }]);
73
+
74
+ expect(results.length).toBe(2);
75
+
76
+ const result1 = results.find((r) => r.entity === entity1);
77
+ const result2 = results.find((r) => r.entity === entity2);
78
+
79
+ expect(result1).toBeDefined();
80
+ expect(result2).toBeDefined();
81
+
82
+ expect(result1!.components[0]).toEqual({ x: 1, y: 2 });
83
+ expect(result1!.components[1]).toEqual({ value: { x: 0.1, y: 0.2 } });
84
+
85
+ expect(result2!.components[0]).toEqual({ x: 3, y: 4 });
86
+ expect(result2!.components[1]).toBeUndefined();
87
+ });
88
+
89
+ it("should handle optional components in iterate", () => {
90
+ const world = new World();
91
+ const query = world.createQuery([positionComponent]);
92
+
93
+ const entity1 = world.new();
94
+ const entity2 = world.new();
95
+
96
+ world.set(entity1, positionComponent, { x: 1, y: 2 });
97
+ world.set(entity1, velocityComponent, { x: 0.1, y: 0.2 });
98
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
99
+ // entity2 has no velocity
100
+
101
+ world.sync();
102
+
103
+ const results: Array<{ entity: EntityId; position: Position; velocity?: { value: Velocity } }> = [];
104
+
105
+ for (const [entity, position, velocity] of query.iterate([positionComponent, { optional: velocityComponent }])) {
106
+ results.push({ entity, position, velocity });
107
+ }
108
+
109
+ expect(results.length).toBe(2);
110
+
111
+ const result1 = results.find((r) => r.entity === entity1);
112
+ const result2 = results.find((r) => r.entity === entity2);
113
+
114
+ expect(result1).toBeDefined();
115
+ expect(result2).toBeDefined();
116
+
117
+ expect(result1!.position).toEqual({ x: 1, y: 2 });
118
+ expect(result1!.velocity).toEqual({ value: { x: 0.1, y: 0.2 } });
119
+
120
+ expect(result2!.position).toEqual({ x: 3, y: 4 });
121
+ expect(result2!.velocity).toBeUndefined();
122
+ });
123
+
124
+ it("should handle mixed mandatory and optional components", () => {
125
+ const world = new World();
126
+ const query = world.createQuery([positionComponent, velocityComponent]);
127
+
128
+ const entity1 = world.new();
129
+ const entity2 = world.new();
130
+ const entity3 = world.new();
131
+
132
+ world.set(entity1, positionComponent, { x: 1, y: 2 });
133
+ world.set(entity1, velocityComponent, { x: 0.1, y: 0.2 });
134
+ world.set(entity1, healthComponent, { value: 100 });
135
+
136
+ world.set(entity2, positionComponent, { x: 3, y: 4 });
137
+ world.set(entity2, velocityComponent, { x: 0.2, y: 0.3 });
138
+ // entity2 has no health
139
+
140
+ world.set(entity3, positionComponent, { x: 5, y: 6 });
141
+ world.set(entity3, velocityComponent, { x: 0.3, y: 0.4 });
142
+ world.set(entity3, healthComponent, { value: 50 });
143
+
144
+ world.sync();
145
+
146
+ const results: Array<{
147
+ entity: EntityId;
148
+ position: Position;
149
+ velocity: Velocity;
150
+ health?: { value: Health };
151
+ }> = [];
152
+
153
+ query.forEach(
154
+ [positionComponent, velocityComponent, { optional: healthComponent }],
155
+ (entity, position, velocity, health) => {
156
+ results.push({ entity, position, velocity, health });
157
+ },
158
+ );
159
+
160
+ expect(results.length).toBe(3);
161
+
162
+ const result1 = results.find((r) => r.entity === entity1);
163
+ const result2 = results.find((r) => r.entity === entity2);
164
+ const result3 = results.find((r) => r.entity === entity3);
165
+
166
+ expect(result1).toBeDefined();
167
+ expect(result2).toBeDefined();
168
+ expect(result3).toBeDefined();
169
+
170
+ expect(result1!.position).toEqual({ x: 1, y: 2 });
171
+ expect(result1!.velocity).toEqual({ x: 0.1, y: 0.2 });
172
+ expect(result1!.health).toEqual({ value: { value: 100 } });
173
+
174
+ expect(result2!.position).toEqual({ x: 3, y: 4 });
175
+ expect(result2!.velocity).toEqual({ x: 0.2, y: 0.3 });
176
+ expect(result2!.health).toBeUndefined();
177
+
178
+ expect(result3!.position).toEqual({ x: 5, y: 6 });
179
+ expect(result3!.velocity).toEqual({ x: 0.3, y: 0.4 });
180
+ expect(result3!.health).toEqual({ value: { value: 50 } });
181
+ });
182
+
183
+ it("should handle optional wildcard relations", () => {
184
+ const world = new World();
185
+
186
+ const wildcardPositionRelation = relation(positionComponent, "*");
187
+ const query = world.createQuery([velocityComponent]);
188
+
189
+ const entity1 = world.new();
190
+ const entity2 = world.new();
191
+ const targetEntity = world.new();
192
+
193
+ world.set(entity1, velocityComponent, { x: 0.1, y: 0.2 });
194
+ world.set(entity1, relation(positionComponent, targetEntity), { x: 1, y: 2 });
195
+
196
+ world.set(entity2, velocityComponent, { x: 0.2, y: 0.3 });
197
+ // entity2 has no position relation
198
+
199
+ world.sync();
200
+
201
+ const results: Array<{
202
+ entity: EntityId;
203
+ velocity: Velocity;
204
+ positionRelation?: { value: [EntityId<unknown>, Position][] };
205
+ }> = [];
206
+
207
+ query.forEach(
208
+ [velocityComponent, { optional: wildcardPositionRelation }],
209
+ (entity, velocity, positionRelation) => {
210
+ results.push({ entity, velocity, positionRelation });
211
+ },
212
+ );
213
+
214
+ expect(results.length).toBe(2);
215
+
216
+ const result1 = results.find((r) => r.entity === entity1);
217
+ const result2 = results.find((r) => r.entity === entity2);
218
+
219
+ expect(result1).toBeDefined();
220
+ expect(result2).toBeDefined();
221
+
222
+ expect(result1!.velocity).toEqual({ x: 0.1, y: 0.2 });
223
+ expect(result1!.positionRelation).toEqual({
224
+ value: [[targetEntity, { x: 1, y: 2 }]],
225
+ });
226
+
227
+ expect(result2!.velocity).toEqual({ x: 0.2, y: 0.3 });
228
+ expect(result2!.positionRelation).toBeUndefined();
229
+ });
230
+ });
231
+ });
@@ -0,0 +1,99 @@
1
+ import { World, component } from "../../index";
2
+
3
+ // Define component types
4
+ type Position = { x: number; y: number };
5
+ type Velocity = { x: number; y: number };
6
+ type Health = { value: number };
7
+
8
+ // Create component IDs
9
+ const positionComponent = component<Position>();
10
+ const velocityComponent = component<Velocity>();
11
+ const healthComponent = component<Health>();
12
+
13
+ // Performance test function
14
+ function performanceTest() {
15
+ console.log("=== Query Performance Test ===");
16
+
17
+ const world = new World();
18
+
19
+ // Create many entities
20
+ console.log("Creating 1000 entities...");
21
+ const startCreate = performance.now();
22
+
23
+ for (let i = 0; i < 1000; i++) {
24
+ const entity = world.new();
25
+
26
+ // Add position component
27
+ world.set(entity, positionComponent, {
28
+ x: Math.random() * 100,
29
+ y: Math.random() * 100,
30
+ });
31
+
32
+ // 50% of entities have velocity component
33
+ if (i % 2 === 0) {
34
+ world.set(entity, velocityComponent, {
35
+ x: Math.random() - 0.5,
36
+ y: Math.random() - 0.5,
37
+ });
38
+ }
39
+
40
+ // 25% of entities have health component
41
+ if (i % 4 === 0) {
42
+ world.set(entity, healthComponent, {
43
+ value: Math.floor(Math.random() * 100) + 1,
44
+ });
45
+ }
46
+ }
47
+
48
+ world.sync();
49
+
50
+ const endCreate = performance.now();
51
+ console.log(`Entity creation time: ${(endCreate - startCreate).toFixed(2)}ms`);
52
+
53
+ // Create queries
54
+ const positionVelocityQuery = world.createQuery([positionComponent, velocityComponent]);
55
+ const healthQuery = world.createQuery([healthComponent]);
56
+
57
+ // Test getEntitiesWithComponents performance
58
+ console.log("\nTesting getEntitiesWithComponents performance...");
59
+ const iterations = 100;
60
+
61
+ let totalTime = 0;
62
+ for (let i = 0; i < iterations; i++) {
63
+ const start = performance.now();
64
+ positionVelocityQuery.getEntitiesWithComponents([positionComponent, velocityComponent]);
65
+ const end = performance.now();
66
+ totalTime += end - start;
67
+ }
68
+ console.log(`Average getEntitiesWithComponents time: ${(totalTime / iterations).toFixed(4)}ms`);
69
+
70
+ // Test forEach performance
71
+ totalTime = 0;
72
+ for (let i = 0; i < iterations; i++) {
73
+ const start = performance.now();
74
+ positionVelocityQuery.forEach([positionComponent, velocityComponent], (_entity, _position, _velocity) => {
75
+ // No-op, just for measuring iteration performance
76
+ });
77
+ const end = performance.now();
78
+ totalTime += end - start;
79
+ }
80
+ console.log(`Average forEach time: ${(totalTime / iterations).toFixed(4)}ms`);
81
+
82
+ // Verify result correctness
83
+ const entitiesWithData = positionVelocityQuery.getEntitiesWithComponents([positionComponent, velocityComponent]);
84
+ console.log(`\nFound ${entitiesWithData.length} entities with position and velocity`);
85
+
86
+ let forEachCount = 0;
87
+ positionVelocityQuery.forEach([positionComponent, velocityComponent], () => {
88
+ forEachCount++;
89
+ });
90
+ console.log(`forEach iterated over ${forEachCount} entities`);
91
+
92
+ // Cleanup
93
+ positionVelocityQuery.dispose();
94
+ healthQuery.dispose();
95
+
96
+ console.log("\nPerformance test completed!");
97
+ }
98
+
99
+ performanceTest();