@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.
- package/examples/advanced-scheduling.ts +96 -0
- package/examples/collision-detection.ts +229 -0
- package/examples/inventory-system-relations.ts +108 -0
- package/examples/parent-child-hierarchy.ts +206 -0
- package/examples/serialization.ts +337 -0
- package/examples/simple.ts +96 -0
- package/examples/spatial-grid.ts +276 -0
- package/examples/state-machine.ts +273 -0
- package/examples/tag-filtering.ts +266 -0
- package/package.json +58 -12
- package/src/__tests__/commands/buffer-limits.test.ts +72 -0
- package/src/__tests__/commands/buffer.test.ts +195 -0
- package/src/__tests__/component/singleton.test.ts +148 -0
- package/src/__tests__/core/archetype.test.ts +247 -0
- package/src/__tests__/core/bitset.test.ts +171 -0
- package/src/__tests__/core/changeset.test.ts +254 -0
- package/src/__tests__/core/multi-map.test.ts +74 -0
- package/src/__tests__/entity/component-registry.test.ts +66 -0
- package/src/__tests__/entity/entity.test.ts +520 -0
- package/src/__tests__/entity/id-manager.test.ts +157 -0
- package/src/__tests__/entity/id-system.test.ts +260 -0
- package/src/__tests__/perf/comprehensive.perf.test.ts +300 -0
- package/src/__tests__/perf/sync-hotpath.perf.test.ts +79 -0
- package/src/__tests__/query/basic.test.ts +341 -0
- package/src/__tests__/query/caching.test.ts +112 -0
- package/src/__tests__/query/filter.test.ts +111 -0
- package/src/__tests__/query/optional.test.ts +231 -0
- package/src/__tests__/query/perf.test.ts +99 -0
- package/src/__tests__/relations/dont-fragment/basic.test.ts +496 -0
- package/src/__tests__/relations/dont-fragment/query-notification.test.ts +125 -0
- package/src/__tests__/relations/wildcard.test.ts +179 -0
- package/src/__tests__/serialization/bounds.test.ts +237 -0
- package/src/__tests__/testing/assertions.test.ts +224 -0
- package/src/__tests__/testing/entity-builder.test.ts +84 -0
- package/src/__tests__/testing/snapshot.test.ts +150 -0
- package/src/__tests__/testing/world-fixture.test.ts +73 -0
- package/src/__tests__/world/component-hooks.test.ts +185 -0
- package/src/__tests__/world/component-management.test.ts +447 -0
- package/src/__tests__/world/entity-management.test.ts +86 -0
- package/src/__tests__/world/get-optional.test.ts +96 -0
- package/src/__tests__/world/multi-component-hooks.test.ts +502 -0
- package/src/__tests__/world/perf.test.ts +93 -0
- package/src/__tests__/world/query.test.ts +223 -0
- package/src/__tests__/world/serialize.test.ts +83 -0
- package/src/__tests__/world/wildcard-relation-hooks.test.ts +332 -0
- package/src/archetype/archetype.ts +472 -0
- package/src/archetype/helpers.ts +186 -0
- package/src/archetype/store.ts +33 -0
- package/src/commands/buffer.ts +110 -0
- package/src/commands/changeset.ts +104 -0
- package/src/component/entity-store.ts +223 -0
- package/src/component/registry.ts +657 -0
- package/src/component/type-utils.ts +9 -0
- package/src/entity/index.ts +63 -0
- package/src/entity/manager.ts +115 -0
- package/src/entity/relation.ts +319 -0
- package/src/entity/types.ts +135 -0
- package/src/index.ts +41 -0
- package/src/query/filter.ts +75 -0
- package/src/query/query.ts +313 -0
- package/src/query/registry.ts +101 -0
- package/src/storage/serialization.ts +130 -0
- package/src/testing/index.ts +634 -0
- package/src/types/index.ts +99 -0
- package/src/utils/bit-set.ts +133 -0
- package/src/utils/multi-map.ts +96 -0
- package/src/utils/utils.ts +19 -0
- package/src/world/builder.ts +100 -0
- package/src/world/commands.ts +378 -0
- package/src/world/hooks.ts +358 -0
- package/src/world/references.ts +38 -0
- package/src/world/serialization.ts +122 -0
- package/src/world/world.ts +1201 -0
- /package/{builder.d.mts → dist/builder.d.mts} +0 -0
- /package/{index.d.mts → dist/index.d.mts} +0 -0
- /package/{index.mjs → dist/index.mjs} +0 -0
- /package/{testing.d.mts → dist/testing.d.mts} +0 -0
- /package/{testing.mjs → dist/testing.mjs} +0 -0
- /package/{testing.mjs.map → dist/testing.mjs.map} +0 -0
- /package/{world.mjs → dist/world.mjs} +0 -0
- /package/{world.mjs.map → dist/world.mjs.map} +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { component, relation, type ComponentId } from "../../entity";
|
|
3
|
+
import { EntityBuilder } from "../../testing/index";
|
|
4
|
+
import { World } from "../../world/world";
|
|
5
|
+
|
|
6
|
+
let PositionId: ComponentId<{ x: number; y: number }>;
|
|
7
|
+
let VelocityId: ComponentId<{ x: number; y: number }>;
|
|
8
|
+
let HealthId: ComponentId<{ current: number; max: number }>;
|
|
9
|
+
let TagId: ComponentId<void>;
|
|
10
|
+
let ParentId: ComponentId<{ offset: { x: number; y: number } }>;
|
|
11
|
+
|
|
12
|
+
describe("EntityBuilder", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
PositionId = component<{ x: number; y: number }>();
|
|
15
|
+
VelocityId = component<{ x: number; y: number }>();
|
|
16
|
+
HealthId = component<{ current: number; max: number }>();
|
|
17
|
+
TagId = component<void>();
|
|
18
|
+
ParentId = component<{ offset: { x: number; y: number } }>();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should build entity with multiple components", () => {
|
|
22
|
+
const world = new World();
|
|
23
|
+
const entity = new EntityBuilder(world)
|
|
24
|
+
.with(PositionId, { x: 1, y: 2 })
|
|
25
|
+
.with(VelocityId, { x: 3, y: 4 })
|
|
26
|
+
.with(HealthId, { current: 100, max: 100 })
|
|
27
|
+
.build();
|
|
28
|
+
world.sync();
|
|
29
|
+
|
|
30
|
+
expect(world.has(entity, PositionId)).toBe(true);
|
|
31
|
+
expect(world.has(entity, VelocityId)).toBe(true);
|
|
32
|
+
expect(world.has(entity, HealthId)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should support tag components", () => {
|
|
36
|
+
const world = new World();
|
|
37
|
+
const entity = new EntityBuilder(world).with(TagId).build();
|
|
38
|
+
world.sync();
|
|
39
|
+
|
|
40
|
+
expect(world.has(entity, TagId)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should support relations", () => {
|
|
44
|
+
const world = new World();
|
|
45
|
+
const parent = new EntityBuilder(world).with(PositionId, { x: 0, y: 0 }).build();
|
|
46
|
+
|
|
47
|
+
const child = new EntityBuilder(world)
|
|
48
|
+
.with(PositionId, { x: 10, y: 10 })
|
|
49
|
+
.withRelation(ParentId, parent, { offset: { x: 5, y: 5 } })
|
|
50
|
+
.build();
|
|
51
|
+
world.sync();
|
|
52
|
+
|
|
53
|
+
const parentRelationId = relation(ParentId, parent);
|
|
54
|
+
expect(world.has(child, parentRelationId)).toBe(true);
|
|
55
|
+
expect(world.get(child, parentRelationId)).toEqual({ offset: { x: 5, y: 5 } });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should support relation tags", () => {
|
|
59
|
+
const ChildOfId = component<void>();
|
|
60
|
+
const world = new World();
|
|
61
|
+
const parent = world.new();
|
|
62
|
+
world.sync();
|
|
63
|
+
|
|
64
|
+
const child = new EntityBuilder(world).withRelation(ChildOfId, parent).build();
|
|
65
|
+
world.sync();
|
|
66
|
+
|
|
67
|
+
const relationId = relation(ChildOfId, parent);
|
|
68
|
+
expect(world.has(child, relationId)).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should support deferred build", () => {
|
|
72
|
+
const world = new World();
|
|
73
|
+
const e1 = new EntityBuilder(world).with(PositionId, { x: 1, y: 1 }).build();
|
|
74
|
+
const e2 = new EntityBuilder(world).with(PositionId, { x: 2, y: 2 }).build();
|
|
75
|
+
|
|
76
|
+
expect(world.has(e1, PositionId)).toBe(false);
|
|
77
|
+
expect(world.has(e2, PositionId)).toBe(false);
|
|
78
|
+
|
|
79
|
+
world.sync();
|
|
80
|
+
|
|
81
|
+
expect(world.has(e1, PositionId)).toBe(true);
|
|
82
|
+
expect(world.has(e2, PositionId)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { component, type ComponentId } from "../../entity";
|
|
3
|
+
import { Snapshot, WorldFixture, type WorldSnapshot } from "../../testing/index";
|
|
4
|
+
|
|
5
|
+
let PositionId: ComponentId<{ x: number; y: number }>;
|
|
6
|
+
let VelocityId: ComponentId<{ x: number; y: number }>;
|
|
7
|
+
|
|
8
|
+
describe("Snapshot", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
PositionId = component<{ x: number; y: number }>();
|
|
11
|
+
VelocityId = component<{ x: number; y: number }>();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should capture entity state", () => {
|
|
15
|
+
const fixture = new WorldFixture();
|
|
16
|
+
const entity = fixture.spawn().with(PositionId, { x: 10, y: 20 }).with(VelocityId, { x: 1, y: 2 }).build();
|
|
17
|
+
fixture.sync();
|
|
18
|
+
const snapshot = Snapshot.capture(fixture.world, [entity], [PositionId, VelocityId]);
|
|
19
|
+
|
|
20
|
+
expect(snapshot.entities).toHaveLength(1);
|
|
21
|
+
expect(snapshot.entities[0]!.entity).toBe(entity);
|
|
22
|
+
expect(snapshot.entities[0]!.components.get(PositionId)).toEqual({ x: 10, y: 20 });
|
|
23
|
+
expect(snapshot.entities[0]!.components.get(VelocityId)).toEqual({ x: 1, y: 2 });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should capture multiple entities", () => {
|
|
27
|
+
const fixture = new WorldFixture();
|
|
28
|
+
const e1 = fixture.spawn().with(PositionId, { x: 1, y: 1 }).build();
|
|
29
|
+
const e2 = fixture.spawn().with(PositionId, { x: 2, y: 2 }).build();
|
|
30
|
+
fixture.sync();
|
|
31
|
+
|
|
32
|
+
const snapshot = Snapshot.capture(fixture.world, [e1, e2], [PositionId]);
|
|
33
|
+
|
|
34
|
+
expect(snapshot.entities).toHaveLength(2);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should skip non-existent entities", () => {
|
|
38
|
+
const fixture = new WorldFixture();
|
|
39
|
+
const entity = fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
40
|
+
fixture.world.delete(entity);
|
|
41
|
+
fixture.sync();
|
|
42
|
+
|
|
43
|
+
const snapshot = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
44
|
+
|
|
45
|
+
expect(snapshot.entities).toHaveLength(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should detect added entities in diff", () => {
|
|
49
|
+
const before: WorldSnapshot = { entities: [] };
|
|
50
|
+
const fixture = new WorldFixture();
|
|
51
|
+
const entity = fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
52
|
+
fixture.sync();
|
|
53
|
+
const after = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
54
|
+
|
|
55
|
+
const diff = Snapshot.compare(before, after);
|
|
56
|
+
|
|
57
|
+
expect(diff.addedEntities).toContain(entity);
|
|
58
|
+
expect(diff.removedEntities).toHaveLength(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should detect removed entities in diff", () => {
|
|
62
|
+
const fixture = new WorldFixture();
|
|
63
|
+
const entity = fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
64
|
+
fixture.sync();
|
|
65
|
+
const before = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
66
|
+
|
|
67
|
+
fixture.world.delete(entity);
|
|
68
|
+
fixture.sync();
|
|
69
|
+
|
|
70
|
+
const after = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
71
|
+
const diff = Snapshot.compare(before, after);
|
|
72
|
+
|
|
73
|
+
expect(diff.removedEntities).toContain(entity);
|
|
74
|
+
expect(diff.addedEntities).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should detect component changes in diff", () => {
|
|
78
|
+
const fixture = new WorldFixture();
|
|
79
|
+
const entity = fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
80
|
+
fixture.sync();
|
|
81
|
+
const before = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
82
|
+
|
|
83
|
+
fixture.world.set(entity, PositionId, { x: 100, y: 200 });
|
|
84
|
+
fixture.sync();
|
|
85
|
+
|
|
86
|
+
const after = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
87
|
+
const diff = Snapshot.compare(before, after);
|
|
88
|
+
|
|
89
|
+
expect(diff.componentChanges).toHaveLength(1);
|
|
90
|
+
expect(diff.componentChanges[0]!.changeType).toBe("modified");
|
|
91
|
+
expect(diff.componentChanges[0]!.before).toEqual({ x: 0, y: 0 });
|
|
92
|
+
expect(diff.componentChanges[0]!.after).toEqual({ x: 100, y: 200 });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should detect added components in diff", () => {
|
|
96
|
+
const fixture = new WorldFixture();
|
|
97
|
+
const entity = fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
98
|
+
fixture.sync();
|
|
99
|
+
const before = Snapshot.capture(fixture.world, [entity], [PositionId, VelocityId]);
|
|
100
|
+
|
|
101
|
+
fixture.world.set(entity, VelocityId, { x: 1, y: 1 });
|
|
102
|
+
fixture.sync();
|
|
103
|
+
|
|
104
|
+
const after = Snapshot.capture(fixture.world, [entity], [PositionId, VelocityId]);
|
|
105
|
+
const diff = Snapshot.compare(before, after);
|
|
106
|
+
|
|
107
|
+
const velocityChange = diff.componentChanges.find((c) => c.componentId === VelocityId);
|
|
108
|
+
expect(velocityChange).toBeDefined();
|
|
109
|
+
expect(velocityChange!.changeType).toBe("added");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("should detect removed components in diff", () => {
|
|
113
|
+
const fixture = new WorldFixture();
|
|
114
|
+
const entity = fixture.spawn().with(PositionId, { x: 0, y: 0 }).with(VelocityId, { x: 1, y: 1 }).build();
|
|
115
|
+
fixture.sync();
|
|
116
|
+
const before = Snapshot.capture(fixture.world, [entity], [PositionId, VelocityId]);
|
|
117
|
+
|
|
118
|
+
fixture.world.remove(entity, VelocityId);
|
|
119
|
+
fixture.sync();
|
|
120
|
+
|
|
121
|
+
const after = Snapshot.capture(fixture.world, [entity], [PositionId, VelocityId]);
|
|
122
|
+
const diff = Snapshot.compare(before, after);
|
|
123
|
+
|
|
124
|
+
const velocityChange = diff.componentChanges.find((c) => c.componentId === VelocityId);
|
|
125
|
+
expect(velocityChange).toBeDefined();
|
|
126
|
+
expect(velocityChange!.changeType).toBe("removed");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should check snapshot equality", () => {
|
|
130
|
+
const fixture = new WorldFixture();
|
|
131
|
+
const entity = fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
132
|
+
fixture.sync();
|
|
133
|
+
const snapshot1 = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
134
|
+
const snapshot2 = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
135
|
+
|
|
136
|
+
expect(Snapshot.equals(snapshot1, snapshot2)).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should isolate snapshots from mutations", () => {
|
|
140
|
+
const fixture = new WorldFixture();
|
|
141
|
+
const entity = fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
142
|
+
fixture.sync();
|
|
143
|
+
const snapshot = Snapshot.capture(fixture.world, [entity], [PositionId]);
|
|
144
|
+
|
|
145
|
+
fixture.world.set(entity, PositionId, { x: 999, y: 999 });
|
|
146
|
+
fixture.sync();
|
|
147
|
+
|
|
148
|
+
expect(snapshot.entities[0]!.components.get(PositionId)).toEqual({ x: 0, y: 0 });
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { component, type ComponentId } from "../../entity";
|
|
3
|
+
import { WorldFixture } from "../../testing/index";
|
|
4
|
+
import { World } from "../../world/world";
|
|
5
|
+
|
|
6
|
+
let PositionId: ComponentId<{ x: number; y: number }>;
|
|
7
|
+
|
|
8
|
+
describe("WorldFixture", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
PositionId = component<{ x: number; y: number }>();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should create a world instance", () => {
|
|
14
|
+
const fixture = new WorldFixture();
|
|
15
|
+
expect(fixture.world).toBeInstanceOf(World);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should spawn entities with fluent API", () => {
|
|
19
|
+
const fixture = new WorldFixture();
|
|
20
|
+
const entity = fixture.spawn().with(PositionId, { x: 10, y: 20 }).build();
|
|
21
|
+
fixture.sync();
|
|
22
|
+
|
|
23
|
+
expect(fixture.world.exists(entity)).toBe(true);
|
|
24
|
+
expect(fixture.world.has(entity, PositionId)).toBe(true);
|
|
25
|
+
expect(fixture.world.get(entity, PositionId)).toEqual({ x: 10, y: 20 });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should spawn multiple entities", () => {
|
|
29
|
+
const fixture = new WorldFixture();
|
|
30
|
+
const entities = fixture.spawnMany(3, (builder, index) =>
|
|
31
|
+
builder.with(PositionId, { x: index * 10, y: index * 20 }),
|
|
32
|
+
);
|
|
33
|
+
fixture.sync();
|
|
34
|
+
|
|
35
|
+
expect(entities).toHaveLength(3);
|
|
36
|
+
expect(fixture.world.get(entities[0]!, PositionId)).toEqual({ x: 0, y: 0 });
|
|
37
|
+
expect(fixture.world.get(entities[1]!, PositionId)).toEqual({ x: 10, y: 20 });
|
|
38
|
+
expect(fixture.world.get(entities[2]!, PositionId)).toEqual({ x: 20, y: 40 });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should reset to fresh world", () => {
|
|
42
|
+
const fixture = new WorldFixture();
|
|
43
|
+
const entity = fixture.spawn().with(PositionId, { x: 10, y: 20 }).build();
|
|
44
|
+
const oldWorld = fixture.world;
|
|
45
|
+
|
|
46
|
+
fixture.reset();
|
|
47
|
+
|
|
48
|
+
expect(fixture.world).not.toBe(oldWorld);
|
|
49
|
+
expect(fixture.world.exists(entity)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should track and dispose queries on reset", () => {
|
|
53
|
+
const fixture = new WorldFixture();
|
|
54
|
+
fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
55
|
+
fixture.sync();
|
|
56
|
+
|
|
57
|
+
const query = fixture.createQuery([PositionId]);
|
|
58
|
+
expect(query.getEntities()).toHaveLength(1);
|
|
59
|
+
|
|
60
|
+
fixture.reset();
|
|
61
|
+
expect(query.disposed).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should support Symbol.dispose", () => {
|
|
65
|
+
const fixture = new WorldFixture();
|
|
66
|
+
fixture.spawn().with(PositionId, { x: 0, y: 0 }).build();
|
|
67
|
+
fixture.sync();
|
|
68
|
+
const query = fixture.createQuery([PositionId]);
|
|
69
|
+
|
|
70
|
+
fixture[Symbol.dispose]();
|
|
71
|
+
expect(query.disposed).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { component, type EntityId } from "../../entity";
|
|
3
|
+
import { World } from "../../world/world";
|
|
4
|
+
|
|
5
|
+
describe("World - Single Component Hooks", () => {
|
|
6
|
+
type Position = { x: number; y: number };
|
|
7
|
+
|
|
8
|
+
const positionComponent = component<Position>();
|
|
9
|
+
|
|
10
|
+
it("should trigger component initialized hooks", () => {
|
|
11
|
+
const world = new World();
|
|
12
|
+
const entity = world.new();
|
|
13
|
+
const position: Position = { x: 10, y: 20 };
|
|
14
|
+
|
|
15
|
+
let hookCalled = false;
|
|
16
|
+
let hookEntityId: EntityId | undefined;
|
|
17
|
+
|
|
18
|
+
let hookComponent: Position | undefined;
|
|
19
|
+
|
|
20
|
+
world.set(entity, positionComponent, position);
|
|
21
|
+
world.sync();
|
|
22
|
+
|
|
23
|
+
world.hook([positionComponent], {
|
|
24
|
+
on_init: (entityId, component) => {
|
|
25
|
+
hookCalled = true;
|
|
26
|
+
hookEntityId = entityId;
|
|
27
|
+
hookComponent = component;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(hookCalled).toBe(true);
|
|
32
|
+
expect(hookEntityId).toBe(entity);
|
|
33
|
+
expect(hookComponent).toEqual(position);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should trigger component added hooks", () => {
|
|
37
|
+
const world = new World();
|
|
38
|
+
const entity = world.new();
|
|
39
|
+
const position: Position = { x: 10, y: 20 };
|
|
40
|
+
|
|
41
|
+
let hookCalled = false;
|
|
42
|
+
let hookEntityId: EntityId | undefined;
|
|
43
|
+
let hookComponent: Position | undefined;
|
|
44
|
+
|
|
45
|
+
world.hook([positionComponent], {
|
|
46
|
+
on_set: (entityId, component) => {
|
|
47
|
+
hookCalled = true;
|
|
48
|
+
hookEntityId = entityId;
|
|
49
|
+
hookComponent = component;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
world.set(entity, positionComponent, position);
|
|
54
|
+
world.sync();
|
|
55
|
+
|
|
56
|
+
expect(hookCalled).toBe(true);
|
|
57
|
+
expect(hookEntityId).toBe(entity);
|
|
58
|
+
expect(hookComponent).toEqual(position);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should trigger component removed hooks", () => {
|
|
62
|
+
const world = new World();
|
|
63
|
+
const entity = world.new();
|
|
64
|
+
const position: Position = { x: 10, y: 20 };
|
|
65
|
+
|
|
66
|
+
world.set(entity, positionComponent, position);
|
|
67
|
+
world.sync();
|
|
68
|
+
|
|
69
|
+
let hookCalled = false;
|
|
70
|
+
let hookEntityId: EntityId | undefined;
|
|
71
|
+
let hookComponent: Position | undefined;
|
|
72
|
+
|
|
73
|
+
world.hook([positionComponent], {
|
|
74
|
+
on_remove: (entityId, component) => {
|
|
75
|
+
hookCalled = true;
|
|
76
|
+
hookEntityId = entityId;
|
|
77
|
+
hookComponent = component;
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
world.remove(entity, positionComponent);
|
|
82
|
+
world.sync();
|
|
83
|
+
|
|
84
|
+
expect(hookCalled).toBe(true);
|
|
85
|
+
expect(hookEntityId).toBe(entity);
|
|
86
|
+
expect(hookComponent).toEqual(position);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should handle multiple hooks for the same component type", () => {
|
|
90
|
+
const world = new World();
|
|
91
|
+
const entity = world.new();
|
|
92
|
+
const position: Position = { x: 10, y: 20 };
|
|
93
|
+
|
|
94
|
+
let hook1Called = false;
|
|
95
|
+
let hook2Called = false;
|
|
96
|
+
|
|
97
|
+
world.hook([positionComponent], {
|
|
98
|
+
on_set: () => {
|
|
99
|
+
hook1Called = true;
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
world.hook([positionComponent], {
|
|
104
|
+
on_set: () => {
|
|
105
|
+
hook2Called = true;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
world.set(entity, positionComponent, position);
|
|
110
|
+
world.sync();
|
|
111
|
+
|
|
112
|
+
expect(hook1Called).toBe(true);
|
|
113
|
+
expect(hook2Called).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should support hooks with both onAdded and onRemoved", () => {
|
|
117
|
+
const world = new World();
|
|
118
|
+
const entity = world.new();
|
|
119
|
+
const position: Position = { x: 10, y: 20 };
|
|
120
|
+
|
|
121
|
+
let addedCalled = false;
|
|
122
|
+
let removedCalled = false;
|
|
123
|
+
|
|
124
|
+
world.hook([positionComponent], {
|
|
125
|
+
on_set: () => {
|
|
126
|
+
addedCalled = true;
|
|
127
|
+
},
|
|
128
|
+
on_remove: () => {
|
|
129
|
+
removedCalled = true;
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
world.set(entity, positionComponent, position);
|
|
134
|
+
world.sync();
|
|
135
|
+
|
|
136
|
+
expect(addedCalled).toBe(true);
|
|
137
|
+
expect(removedCalled).toBe(false);
|
|
138
|
+
|
|
139
|
+
world.remove(entity, positionComponent);
|
|
140
|
+
world.sync();
|
|
141
|
+
|
|
142
|
+
expect(removedCalled).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should support hooks with only onAdded", () => {
|
|
146
|
+
const world = new World();
|
|
147
|
+
const entity = world.new();
|
|
148
|
+
const position: Position = { x: 10, y: 20 };
|
|
149
|
+
|
|
150
|
+
let addedCalled = false;
|
|
151
|
+
|
|
152
|
+
world.hook([positionComponent], {
|
|
153
|
+
on_set: () => {
|
|
154
|
+
addedCalled = true;
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
world.set(entity, positionComponent, position);
|
|
159
|
+
world.sync();
|
|
160
|
+
|
|
161
|
+
expect(addedCalled).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should support hooks with only onRemoved", () => {
|
|
165
|
+
const world = new World();
|
|
166
|
+
const entity = world.new();
|
|
167
|
+
const position: Position = { x: 10, y: 20 };
|
|
168
|
+
|
|
169
|
+
world.set(entity, positionComponent, position);
|
|
170
|
+
world.sync();
|
|
171
|
+
|
|
172
|
+
let removedCalled = false;
|
|
173
|
+
|
|
174
|
+
world.hook([positionComponent], {
|
|
175
|
+
on_remove: () => {
|
|
176
|
+
removedCalled = true;
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
world.remove(entity, positionComponent);
|
|
181
|
+
world.sync();
|
|
182
|
+
|
|
183
|
+
expect(removedCalled).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
});
|