@codehz/ecs 0.0.2 → 0.0.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/README.md +50 -10
- package/entity.d.ts +39 -23
- package/index.js +58 -59
- package/package.json +1 -1
- package/world.d.ts +7 -21
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- 🏗️ 模块化:清晰的架构,支持自定义系统和组件
|
|
10
10
|
- 📦 轻量级:零依赖,易于集成
|
|
11
11
|
- ⚡ 内存高效:连续内存布局,优化的迭代性能
|
|
12
|
+
- 🎣 生命周期钩子:支持组件和通配符关系的事件监听
|
|
12
13
|
|
|
13
14
|
## 安装
|
|
14
15
|
|
|
@@ -22,15 +23,15 @@ bun install
|
|
|
22
23
|
|
|
23
24
|
```typescript
|
|
24
25
|
import { World } from "@codehz/ecs";
|
|
25
|
-
import {
|
|
26
|
+
import { component } from "@codehz/ecs";
|
|
26
27
|
|
|
27
28
|
// 定义组件类型
|
|
28
29
|
type Position = { x: number; y: number };
|
|
29
30
|
type Velocity = { x: number; y: number };
|
|
30
31
|
|
|
31
32
|
// 定义组件ID
|
|
32
|
-
const PositionId =
|
|
33
|
-
const VelocityId =
|
|
33
|
+
const PositionId = component<Position>(1);
|
|
34
|
+
const VelocityId = component<Velocity>(2);
|
|
34
35
|
|
|
35
36
|
// 创建世界
|
|
36
37
|
const world = new World();
|
|
@@ -58,20 +59,20 @@ ECS 支持在组件添加或移除时执行回调函数:
|
|
|
58
59
|
|
|
59
60
|
```typescript
|
|
60
61
|
// 注册组件生命周期钩子
|
|
61
|
-
world.
|
|
62
|
+
world.registerLifecycleHook(PositionId, {
|
|
62
63
|
onAdded: (entityId, componentType, component) => {
|
|
63
64
|
console.log(`组件 ${componentType} 被添加到实体 ${entityId}`);
|
|
64
65
|
},
|
|
65
66
|
onRemoved: (entityId, componentType) => {
|
|
66
67
|
console.log(`组件 ${componentType} 被从实体 ${entityId} 移除`);
|
|
67
|
-
}
|
|
68
|
+
},
|
|
68
69
|
});
|
|
69
70
|
|
|
70
71
|
// 你也可以只注册其中一个钩子
|
|
71
|
-
world.
|
|
72
|
+
world.registerLifecycleHook(VelocityId, {
|
|
72
73
|
onRemoved: (entityId, componentType) => {
|
|
73
74
|
console.log(`组件 ${componentType} 被从实体 ${entityId} 移除`);
|
|
74
|
-
}
|
|
75
|
+
},
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
// 添加组件时会触发钩子
|
|
@@ -79,6 +80,45 @@ world.addComponent(entity, PositionId, { x: 0, y: 0 });
|
|
|
79
80
|
world.flushCommands(); // 钩子在这里被调用
|
|
80
81
|
```
|
|
81
82
|
|
|
83
|
+
### 通配符关系生命周期钩子
|
|
84
|
+
|
|
85
|
+
ECS 还支持通配符关系生命周期钩子,可以监听特定组件的所有关系变化:
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { World, component, relation } from "@codehz/ecs";
|
|
89
|
+
|
|
90
|
+
// 定义组件类型
|
|
91
|
+
type Position = { x: number; y: number };
|
|
92
|
+
|
|
93
|
+
// 定义组件ID
|
|
94
|
+
const PositionId = component<Position>(1);
|
|
95
|
+
|
|
96
|
+
// 创建世界
|
|
97
|
+
const world = new World();
|
|
98
|
+
|
|
99
|
+
// 创建实体
|
|
100
|
+
const entity = world.createEntity();
|
|
101
|
+
|
|
102
|
+
// 创建通配符关系ID,用于监听所有 Position 相关的关系
|
|
103
|
+
const wildcardPositionRelation = relation(PositionId, "*");
|
|
104
|
+
|
|
105
|
+
// 注册通配符关系钩子
|
|
106
|
+
world.registerLifecycleHook(wildcardPositionRelation, {
|
|
107
|
+
onAdded: (entityId, componentType, component) => {
|
|
108
|
+
console.log(`关系组件 ${componentType} 被添加到实体 ${entityId}`);
|
|
109
|
+
},
|
|
110
|
+
onRemoved: (entityId, componentType) => {
|
|
111
|
+
console.log(`关系组件 ${componentType} 被从实体 ${entityId} 移除`);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// 创建实体间的关系
|
|
116
|
+
const entity2 = world.createEntity();
|
|
117
|
+
const positionRelation = relation(PositionId, entity2);
|
|
118
|
+
world.addComponent(entity, positionRelation, { x: 10, y: 20 });
|
|
119
|
+
world.flushCommands(); // 通配符钩子会被触发
|
|
120
|
+
```
|
|
121
|
+
|
|
82
122
|
### 运行示例
|
|
83
123
|
|
|
84
124
|
```bash
|
|
@@ -100,14 +140,14 @@ bun run examples/simple/demo.ts
|
|
|
100
140
|
- `removeComponent(entity, componentId)`: 从实体移除组件
|
|
101
141
|
- `createQuery(componentIds)`: 创建查询
|
|
102
142
|
- `registerSystem(system)`: 注册系统
|
|
103
|
-
- `
|
|
104
|
-
- `
|
|
143
|
+
- `registerLifecycleHook(componentId, hook)`: 注册组件或通配符关系生命周期钩子
|
|
144
|
+
- `unregisterLifecycleHook(componentId, hook)`: 注销组件或通配符关系生命周期钩子
|
|
105
145
|
- `update(deltaTime)`: 更新世界
|
|
106
146
|
- `flushCommands()`: 应用命令缓冲区
|
|
107
147
|
|
|
108
148
|
### Entity
|
|
109
149
|
|
|
110
|
-
- `
|
|
150
|
+
- `component<T>(id)`: 分配类型安全的组件ID(上限:1022个)
|
|
111
151
|
|
|
112
152
|
### Query
|
|
113
153
|
|
package/entity.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Unique symbol brand for associating component type information with EntityId
|
|
3
3
|
*/
|
|
4
|
-
declare const
|
|
4
|
+
declare const __componentTypeMarker: unique symbol;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Unique symbol brand for tagging the kind of EntityId (e.g., 'component', 'entity-relation')
|
|
7
7
|
*/
|
|
8
|
-
declare const
|
|
8
|
+
declare const __entityIdTypeTag: unique symbol;
|
|
9
9
|
/**
|
|
10
10
|
* Entity ID type for ECS architecture
|
|
11
11
|
* Based on 52-bit integers within safe integer range
|
|
@@ -13,12 +13,15 @@ declare const __wildcardRelationBrand: unique symbol;
|
|
|
13
13
|
* - Entity IDs: 1024+
|
|
14
14
|
* - Relation IDs: negative numbers encoding component and entity associations
|
|
15
15
|
*/
|
|
16
|
-
export type EntityId<T = void> = number & {
|
|
17
|
-
readonly [
|
|
18
|
-
|
|
19
|
-
export type WildcardRelationId<T = void> = EntityId<T> & {
|
|
20
|
-
readonly [__wildcardRelationBrand]: true;
|
|
16
|
+
export type EntityId<T = void, U = unknown> = number & {
|
|
17
|
+
readonly [__componentTypeMarker]: T;
|
|
18
|
+
readonly [__entityIdTypeTag]: U;
|
|
21
19
|
};
|
|
20
|
+
export type ComponentId<T = void> = EntityId<T, "component">;
|
|
21
|
+
export type EntityRelationId<T = void> = EntityId<T, "entity-relation">;
|
|
22
|
+
export type ComponentRelationId<T = void> = EntityId<T, "component-relation">;
|
|
23
|
+
export type WildcardRelationId<T = void> = EntityId<T, "wildcard-relation">;
|
|
24
|
+
export type RelationId<T = void> = EntityRelationId<T> | ComponentRelationId<T> | WildcardRelationId<T>;
|
|
22
25
|
/**
|
|
23
26
|
* Constants for ID ranges
|
|
24
27
|
*/
|
|
@@ -33,8 +36,9 @@ export declare const WILDCARD_TARGET_ID = 0;
|
|
|
33
36
|
/**
|
|
34
37
|
* Create a component ID
|
|
35
38
|
* @param id Component identifier (1-1023)
|
|
39
|
+
* @see component
|
|
36
40
|
*/
|
|
37
|
-
export declare function createComponentId<T = void>(id: number):
|
|
41
|
+
export declare function createComponentId<T = void>(id: number): ComponentId<T>;
|
|
38
42
|
/**
|
|
39
43
|
* Create an entity ID
|
|
40
44
|
* @param id Entity identifier (starting from 1024)
|
|
@@ -43,33 +47,37 @@ export declare function createEntityId(id: number): EntityId;
|
|
|
43
47
|
/**
|
|
44
48
|
* Type for relation ID based on component and target types
|
|
45
49
|
*/
|
|
46
|
-
type RelationIdType<T,
|
|
50
|
+
type RelationIdType<T, R> = R extends ComponentId<infer U> ? U extends void ? ComponentRelationId<T> : ComponentRelationId<T & U> : R extends EntityId<any> ? EntityRelationId<T> : never;
|
|
47
51
|
/**
|
|
48
52
|
* Create a relation ID by associating a component with another ID (entity or component)
|
|
49
53
|
* @param componentId The component ID (0-1023)
|
|
50
54
|
* @param targetId The target ID (entity, component, or '*' for wildcard)
|
|
51
55
|
*/
|
|
52
|
-
export declare function
|
|
53
|
-
export declare function
|
|
56
|
+
export declare function relation<T>(componentId: ComponentId<T>, targetId: "*"): WildcardRelationId<T>;
|
|
57
|
+
export declare function relation<T, R extends EntityId<any>>(componentId: ComponentId<T>, targetId: R): RelationIdType<T, R>;
|
|
54
58
|
/**
|
|
55
59
|
* Check if an ID is a component ID
|
|
56
60
|
*/
|
|
57
|
-
export declare function isComponentId(id: EntityId<
|
|
61
|
+
export declare function isComponentId<T>(id: EntityId<T>): id is ComponentId<T>;
|
|
58
62
|
/**
|
|
59
63
|
* Check if an ID is an entity ID
|
|
60
64
|
*/
|
|
61
|
-
export declare function isEntityId(id: EntityId<
|
|
65
|
+
export declare function isEntityId<T>(id: EntityId<T>): id is EntityId<T>;
|
|
62
66
|
/**
|
|
63
67
|
* Check if an ID is a relation ID
|
|
64
68
|
*/
|
|
65
|
-
export declare function isRelationId(id: EntityId):
|
|
69
|
+
export declare function isRelationId<T>(id: EntityId<T>): id is RelationId<T>;
|
|
70
|
+
/**
|
|
71
|
+
* Check if an ID is a wildcard relation id
|
|
72
|
+
*/
|
|
73
|
+
export declare function isWildcardRelationId<T>(id: EntityId<T>): id is WildcardRelationId<T>;
|
|
66
74
|
/**
|
|
67
75
|
* Decode a relation ID into component and target IDs
|
|
68
76
|
* @param relationId The relation ID (must be negative)
|
|
69
77
|
* @returns Object with componentId, targetId, and relation type
|
|
70
78
|
*/
|
|
71
|
-
export declare function decodeRelationId(relationId:
|
|
72
|
-
componentId:
|
|
79
|
+
export declare function decodeRelationId(relationId: RelationId<any>): {
|
|
80
|
+
componentId: ComponentId<any>;
|
|
73
81
|
targetId: EntityId<any>;
|
|
74
82
|
type: "entity" | "component" | "wildcard";
|
|
75
83
|
};
|
|
@@ -83,9 +91,13 @@ export declare function getIdType(id: EntityId<any>): "component" | "entity" | "
|
|
|
83
91
|
* @returns Detailed type information including relation subtypes
|
|
84
92
|
*/
|
|
85
93
|
export declare function getDetailedIdType(id: EntityId<any>): {
|
|
86
|
-
type: "component" | "entity" | "
|
|
87
|
-
componentId?:
|
|
88
|
-
targetId?:
|
|
94
|
+
type: "component" | "entity" | "invalid";
|
|
95
|
+
componentId?: never;
|
|
96
|
+
targetId?: never;
|
|
97
|
+
} | {
|
|
98
|
+
type: "entity-relation" | "component-relation" | "wildcard-relation";
|
|
99
|
+
componentId: ComponentId<any>;
|
|
100
|
+
targetId: EntityId<any>;
|
|
89
101
|
};
|
|
90
102
|
/**
|
|
91
103
|
* Inspect an EntityId and return a human-readable string representation
|
|
@@ -122,13 +134,13 @@ export declare class EntityIdManager {
|
|
|
122
134
|
* Component ID Manager for automatic allocation
|
|
123
135
|
* Components are typically registered once and not recycled
|
|
124
136
|
*/
|
|
125
|
-
export declare class
|
|
137
|
+
export declare class ComponentIdAllocator {
|
|
126
138
|
private nextId;
|
|
127
139
|
/**
|
|
128
140
|
* Allocate a new component ID
|
|
129
141
|
* Increments counter sequentially from 1
|
|
130
142
|
*/
|
|
131
|
-
allocate<T = void>():
|
|
143
|
+
allocate<T = void>(): ComponentId<T>;
|
|
132
144
|
/**
|
|
133
145
|
* Get the next ID that would be allocated (for debugging)
|
|
134
146
|
*/
|
|
@@ -138,4 +150,8 @@ export declare class ComponentIdManager {
|
|
|
138
150
|
*/
|
|
139
151
|
hasAvailableIds(): boolean;
|
|
140
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Allocate a new component ID from the global allocator
|
|
155
|
+
*/
|
|
156
|
+
export declare function component<T>(): ComponentId<T>;
|
|
141
157
|
export {};
|
package/index.js
CHANGED
|
@@ -16,7 +16,7 @@ function createEntityId(id) {
|
|
|
16
16
|
}
|
|
17
17
|
return id;
|
|
18
18
|
}
|
|
19
|
-
function
|
|
19
|
+
function relation(componentId, targetId) {
|
|
20
20
|
if (!isComponentId(componentId)) {
|
|
21
21
|
throw new Error("First argument must be a valid component ID");
|
|
22
22
|
}
|
|
@@ -40,6 +40,14 @@ function isEntityId(id) {
|
|
|
40
40
|
function isRelationId(id) {
|
|
41
41
|
return id < 0;
|
|
42
42
|
}
|
|
43
|
+
function isWildcardRelationId(id) {
|
|
44
|
+
if (!isRelationId(id)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const absId = -id;
|
|
48
|
+
const targetId = absId % RELATION_SHIFT;
|
|
49
|
+
return targetId === WILDCARD_TARGET_ID;
|
|
50
|
+
}
|
|
43
51
|
function decodeRelationId(relationId) {
|
|
44
52
|
if (!isRelationId(relationId)) {
|
|
45
53
|
throw new Error("ID is not a relation ID");
|
|
@@ -178,7 +186,7 @@ class EntityIdManager {
|
|
|
178
186
|
}
|
|
179
187
|
}
|
|
180
188
|
|
|
181
|
-
class
|
|
189
|
+
class ComponentIdAllocator {
|
|
182
190
|
nextId = 1;
|
|
183
191
|
allocate() {
|
|
184
192
|
if (this.nextId > COMPONENT_ID_MAX) {
|
|
@@ -195,6 +203,10 @@ class ComponentIdManager {
|
|
|
195
203
|
return this.nextId <= COMPONENT_ID_MAX;
|
|
196
204
|
}
|
|
197
205
|
}
|
|
206
|
+
var globalComponentIdAllocator = new ComponentIdAllocator;
|
|
207
|
+
function component() {
|
|
208
|
+
return globalComponentIdAllocator.allocate();
|
|
209
|
+
}
|
|
198
210
|
// src/utils.ts
|
|
199
211
|
function getOrComputeCache(cache, key, compute) {
|
|
200
212
|
let value = cache.get(key);
|
|
@@ -281,16 +293,16 @@ class Archetype {
|
|
|
281
293
|
return;
|
|
282
294
|
}
|
|
283
295
|
}
|
|
284
|
-
if (
|
|
296
|
+
if (isWildcardRelationId(componentType)) {
|
|
285
297
|
const decoded = decodeRelationId(componentType);
|
|
286
298
|
const componentId = decoded.componentId;
|
|
287
299
|
const relations = [];
|
|
288
300
|
for (const relType of this.componentTypes) {
|
|
289
|
-
const
|
|
290
|
-
if (
|
|
301
|
+
const relDetailed = getDetailedIdType(relType);
|
|
302
|
+
if ((relDetailed.type === "entity-relation" || relDetailed.type === "component-relation") && relDetailed.componentId === componentId) {
|
|
291
303
|
const dataArray = this.componentData.get(relType);
|
|
292
304
|
if (dataArray && dataArray[index] !== undefined) {
|
|
293
|
-
relations.push([
|
|
305
|
+
relations.push([relDetailed.targetId, dataArray[index]]);
|
|
294
306
|
}
|
|
295
307
|
}
|
|
296
308
|
}
|
|
@@ -327,15 +339,14 @@ class Archetype {
|
|
|
327
339
|
const cacheKey = componentTypes.map((id) => id.toString()).join(",");
|
|
328
340
|
const componentDataSources = getOrComputeCache(this.componentDataSourcesCache, cacheKey, () => {
|
|
329
341
|
return componentTypes.map((compType) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const componentId =
|
|
342
|
+
const detailedType = getDetailedIdType(compType);
|
|
343
|
+
if (detailedType.type === "wildcard-relation") {
|
|
344
|
+
const componentId = detailedType.componentId;
|
|
333
345
|
const matchingRelations = this.componentTypes.filter((ct) => {
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
346
|
+
const detailedCt = getDetailedIdType(ct);
|
|
347
|
+
if (detailedCt.type !== "entity-relation" && detailedCt.type !== "component-relation")
|
|
336
348
|
return false;
|
|
337
|
-
|
|
338
|
-
return decodedCt.componentId === componentId;
|
|
349
|
+
return detailedCt.componentId === componentId;
|
|
339
350
|
});
|
|
340
351
|
return matchingRelations;
|
|
341
352
|
} else {
|
|
@@ -384,8 +395,8 @@ class CommandBuffer {
|
|
|
384
395
|
constructor(executeEntityCommands) {
|
|
385
396
|
this.executeEntityCommands = executeEntityCommands;
|
|
386
397
|
}
|
|
387
|
-
addComponent(entityId, componentType,
|
|
388
|
-
this.commands.push({ type: "addComponent", entityId, componentType, component });
|
|
398
|
+
addComponent(entityId, componentType, component2) {
|
|
399
|
+
this.commands.push({ type: "addComponent", entityId, componentType, component: component2 });
|
|
389
400
|
}
|
|
390
401
|
removeComponent(entityId, componentType) {
|
|
391
402
|
this.commands.push({ type: "removeComponent", entityId, componentType });
|
|
@@ -541,8 +552,7 @@ class World {
|
|
|
541
552
|
queries = [];
|
|
542
553
|
commandBuffer;
|
|
543
554
|
componentToArchetypes = new Map;
|
|
544
|
-
|
|
545
|
-
wildcardRelationLifecycleHooks = new Map;
|
|
555
|
+
lifecycleHooks = new Map;
|
|
546
556
|
entityReverseIndex = new Map;
|
|
547
557
|
constructor() {
|
|
548
558
|
this.commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
@@ -598,7 +608,7 @@ class World {
|
|
|
598
608
|
hasEntity(entityId) {
|
|
599
609
|
return this.entityToArchetype.has(entityId);
|
|
600
610
|
}
|
|
601
|
-
addComponent(entityId, componentType,
|
|
611
|
+
addComponent(entityId, componentType, component2) {
|
|
602
612
|
if (!this.hasEntity(entityId)) {
|
|
603
613
|
throw new Error(`Entity ${entityId} does not exist`);
|
|
604
614
|
}
|
|
@@ -609,7 +619,7 @@ class World {
|
|
|
609
619
|
if (detailedType.type === "wildcard-relation") {
|
|
610
620
|
throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
|
|
611
621
|
}
|
|
612
|
-
this.commandBuffer.addComponent(entityId, componentType,
|
|
622
|
+
this.commandBuffer.addComponent(entityId, componentType, component2);
|
|
613
623
|
}
|
|
614
624
|
removeComponent(entityId, componentType) {
|
|
615
625
|
if (!this.hasEntity(entityId)) {
|
|
@@ -648,33 +658,18 @@ class World {
|
|
|
648
658
|
this.systems.splice(index, 1);
|
|
649
659
|
}
|
|
650
660
|
}
|
|
651
|
-
|
|
652
|
-
if (!this.
|
|
653
|
-
this.
|
|
654
|
-
}
|
|
655
|
-
this.componentLifecycleHooks.get(componentType).add(hook);
|
|
656
|
-
}
|
|
657
|
-
unregisterComponentLifecycleHook(componentType, hook) {
|
|
658
|
-
const hooks = this.componentLifecycleHooks.get(componentType);
|
|
659
|
-
if (hooks) {
|
|
660
|
-
hooks.delete(hook);
|
|
661
|
-
if (hooks.size === 0) {
|
|
662
|
-
this.componentLifecycleHooks.delete(componentType);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
registerWildcardRelationLifecycleHook(baseComponentType, hook) {
|
|
667
|
-
if (!this.wildcardRelationLifecycleHooks.has(baseComponentType)) {
|
|
668
|
-
this.wildcardRelationLifecycleHooks.set(baseComponentType, new Set);
|
|
661
|
+
registerLifecycleHook(componentType, hook) {
|
|
662
|
+
if (!this.lifecycleHooks.has(componentType)) {
|
|
663
|
+
this.lifecycleHooks.set(componentType, new Set);
|
|
669
664
|
}
|
|
670
|
-
this.
|
|
665
|
+
this.lifecycleHooks.get(componentType).add(hook);
|
|
671
666
|
}
|
|
672
|
-
|
|
673
|
-
const hooks = this.
|
|
667
|
+
unregisterLifecycleHook(componentType, hook) {
|
|
668
|
+
const hooks = this.lifecycleHooks.get(componentType);
|
|
674
669
|
if (hooks) {
|
|
675
670
|
hooks.delete(hook);
|
|
676
671
|
if (hooks.size === 0) {
|
|
677
|
-
this.
|
|
672
|
+
this.lifecycleHooks.delete(componentType);
|
|
678
673
|
}
|
|
679
674
|
}
|
|
680
675
|
}
|
|
@@ -820,8 +815,8 @@ class World {
|
|
|
820
815
|
for (const componentType of removes) {
|
|
821
816
|
finalComponents.delete(componentType);
|
|
822
817
|
}
|
|
823
|
-
for (const [componentType,
|
|
824
|
-
finalComponents.set(componentType,
|
|
818
|
+
for (const [componentType, component2] of adds) {
|
|
819
|
+
finalComponents.set(componentType, component2);
|
|
825
820
|
}
|
|
826
821
|
const finalComponentTypes = Array.from(finalComponents.keys()).sort((a, b) => a - b);
|
|
827
822
|
const currentComponentTypes = currentArchetype.componentTypes.sort((a, b) => a - b);
|
|
@@ -832,8 +827,8 @@ class World {
|
|
|
832
827
|
newArchetype.addEntity(entityId, finalComponents);
|
|
833
828
|
this.entityToArchetype.set(entityId, newArchetype);
|
|
834
829
|
} else {
|
|
835
|
-
for (const [componentType,
|
|
836
|
-
currentArchetype.setComponent(entityId, componentType,
|
|
830
|
+
for (const [componentType, component2] of adds) {
|
|
831
|
+
currentArchetype.setComponent(entityId, componentType, component2);
|
|
837
832
|
}
|
|
838
833
|
}
|
|
839
834
|
for (const componentType of removes) {
|
|
@@ -845,7 +840,7 @@ class World {
|
|
|
845
840
|
this.removeComponentReference(entityId, componentType, componentType);
|
|
846
841
|
}
|
|
847
842
|
}
|
|
848
|
-
for (const [componentType,
|
|
843
|
+
for (const [componentType, component2] of adds) {
|
|
849
844
|
const detailedType = getDetailedIdType(componentType);
|
|
850
845
|
if (detailedType.type === "entity-relation") {
|
|
851
846
|
const targetEntityId = detailedType.targetId;
|
|
@@ -920,31 +915,32 @@ class World {
|
|
|
920
915
|
}
|
|
921
916
|
}
|
|
922
917
|
executeComponentLifecycleHooks(entityId, addedComponents, removedComponents) {
|
|
923
|
-
for (const [componentType,
|
|
924
|
-
const
|
|
925
|
-
if (
|
|
926
|
-
for (const hook of
|
|
918
|
+
for (const [componentType, component2] of addedComponents) {
|
|
919
|
+
const directHooks = this.lifecycleHooks.get(componentType);
|
|
920
|
+
if (directHooks) {
|
|
921
|
+
for (const hook of directHooks) {
|
|
927
922
|
if (hook.onAdded) {
|
|
928
|
-
hook.onAdded(entityId, componentType,
|
|
923
|
+
hook.onAdded(entityId, componentType, component2);
|
|
929
924
|
}
|
|
930
925
|
}
|
|
931
926
|
}
|
|
932
927
|
const detailedType = getDetailedIdType(componentType);
|
|
933
928
|
if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
|
|
934
|
-
const
|
|
929
|
+
const wildcardRelationId = relation(detailedType.componentId, "*");
|
|
930
|
+
const wildcardHooks = this.lifecycleHooks.get(wildcardRelationId);
|
|
935
931
|
if (wildcardHooks) {
|
|
936
932
|
for (const hook of wildcardHooks) {
|
|
937
933
|
if (hook.onAdded) {
|
|
938
|
-
hook.onAdded(entityId, componentType,
|
|
934
|
+
hook.onAdded(entityId, componentType, component2);
|
|
939
935
|
}
|
|
940
936
|
}
|
|
941
937
|
}
|
|
942
938
|
}
|
|
943
939
|
}
|
|
944
940
|
for (const componentType of removedComponents) {
|
|
945
|
-
const
|
|
946
|
-
if (
|
|
947
|
-
for (const hook of
|
|
941
|
+
const directHooks = this.lifecycleHooks.get(componentType);
|
|
942
|
+
if (directHooks) {
|
|
943
|
+
for (const hook of directHooks) {
|
|
948
944
|
if (hook.onRemoved) {
|
|
949
945
|
hook.onRemoved(entityId, componentType);
|
|
950
946
|
}
|
|
@@ -952,7 +948,8 @@ class World {
|
|
|
952
948
|
}
|
|
953
949
|
const detailedType = getDetailedIdType(componentType);
|
|
954
950
|
if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
|
|
955
|
-
const
|
|
951
|
+
const wildcardRelationId = relation(detailedType.componentId, "*");
|
|
952
|
+
const wildcardHooks = this.lifecycleHooks.get(wildcardRelationId);
|
|
956
953
|
if (wildcardHooks) {
|
|
957
954
|
for (const hook of wildcardHooks) {
|
|
958
955
|
if (hook.onRemoved) {
|
|
@@ -965,6 +962,8 @@ class World {
|
|
|
965
962
|
}
|
|
966
963
|
}
|
|
967
964
|
export {
|
|
965
|
+
relation,
|
|
966
|
+
isWildcardRelationId,
|
|
968
967
|
isRelationId,
|
|
969
968
|
isEntityId,
|
|
970
969
|
isComponentId,
|
|
@@ -972,9 +971,9 @@ export {
|
|
|
972
971
|
getIdType,
|
|
973
972
|
getDetailedIdType,
|
|
974
973
|
decodeRelationId,
|
|
975
|
-
createRelationId,
|
|
976
974
|
createEntityId,
|
|
977
975
|
createComponentId,
|
|
976
|
+
component,
|
|
978
977
|
World,
|
|
979
978
|
WILDCARD_TARGET_ID,
|
|
980
979
|
RELATION_SHIFT,
|
|
@@ -982,7 +981,7 @@ export {
|
|
|
982
981
|
INVALID_COMPONENT_ID,
|
|
983
982
|
EntityIdManager,
|
|
984
983
|
ENTITY_ID_START,
|
|
985
|
-
|
|
984
|
+
ComponentIdAllocator,
|
|
986
985
|
COMPONENT_ID_MAX,
|
|
987
986
|
Archetype
|
|
988
987
|
};
|
package/package.json
CHANGED
package/world.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { type Command } from "./command-buffer";
|
|
|
3
3
|
import type { EntityId, WildcardRelationId } from "./entity";
|
|
4
4
|
import { Query } from "./query";
|
|
5
5
|
import type { QueryFilter } from "./query-filter";
|
|
6
|
-
import type { ComponentTuple, LifecycleHook } from "./types";
|
|
7
6
|
import type { System } from "./system";
|
|
7
|
+
import type { ComponentTuple, LifecycleHook } from "./types";
|
|
8
8
|
/**
|
|
9
9
|
* World class for ECS architecture
|
|
10
10
|
* Manages entities, components, and systems
|
|
@@ -19,14 +19,9 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
|
|
|
19
19
|
private commandBuffer;
|
|
20
20
|
private componentToArchetypes;
|
|
21
21
|
/**
|
|
22
|
-
* Hook storage for component lifecycle events
|
|
23
|
-
*/
|
|
24
|
-
private componentLifecycleHooks;
|
|
25
|
-
/**
|
|
26
|
-
* Hook storage for wildcard relation lifecycle events
|
|
27
|
-
* Maps base component type to set of wildcard relation hooks
|
|
22
|
+
* Hook storage for component and wildcard relation lifecycle events
|
|
28
23
|
*/
|
|
29
|
-
private
|
|
24
|
+
private lifecycleHooks;
|
|
30
25
|
/**
|
|
31
26
|
* Reverse index tracking which entities use each entity as a component type
|
|
32
27
|
* Maps entity ID to set of {sourceEntityId, componentType} pairs where componentType uses this entity
|
|
@@ -83,22 +78,13 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
|
|
|
83
78
|
*/
|
|
84
79
|
unregisterSystem(system: System<ExtraParams>): void;
|
|
85
80
|
/**
|
|
86
|
-
* Register a lifecycle hook for component events
|
|
87
|
-
*/
|
|
88
|
-
registerComponentLifecycleHook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
89
|
-
/**
|
|
90
|
-
* Unregister a lifecycle hook for component events
|
|
91
|
-
*/
|
|
92
|
-
unregisterComponentLifecycleHook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
93
|
-
/**
|
|
94
|
-
* Register a lifecycle hook for wildcard relation events
|
|
95
|
-
* The hook will be triggered for any component that matches the wildcard relation pattern
|
|
81
|
+
* Register a lifecycle hook for component or wildcard relation events
|
|
96
82
|
*/
|
|
97
|
-
|
|
83
|
+
registerLifecycleHook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
98
84
|
/**
|
|
99
|
-
* Unregister a lifecycle hook for wildcard relation events
|
|
85
|
+
* Unregister a lifecycle hook for component or wildcard relation events
|
|
100
86
|
*/
|
|
101
|
-
|
|
87
|
+
unregisterLifecycleHook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
102
88
|
/**
|
|
103
89
|
* Update the world (run all systems)
|
|
104
90
|
*/
|