@codehz/ecs 0.0.1 → 0.0.2
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 +37 -12
- package/archetype.d.ts +8 -1
- package/index.d.ts +1 -0
- package/index.js +57 -5
- package/package.json +1 -1
- package/types.d.ts +13 -0
- package/world.d.ts +10 -33
package/README.md
CHANGED
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
|
-
- 🚀
|
|
7
|
+
- 🚀 高性能:基于 Archetype 的组件存储和高效的查询系统
|
|
8
8
|
- 🔧 类型安全:完整的 TypeScript 支持
|
|
9
9
|
- 🏗️ 模块化:清晰的架构,支持自定义系统和组件
|
|
10
10
|
- 📦 轻量级:零依赖,易于集成
|
|
11
|
+
- ⚡ 内存高效:连续内存布局,优化的迭代性能
|
|
11
12
|
|
|
12
13
|
## 安装
|
|
13
14
|
|
|
@@ -44,6 +45,7 @@ world.flushCommands();
|
|
|
44
45
|
|
|
45
46
|
// 创建查询并更新
|
|
46
47
|
const query = world.createQuery([PositionId, VelocityId]);
|
|
48
|
+
const deltaTime = 1.0 / 60.0; // 假设60FPS
|
|
47
49
|
query.forEach([PositionId, VelocityId], (entity, position, velocity) => {
|
|
48
50
|
position.x += velocity.x * deltaTime;
|
|
49
51
|
position.y += velocity.y * deltaTime;
|
|
@@ -79,6 +81,12 @@ world.flushCommands(); // 钩子在这里被调用
|
|
|
79
81
|
|
|
80
82
|
### 运行示例
|
|
81
83
|
|
|
84
|
+
```bash
|
|
85
|
+
bun run demo
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
或者直接运行:
|
|
89
|
+
|
|
82
90
|
```bash
|
|
83
91
|
bun run examples/simple/demo.ts
|
|
84
92
|
```
|
|
@@ -104,6 +112,8 @@ bun run examples/simple/demo.ts
|
|
|
104
112
|
### Query
|
|
105
113
|
|
|
106
114
|
- `forEach(componentIds, callback)`: 遍历匹配的实体
|
|
115
|
+
- `getEntities()`: 获取所有匹配实体的ID列表
|
|
116
|
+
- `getEntitiesWithComponents(componentIds)`: 获取实体及其组件数据
|
|
107
117
|
|
|
108
118
|
### System
|
|
109
119
|
|
|
@@ -117,6 +127,13 @@ class MySystem implements System {
|
|
|
117
127
|
}
|
|
118
128
|
```
|
|
119
129
|
|
|
130
|
+
## 性能特点
|
|
131
|
+
|
|
132
|
+
- **Archetype 系统**:实体按组件组合分组,实现连续内存访问
|
|
133
|
+
- **缓存查询**:查询结果自动缓存,减少重复计算
|
|
134
|
+
- **命令缓冲区**:延迟执行组件添加/移除,提高批处理效率
|
|
135
|
+
- **类型安全**:编译时类型检查,无运行时开销
|
|
136
|
+
|
|
120
137
|
## 开发
|
|
121
138
|
|
|
122
139
|
### 运行测试
|
|
@@ -135,20 +152,28 @@ bunx tsc --noEmit
|
|
|
135
152
|
|
|
136
153
|
```
|
|
137
154
|
src/
|
|
138
|
-
├── index.ts
|
|
139
|
-
├── entity.ts
|
|
140
|
-
├── world.ts
|
|
141
|
-
├── archetype.ts
|
|
142
|
-
├── query.ts
|
|
143
|
-
├──
|
|
144
|
-
├──
|
|
145
|
-
├──
|
|
146
|
-
|
|
155
|
+
├── index.ts # 入口文件
|
|
156
|
+
├── entity.ts # 实体和组件管理
|
|
157
|
+
├── world.ts # 世界管理
|
|
158
|
+
├── archetype.ts # Archetype 系统(高效组件存储)
|
|
159
|
+
├── query.ts # 查询系统
|
|
160
|
+
├── query-filter.ts # 查询过滤器
|
|
161
|
+
├── system.ts # 系统接口
|
|
162
|
+
├── command-buffer.ts # 命令缓冲区
|
|
163
|
+
├── types.ts # 类型定义
|
|
164
|
+
├── utils.ts # 工具函数
|
|
165
|
+
├── *.test.ts # 单元测试
|
|
166
|
+
├── query.example.ts # 查询示例
|
|
167
|
+
└── *.perf.test.ts # 性能测试
|
|
147
168
|
|
|
148
169
|
examples/
|
|
149
170
|
└── simple/
|
|
150
|
-
├── demo.ts
|
|
151
|
-
└── README.md
|
|
171
|
+
├── demo.ts # 基本示例
|
|
172
|
+
└── README.md # 示例说明
|
|
173
|
+
|
|
174
|
+
scripts/
|
|
175
|
+
├── build.ts # 构建脚本
|
|
176
|
+
└── release.ts # 发布脚本
|
|
152
177
|
```
|
|
153
178
|
|
|
154
179
|
## 许可证
|
package/archetype.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EntityId } from "./entity";
|
|
1
|
+
import type { EntityId, WildcardRelationId } from "./entity";
|
|
2
2
|
import type { ComponentTuple } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Archetype class for ECS architecture
|
|
@@ -60,6 +60,13 @@ export declare class Archetype {
|
|
|
60
60
|
* @param entityId The entity to check
|
|
61
61
|
*/
|
|
62
62
|
hasEntity(entityId: EntityId): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Get component data for a specific entity and wildcard relation type
|
|
65
|
+
* Returns an array of all matching relation instances
|
|
66
|
+
* @param entityId The entity
|
|
67
|
+
* @param componentType The wildcard relation type
|
|
68
|
+
*/
|
|
69
|
+
getComponent<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, any][] | undefined;
|
|
63
70
|
/**
|
|
64
71
|
* Get component data for a specific entity and component type
|
|
65
72
|
* @param entityId The entity
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -275,9 +275,29 @@ class Archetype {
|
|
|
275
275
|
getComponent(entityId, componentType) {
|
|
276
276
|
const index = this.entityToIndex.get(entityId);
|
|
277
277
|
if (index === undefined) {
|
|
278
|
-
|
|
278
|
+
if (getIdType(componentType) === "wildcard-relation") {
|
|
279
|
+
return [];
|
|
280
|
+
} else {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (getIdType(componentType) === "wildcard-relation") {
|
|
285
|
+
const decoded = decodeRelationId(componentType);
|
|
286
|
+
const componentId = decoded.componentId;
|
|
287
|
+
const relations = [];
|
|
288
|
+
for (const relType of this.componentTypes) {
|
|
289
|
+
const relDecoded = decodeRelationId(relType);
|
|
290
|
+
if (relDecoded.componentId === componentId && (getIdType(relType) === "entity-relation" || getIdType(relType) === "component-relation")) {
|
|
291
|
+
const dataArray = this.componentData.get(relType);
|
|
292
|
+
if (dataArray && dataArray[index] !== undefined) {
|
|
293
|
+
relations.push([relDecoded.targetId, dataArray[index]]);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return relations;
|
|
298
|
+
} else {
|
|
299
|
+
return this.componentData.get(componentType)?.[index];
|
|
279
300
|
}
|
|
280
|
-
return this.componentData.get(componentType)?.[index];
|
|
281
301
|
}
|
|
282
302
|
setComponent(entityId, componentType, data) {
|
|
283
303
|
const index = this.entityToIndex.get(entityId);
|
|
@@ -582,12 +602,23 @@ class World {
|
|
|
582
602
|
if (!this.hasEntity(entityId)) {
|
|
583
603
|
throw new Error(`Entity ${entityId} does not exist`);
|
|
584
604
|
}
|
|
605
|
+
const detailedType = getDetailedIdType(componentType);
|
|
606
|
+
if (detailedType.type === "invalid") {
|
|
607
|
+
throw new Error(`Invalid component type: ${componentType}`);
|
|
608
|
+
}
|
|
609
|
+
if (detailedType.type === "wildcard-relation") {
|
|
610
|
+
throw new Error(`Cannot directly add wildcard relation components: ${componentType}`);
|
|
611
|
+
}
|
|
585
612
|
this.commandBuffer.addComponent(entityId, componentType, component);
|
|
586
613
|
}
|
|
587
614
|
removeComponent(entityId, componentType) {
|
|
588
615
|
if (!this.hasEntity(entityId)) {
|
|
589
616
|
throw new Error(`Entity ${entityId} does not exist`);
|
|
590
617
|
}
|
|
618
|
+
const detailedType = getDetailedIdType(componentType);
|
|
619
|
+
if (detailedType.type === "invalid") {
|
|
620
|
+
throw new Error(`Invalid component type: ${componentType}`);
|
|
621
|
+
}
|
|
591
622
|
this.commandBuffer.removeComponent(entityId, componentType);
|
|
592
623
|
}
|
|
593
624
|
destroyEntity(entityId) {
|
|
@@ -599,7 +630,14 @@ class World {
|
|
|
599
630
|
}
|
|
600
631
|
getComponent(entityId, componentType) {
|
|
601
632
|
const archetype = this.entityToArchetype.get(entityId);
|
|
602
|
-
|
|
633
|
+
if (!archetype) {
|
|
634
|
+
if (getIdType(componentType) === "wildcard-relation") {
|
|
635
|
+
return [];
|
|
636
|
+
} else {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return archetype.getComponent(entityId, componentType);
|
|
603
641
|
}
|
|
604
642
|
registerSystem(system) {
|
|
605
643
|
this.systems.push(system);
|
|
@@ -758,8 +796,22 @@ class World {
|
|
|
758
796
|
break;
|
|
759
797
|
case "removeComponent":
|
|
760
798
|
if (cmd.componentType) {
|
|
761
|
-
|
|
762
|
-
|
|
799
|
+
const detailedType = getDetailedIdType(cmd.componentType);
|
|
800
|
+
if (detailedType.type === "wildcard-relation") {
|
|
801
|
+
const baseComponentId = detailedType.componentId;
|
|
802
|
+
for (const componentType of currentArchetype.componentTypes) {
|
|
803
|
+
const componentDetailedType = getDetailedIdType(componentType);
|
|
804
|
+
if (componentDetailedType.type === "entity-relation" || componentDetailedType.type === "component-relation") {
|
|
805
|
+
if (componentDetailedType.componentId === baseComponentId) {
|
|
806
|
+
removes.add(componentType);
|
|
807
|
+
adds.delete(componentType);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
} else {
|
|
812
|
+
removes.add(cmd.componentType);
|
|
813
|
+
adds.delete(cmd.componentType);
|
|
814
|
+
}
|
|
763
815
|
}
|
|
764
816
|
break;
|
|
765
817
|
}
|
package/package.json
CHANGED
package/types.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { EntityId, WildcardRelationId } from "./entity";
|
|
2
|
+
/**
|
|
3
|
+
* Hook types for component lifecycle events
|
|
4
|
+
*/
|
|
5
|
+
export interface LifecycleHook<T = unknown> {
|
|
6
|
+
/**
|
|
7
|
+
* Called when a component is added to an entity
|
|
8
|
+
*/
|
|
9
|
+
onAdded?: (entityId: EntityId, componentType: EntityId<T>, component: T) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Called when a component is removed from an entity
|
|
12
|
+
*/
|
|
13
|
+
onRemoved?: (entityId: EntityId, componentType: EntityId<T>) => void;
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
16
|
* Type helper for component tuples extracted from EntityId array
|
|
4
17
|
*/
|
package/world.d.ts
CHANGED
|
@@ -1,37 +1,10 @@
|
|
|
1
1
|
import { Archetype } from "./archetype";
|
|
2
2
|
import { type Command } from "./command-buffer";
|
|
3
|
-
import type { EntityId } from "./entity";
|
|
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 } from "./types";
|
|
6
|
+
import type { ComponentTuple, LifecycleHook } from "./types";
|
|
7
7
|
import type { System } from "./system";
|
|
8
|
-
/**
|
|
9
|
-
* Hook types for component lifecycle events
|
|
10
|
-
*/
|
|
11
|
-
export interface ComponentLifecycleHook<T> {
|
|
12
|
-
/**
|
|
13
|
-
* Called when a component is added to an entity
|
|
14
|
-
*/
|
|
15
|
-
onAdded?: (entityId: EntityId, componentType: EntityId<T>, component: T) => void;
|
|
16
|
-
/**
|
|
17
|
-
* Called when a component is removed from an entity
|
|
18
|
-
*/
|
|
19
|
-
onRemoved?: (entityId: EntityId, componentType: EntityId<T>) => void;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Hook types for wildcard relation lifecycle events
|
|
23
|
-
* These hooks are triggered for any component that matches a wildcard relation pattern
|
|
24
|
-
*/
|
|
25
|
-
export interface WildcardRelationLifecycleHook<T = unknown> {
|
|
26
|
-
/**
|
|
27
|
-
* Called when any component matching the wildcard relation pattern is added to an entity
|
|
28
|
-
*/
|
|
29
|
-
onAdded?: (entityId: EntityId, componentType: EntityId<T>, component: T) => void;
|
|
30
|
-
/**
|
|
31
|
-
* Called when any component matching the wildcard relation pattern is removed from an entity
|
|
32
|
-
*/
|
|
33
|
-
onRemoved?: (entityId: EntityId, componentType: EntityId<T>) => void;
|
|
34
|
-
}
|
|
35
8
|
/**
|
|
36
9
|
* World class for ECS architecture
|
|
37
10
|
* Manages entities, components, and systems
|
|
@@ -93,6 +66,10 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
|
|
|
93
66
|
* Check if an entity has a specific component
|
|
94
67
|
*/
|
|
95
68
|
hasComponent<T>(entityId: EntityId, componentType: EntityId<T>): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Get wildcard relations from an entity
|
|
71
|
+
*/
|
|
72
|
+
getComponent<T>(entityId: EntityId, componentType: WildcardRelationId<T>): [EntityId<unknown>, any][] | undefined;
|
|
96
73
|
/**
|
|
97
74
|
* Get a component from an entity
|
|
98
75
|
*/
|
|
@@ -108,20 +85,20 @@ export declare class World<ExtraParams extends any[] = [deltaTime: number]> {
|
|
|
108
85
|
/**
|
|
109
86
|
* Register a lifecycle hook for component events
|
|
110
87
|
*/
|
|
111
|
-
registerComponentLifecycleHook<T>(componentType: EntityId<T>, hook:
|
|
88
|
+
registerComponentLifecycleHook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
112
89
|
/**
|
|
113
90
|
* Unregister a lifecycle hook for component events
|
|
114
91
|
*/
|
|
115
|
-
unregisterComponentLifecycleHook<T>(componentType: EntityId<T>, hook:
|
|
92
|
+
unregisterComponentLifecycleHook<T>(componentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
116
93
|
/**
|
|
117
94
|
* Register a lifecycle hook for wildcard relation events
|
|
118
95
|
* The hook will be triggered for any component that matches the wildcard relation pattern
|
|
119
96
|
*/
|
|
120
|
-
registerWildcardRelationLifecycleHook<T>(baseComponentType: EntityId<T>, hook:
|
|
97
|
+
registerWildcardRelationLifecycleHook<T>(baseComponentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
121
98
|
/**
|
|
122
99
|
* Unregister a lifecycle hook for wildcard relation events
|
|
123
100
|
*/
|
|
124
|
-
unregisterWildcardRelationLifecycleHook<T>(baseComponentType: EntityId<T>, hook:
|
|
101
|
+
unregisterWildcardRelationLifecycleHook<T>(baseComponentType: EntityId<T>, hook: LifecycleHook<T>): void;
|
|
125
102
|
/**
|
|
126
103
|
* Update the world (run all systems)
|
|
127
104
|
*/
|