@codehz/ecs 0.2.4 → 0.3.1
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 +14 -10
- package/archetype.d.ts +4 -0
- package/index.js +40 -29
- package/package.json +1 -1
- package/types.d.ts +7 -3
- package/world.d.ts +5 -5
package/README.md
CHANGED
|
@@ -60,18 +60,22 @@ ECS 支持在组件添加或移除时执行回调函数:
|
|
|
60
60
|
|
|
61
61
|
```typescript
|
|
62
62
|
// 注册组件生命周期钩子
|
|
63
|
-
world.
|
|
64
|
-
|
|
63
|
+
world.hook(PositionId, {
|
|
64
|
+
on_init: (entityId, componentType, component) => {
|
|
65
|
+
// 当钩子注册时,为现有实体上的组件调用
|
|
66
|
+
console.log(`现有组件 ${componentType} 在实体 ${entityId}`);
|
|
67
|
+
},
|
|
68
|
+
on_set: (entityId, componentType, component) => {
|
|
65
69
|
console.log(`组件 ${componentType} 被添加到实体 ${entityId}`);
|
|
66
70
|
},
|
|
67
|
-
|
|
71
|
+
on_remove: (entityId, componentType, component) => {
|
|
68
72
|
console.log(`组件 ${componentType} 被从实体 ${entityId} 移除`);
|
|
69
73
|
},
|
|
70
74
|
});
|
|
71
75
|
|
|
72
76
|
// 你也可以只注册其中一个钩子
|
|
73
|
-
world.
|
|
74
|
-
|
|
77
|
+
world.hook(VelocityId, {
|
|
78
|
+
on_remove: (entityId, componentType, component) => {
|
|
75
79
|
console.log(`组件 ${componentType} 被从实体 ${entityId} 移除`);
|
|
76
80
|
},
|
|
77
81
|
});
|
|
@@ -104,11 +108,11 @@ const entity = world.new();
|
|
|
104
108
|
const wildcardPositionRelation = relation(PositionId, "*");
|
|
105
109
|
|
|
106
110
|
// 注册通配符关系钩子
|
|
107
|
-
world.
|
|
108
|
-
|
|
111
|
+
world.hook(wildcardPositionRelation, {
|
|
112
|
+
on_set: (entityId, componentType, component) => {
|
|
109
113
|
console.log(`关系组件 ${componentType} 被添加到实体 ${entityId}`);
|
|
110
114
|
},
|
|
111
|
-
|
|
115
|
+
on_remove: (entityId, componentType, component) => {
|
|
112
116
|
console.log(`关系组件 ${componentType} 被从实体 ${entityId} 移除`);
|
|
113
117
|
},
|
|
114
118
|
});
|
|
@@ -177,8 +181,8 @@ bun run examples/simple/demo.ts
|
|
|
177
181
|
- `setExclusive(componentId)`: 将组件标记为独占关系
|
|
178
182
|
- `createQuery(componentIds)`: 创建查询
|
|
179
183
|
- `registerSystem(system, dependencies?)`: 注册系统
|
|
180
|
-
- `
|
|
181
|
-
- `
|
|
184
|
+
- `hook(componentId, hook)`: 注册组件或通配符关系生命周期钩子
|
|
185
|
+
- `unhook(componentId, hook)`: 注销组件或通配符关系生命周期钩子
|
|
182
186
|
- `update(...params)`: 更新世界(参数取决于泛型配置)
|
|
183
187
|
- `sync()`: 应用命令缓冲区
|
|
184
188
|
|
package/archetype.d.ts
CHANGED
|
@@ -102,6 +102,10 @@ export declare class Archetype {
|
|
|
102
102
|
* Get all entities in this archetype
|
|
103
103
|
*/
|
|
104
104
|
getEntities(): EntityId[];
|
|
105
|
+
/**
|
|
106
|
+
* Get the mapping of entities to their indices in this archetype
|
|
107
|
+
*/
|
|
108
|
+
getEntityToIndexMap(): Map<EntityId, number>;
|
|
105
109
|
/**
|
|
106
110
|
* Get component data for all entities of a specific component type
|
|
107
111
|
* @param componentType The component type
|
package/index.js
CHANGED
|
@@ -389,6 +389,9 @@ class Archetype {
|
|
|
389
389
|
getEntities() {
|
|
390
390
|
return this.entities;
|
|
391
391
|
}
|
|
392
|
+
getEntityToIndexMap() {
|
|
393
|
+
return this.entityToIndex;
|
|
394
|
+
}
|
|
392
395
|
getComponentData(componentType) {
|
|
393
396
|
const data = this.componentData.get(componentType);
|
|
394
397
|
if (!data) {
|
|
@@ -791,7 +794,7 @@ class World {
|
|
|
791
794
|
queryCache = new Map;
|
|
792
795
|
systemScheduler = new SystemScheduler;
|
|
793
796
|
commandBuffer = new CommandBuffer((entityId, commands) => this.executeEntityCommands(entityId, commands));
|
|
794
|
-
|
|
797
|
+
hooks = new Map;
|
|
795
798
|
exclusiveComponents = new Set;
|
|
796
799
|
constructor(snapshot) {
|
|
797
800
|
if (snapshot && typeof snapshot === "object") {
|
|
@@ -876,6 +879,7 @@ class World {
|
|
|
876
879
|
const sourceArchetype = this.entityToArchetype.get(sourceEntityId);
|
|
877
880
|
if (sourceArchetype) {
|
|
878
881
|
const currentComponents = new Map;
|
|
882
|
+
let removedComponent = sourceArchetype.get(sourceEntityId, componentType);
|
|
879
883
|
for (const archetypeComponentType of sourceArchetype.componentTypes) {
|
|
880
884
|
if (archetypeComponentType !== componentType) {
|
|
881
885
|
const componentData = sourceArchetype.get(sourceEntityId, archetypeComponentType);
|
|
@@ -890,7 +894,7 @@ class World {
|
|
|
890
894
|
newArchetype.addEntity(sourceEntityId, currentComponents);
|
|
891
895
|
this.entityToArchetype.set(sourceEntityId, newArchetype);
|
|
892
896
|
this.untrackEntityReference(sourceEntityId, componentType, entityId);
|
|
893
|
-
this.triggerLifecycleHooks(sourceEntityId, new Map, new
|
|
897
|
+
this.triggerLifecycleHooks(sourceEntityId, new Map, new Map([[componentType, removedComponent]]));
|
|
894
898
|
}
|
|
895
899
|
}
|
|
896
900
|
this.entityReferences.delete(entityId);
|
|
@@ -917,7 +921,7 @@ class World {
|
|
|
917
921
|
}
|
|
918
922
|
this.commandBuffer.set(entityId, componentType, component2);
|
|
919
923
|
}
|
|
920
|
-
|
|
924
|
+
remove(entityId, componentType) {
|
|
921
925
|
if (!this.exists(entityId)) {
|
|
922
926
|
throw new Error(`Entity ${entityId} does not exist`);
|
|
923
927
|
}
|
|
@@ -927,7 +931,7 @@ class World {
|
|
|
927
931
|
}
|
|
928
932
|
this.commandBuffer.delete(entityId, componentType);
|
|
929
933
|
}
|
|
930
|
-
|
|
934
|
+
delete(entityId) {
|
|
931
935
|
this.commandBuffer.destroy(entityId);
|
|
932
936
|
}
|
|
933
937
|
has(entityId, componentType) {
|
|
@@ -950,18 +954,29 @@ class World {
|
|
|
950
954
|
registerSystem(system, additionalDeps = []) {
|
|
951
955
|
this.systemScheduler.addSystem(system, additionalDeps);
|
|
952
956
|
}
|
|
953
|
-
|
|
954
|
-
if (!this.
|
|
955
|
-
this.
|
|
957
|
+
hook(componentType, hook) {
|
|
958
|
+
if (!this.hooks.has(componentType)) {
|
|
959
|
+
this.hooks.set(componentType, new Set);
|
|
960
|
+
}
|
|
961
|
+
this.hooks.get(componentType).add(hook);
|
|
962
|
+
if (hook.on_init !== undefined) {
|
|
963
|
+
this.archetypesByComponent.get(componentType)?.forEach((archetype) => {
|
|
964
|
+
const entities = archetype.getEntityToIndexMap();
|
|
965
|
+
const componentData = archetype.getComponentData(componentType);
|
|
966
|
+
for (const [entity, index] of entities) {
|
|
967
|
+
const data = componentData[index];
|
|
968
|
+
const value = data === MISSING_COMPONENT ? undefined : data;
|
|
969
|
+
hook.on_init?.(entity, componentType, value);
|
|
970
|
+
}
|
|
971
|
+
});
|
|
956
972
|
}
|
|
957
|
-
this.lifecycleHooks.get(componentType).add(hook);
|
|
958
973
|
}
|
|
959
|
-
|
|
960
|
-
const hooks = this.
|
|
974
|
+
unhook(componentType, hook) {
|
|
975
|
+
const hooks = this.hooks.get(componentType);
|
|
961
976
|
if (hooks) {
|
|
962
977
|
hooks.delete(hook);
|
|
963
978
|
if (hooks.size === 0) {
|
|
964
|
-
this.
|
|
979
|
+
this.hooks.delete(componentType);
|
|
965
980
|
}
|
|
966
981
|
}
|
|
967
982
|
}
|
|
@@ -1130,9 +1145,13 @@ class World {
|
|
|
1130
1145
|
}
|
|
1131
1146
|
}
|
|
1132
1147
|
const finalComponentTypes = changeset.getFinalComponentTypes(currentArchetype.componentTypes);
|
|
1148
|
+
const removedCompoents = new Map;
|
|
1133
1149
|
if (finalComponentTypes) {
|
|
1134
1150
|
const newArchetype = this.ensureArchetype(finalComponentTypes);
|
|
1135
1151
|
const currentComponents = currentArchetype.removeEntity(entityId);
|
|
1152
|
+
for (const componentType of changeset.removes) {
|
|
1153
|
+
removedCompoents.set(componentType, currentComponents.get(componentType));
|
|
1154
|
+
}
|
|
1136
1155
|
newArchetype.addEntity(entityId, changeset.applyTo(currentComponents));
|
|
1137
1156
|
this.entityToArchetype.set(entityId, newArchetype);
|
|
1138
1157
|
} else {
|
|
@@ -1158,7 +1177,7 @@ class World {
|
|
|
1158
1177
|
this.trackEntityReference(entityId, componentType, componentType);
|
|
1159
1178
|
}
|
|
1160
1179
|
}
|
|
1161
|
-
this.triggerLifecycleHooks(entityId, changeset.adds,
|
|
1180
|
+
this.triggerLifecycleHooks(entityId, changeset.adds, removedCompoents);
|
|
1162
1181
|
return changeset;
|
|
1163
1182
|
}
|
|
1164
1183
|
ensureArchetype(componentTypes) {
|
|
@@ -1226,45 +1245,37 @@ class World {
|
|
|
1226
1245
|
}
|
|
1227
1246
|
triggerLifecycleHooks(entityId, addedComponents, removedComponents) {
|
|
1228
1247
|
for (const [componentType, component2] of addedComponents) {
|
|
1229
|
-
const directHooks = this.
|
|
1248
|
+
const directHooks = this.hooks.get(componentType);
|
|
1230
1249
|
if (directHooks) {
|
|
1231
1250
|
for (const lifecycleHook of directHooks) {
|
|
1232
|
-
|
|
1233
|
-
lifecycleHook.onAdded(entityId, componentType, component2);
|
|
1234
|
-
}
|
|
1251
|
+
lifecycleHook.on_set?.(entityId, componentType, component2);
|
|
1235
1252
|
}
|
|
1236
1253
|
}
|
|
1237
1254
|
const detailedType = getDetailedIdType(componentType);
|
|
1238
1255
|
if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
|
|
1239
1256
|
const wildcardRelationId = relation(detailedType.componentId, "*");
|
|
1240
|
-
const wildcardHooks = this.
|
|
1257
|
+
const wildcardHooks = this.hooks.get(wildcardRelationId);
|
|
1241
1258
|
if (wildcardHooks) {
|
|
1242
1259
|
for (const lifecycleHook of wildcardHooks) {
|
|
1243
|
-
|
|
1244
|
-
lifecycleHook.onAdded(entityId, componentType, component2);
|
|
1245
|
-
}
|
|
1260
|
+
lifecycleHook.on_set?.(entityId, componentType, component2);
|
|
1246
1261
|
}
|
|
1247
1262
|
}
|
|
1248
1263
|
}
|
|
1249
1264
|
}
|
|
1250
|
-
for (const componentType of removedComponents) {
|
|
1251
|
-
const directHooks = this.
|
|
1265
|
+
for (const [componentType, component2] of removedComponents) {
|
|
1266
|
+
const directHooks = this.hooks.get(componentType);
|
|
1252
1267
|
if (directHooks) {
|
|
1253
1268
|
for (const lifecycleHook of directHooks) {
|
|
1254
|
-
|
|
1255
|
-
lifecycleHook.onRemoved(entityId, componentType);
|
|
1256
|
-
}
|
|
1269
|
+
lifecycleHook.on_remove?.(entityId, componentType, component2);
|
|
1257
1270
|
}
|
|
1258
1271
|
}
|
|
1259
1272
|
const detailedType = getDetailedIdType(componentType);
|
|
1260
1273
|
if (detailedType.type === "entity-relation" || detailedType.type === "component-relation" || detailedType.type === "wildcard-relation") {
|
|
1261
1274
|
const wildcardRelationId = relation(detailedType.componentId, "*");
|
|
1262
|
-
const wildcardHooks = this.
|
|
1275
|
+
const wildcardHooks = this.hooks.get(wildcardRelationId);
|
|
1263
1276
|
if (wildcardHooks) {
|
|
1264
1277
|
for (const hook of wildcardHooks) {
|
|
1265
|
-
|
|
1266
|
-
hook.onRemoved(entityId, componentType);
|
|
1267
|
-
}
|
|
1278
|
+
hook.on_remove?.(entityId, componentType, component2);
|
|
1268
1279
|
}
|
|
1269
1280
|
}
|
|
1270
1281
|
}
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -6,11 +6,15 @@ export interface LifecycleHook<T = unknown> {
|
|
|
6
6
|
/**
|
|
7
7
|
* Called when a component is added to an entity
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
on_init?: (entityId: EntityId, componentType: EntityId<T>, component: T) => void;
|
|
10
10
|
/**
|
|
11
|
-
* Called when a component is
|
|
11
|
+
* Called when a component is added to an entity
|
|
12
|
+
*/
|
|
13
|
+
on_set?: (entityId: EntityId, componentType: EntityId<T>, component: T) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Called when a component is deleted from an entity
|
|
12
16
|
*/
|
|
13
|
-
|
|
17
|
+
on_remove?: (entityId: EntityId, componentType: EntityId<T>, component: T) => void;
|
|
14
18
|
}
|
|
15
19
|
export type ComponentType<T> = EntityId<T> | OptionalEntityId<T>;
|
|
16
20
|
export type OptionalEntityId<T> = {
|
package/world.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
32
32
|
/** Buffers structural changes for deferred execution */
|
|
33
33
|
private commandBuffer;
|
|
34
34
|
/** Stores lifecycle hooks for component and relation events */
|
|
35
|
-
private
|
|
35
|
+
private hooks;
|
|
36
36
|
/** Set of component IDs marked as exclusive relations */
|
|
37
37
|
private exclusiveComponents;
|
|
38
38
|
/**
|
|
@@ -67,11 +67,11 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
67
67
|
/**
|
|
68
68
|
* Remove a component from an entity (deferred)
|
|
69
69
|
*/
|
|
70
|
-
|
|
70
|
+
remove<T>(entityId: EntityId, componentType: EntityId<T>): void;
|
|
71
71
|
/**
|
|
72
72
|
* Destroy an entity and remove all its components (deferred)
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
delete(entityId: EntityId): void;
|
|
75
75
|
/**
|
|
76
76
|
* Check if an entity has a specific component
|
|
77
77
|
*/
|
|
@@ -98,11 +98,11 @@ export declare class World<UpdateParams extends any[] = []> {
|
|
|
98
98
|
/**
|
|
99
99
|
* Register a lifecycle hook for component or wildcard relation events
|
|
100
100
|
*/
|
|
101
|
-
|
|
101
|
+
hook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
102
102
|
/**
|
|
103
103
|
* Unregister a lifecycle hook for component or wildcard relation events
|
|
104
104
|
*/
|
|
105
|
-
|
|
105
|
+
unhook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
106
106
|
/**
|
|
107
107
|
* Mark a component as exclusive relation
|
|
108
108
|
* For exclusive relations, an entity can have at most one relation per base component
|