更新性能分析器及更改部分注释
This commit is contained in:
@@ -21,6 +21,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"__id__": 5
|
"__id__": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__id__": 7
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_active": true,
|
"_active": true,
|
||||||
@@ -55,7 +58,7 @@
|
|||||||
},
|
},
|
||||||
"autoReleaseAssets": false,
|
"autoReleaseAssets": false,
|
||||||
"_globals": {
|
"_globals": {
|
||||||
"__id__": 7
|
"__id__": 9
|
||||||
},
|
},
|
||||||
"_id": "fcbf2917-6d43-4528-8829-7ee089594879"
|
"_id": "fcbf2917-6d43-4528-8829-7ee089594879"
|
||||||
},
|
},
|
||||||
@@ -246,32 +249,90 @@
|
|||||||
"_trackingType": 0,
|
"_trackingType": 0,
|
||||||
"_id": "7dWQTpwS5LrIHnc1zAPUtf"
|
"_id": "7dWQTpwS5LrIHnc1zAPUtf"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"__type__": "cc.Node",
|
||||||
|
"_name": "ECSManager",
|
||||||
|
"_objFlags": 0,
|
||||||
|
"__editorExtras__": {},
|
||||||
|
"_parent": {
|
||||||
|
"__id__": 1
|
||||||
|
},
|
||||||
|
"_children": [],
|
||||||
|
"_active": true,
|
||||||
|
"_components": [
|
||||||
|
{
|
||||||
|
"__id__": 8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_prefab": null,
|
||||||
|
"_lpos": {
|
||||||
|
"__type__": "cc.Vec3",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"_lrot": {
|
||||||
|
"__type__": "cc.Quat",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"w": 1
|
||||||
|
},
|
||||||
|
"_lscale": {
|
||||||
|
"__type__": "cc.Vec3",
|
||||||
|
"x": 1,
|
||||||
|
"y": 1,
|
||||||
|
"z": 1
|
||||||
|
},
|
||||||
|
"_mobility": 0,
|
||||||
|
"_layer": 1073741824,
|
||||||
|
"_euler": {
|
||||||
|
"__type__": "cc.Vec3",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"_id": "3e7rcS/tZHIY0l6bef0fag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"__type__": "b8965bwYyBLbYHNRHv4ESMM",
|
||||||
|
"_name": "",
|
||||||
|
"_objFlags": 0,
|
||||||
|
"__editorExtras__": {},
|
||||||
|
"node": {
|
||||||
|
"__id__": 7
|
||||||
|
},
|
||||||
|
"_enabled": true,
|
||||||
|
"__prefab": null,
|
||||||
|
"debugMode": true,
|
||||||
|
"_id": "a5SvVlMsZCrIJB1jr083gX"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"__type__": "cc.SceneGlobals",
|
"__type__": "cc.SceneGlobals",
|
||||||
"ambient": {
|
"ambient": {
|
||||||
"__id__": 8
|
|
||||||
},
|
|
||||||
"shadows": {
|
|
||||||
"__id__": 9
|
|
||||||
},
|
|
||||||
"_skybox": {
|
|
||||||
"__id__": 10
|
"__id__": 10
|
||||||
},
|
},
|
||||||
"fog": {
|
"shadows": {
|
||||||
"__id__": 11
|
"__id__": 11
|
||||||
},
|
},
|
||||||
"octree": {
|
"_skybox": {
|
||||||
"__id__": 12
|
"__id__": 12
|
||||||
},
|
},
|
||||||
"skin": {
|
"fog": {
|
||||||
"__id__": 13
|
"__id__": 13
|
||||||
},
|
},
|
||||||
"lightProbeInfo": {
|
"octree": {
|
||||||
"__id__": 14
|
"__id__": 14
|
||||||
},
|
},
|
||||||
"postSettings": {
|
"skin": {
|
||||||
"__id__": 15
|
"__id__": 15
|
||||||
},
|
},
|
||||||
|
"lightProbeInfo": {
|
||||||
|
"__id__": 16
|
||||||
|
},
|
||||||
|
"postSettings": {
|
||||||
|
"__id__": 17
|
||||||
|
},
|
||||||
"bakedWithStationaryMainLight": false,
|
"bakedWithStationaryMainLight": false,
|
||||||
"bakedWithHighpLightmap": false
|
"bakedWithHighpLightmap": false
|
||||||
},
|
},
|
||||||
|
|||||||
9
extensions/cocos/cocos-ecs/assets/scripts/ecs.meta
Normal file
9
extensions/cocos/cocos-ecs/assets/scripts/ecs.meta
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.2.0",
|
||||||
|
"importer": "directory",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "a1f43720-46e1-4d07-b56a-c9307e45726c",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
98
extensions/cocos/cocos-ecs/assets/scripts/ecs/ECSManager.ts
Normal file
98
extensions/cocos/cocos-ecs/assets/scripts/ecs/ECSManager.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { Core } from '@esengine/ecs-framework';
|
||||||
|
import { Component, _decorator } from 'cc';
|
||||||
|
import { GameScene } from './scenes/GameScene';
|
||||||
|
|
||||||
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ECS管理器 - Cocos Creator组件
|
||||||
|
* 将此组件添加到场景中的任意节点上即可启动ECS框架
|
||||||
|
*
|
||||||
|
* 使用说明:
|
||||||
|
* 1. 在Cocos Creator场景中创建一个空节点
|
||||||
|
* 2. 将此ECSManager组件添加到该节点
|
||||||
|
* 3. 运行场景即可自动启动ECS框架
|
||||||
|
*/
|
||||||
|
@ccclass('ECSManager')
|
||||||
|
export class ECSManager extends Component {
|
||||||
|
|
||||||
|
@property({
|
||||||
|
tooltip: '是否启用调试模式(建议开发阶段开启)'
|
||||||
|
})
|
||||||
|
public debugMode: boolean = true;
|
||||||
|
|
||||||
|
private isInitialized: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件启动时初始化ECS
|
||||||
|
*/
|
||||||
|
start() {
|
||||||
|
this.initializeECS();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化ECS框架
|
||||||
|
*/
|
||||||
|
private initializeECS(): void {
|
||||||
|
if (this.isInitialized) return;
|
||||||
|
|
||||||
|
// ECS框架初始化开始
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 创建Core实例,启用调试功能
|
||||||
|
if (this.debugMode) {
|
||||||
|
Core.create({
|
||||||
|
debugConfig: {
|
||||||
|
enabled: true,
|
||||||
|
websocketUrl: 'ws://localhost:8080',
|
||||||
|
autoReconnect: true,
|
||||||
|
updateInterval: 100,
|
||||||
|
debugFrameRate: 30,
|
||||||
|
channels: {
|
||||||
|
entities: true,
|
||||||
|
systems: true,
|
||||||
|
performance: true,
|
||||||
|
components: true,
|
||||||
|
scenes: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// ECS调试模式已启用
|
||||||
|
} else {
|
||||||
|
Core.create(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 创建游戏场景
|
||||||
|
const gameScene = new GameScene();
|
||||||
|
|
||||||
|
// 3. 设置为当前场景(会自动调用scene.begin())
|
||||||
|
Core.scene = gameScene;
|
||||||
|
|
||||||
|
this.isInitialized = true;
|
||||||
|
// ECS框架初始化完成
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('ECS框架初始化失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每帧更新ECS框架
|
||||||
|
*/
|
||||||
|
update(deltaTime: number) {
|
||||||
|
if (this.isInitialized) {
|
||||||
|
// 更新ECS核心系统
|
||||||
|
Core.update(deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件销毁时清理ECS
|
||||||
|
*/
|
||||||
|
onDestroy() {
|
||||||
|
if (this.isInitialized) {
|
||||||
|
// ECS框架清理
|
||||||
|
this.isInitialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "b89656f0-6320-4b6d-81cd-447bf811230c",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
153
extensions/cocos/cocos-ecs/assets/scripts/ecs/README.md
Normal file
153
extensions/cocos/cocos-ecs/assets/scripts/ecs/README.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# ECS框架启动模板
|
||||||
|
|
||||||
|
欢迎使用ECS框架!这是一个最基础的启动模板,帮助您快速开始ECS项目开发。
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ecs/
|
||||||
|
├── components/ # 组件目录(请在此添加您的组件)
|
||||||
|
├── systems/ # 系统目录(请在此添加您的系统)
|
||||||
|
├── scenes/ # 场景目录
|
||||||
|
│ └── GameScene.ts # 主游戏场景
|
||||||
|
├── ECSManager.ts # ECS管理器组件
|
||||||
|
└── README.md # 本文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 启动ECS框架
|
||||||
|
|
||||||
|
ECS框架已经配置完成!您只需要:
|
||||||
|
|
||||||
|
1. 在Cocos Creator中打开您的场景
|
||||||
|
2. 创建一个空节点(例如命名为"ECSManager")
|
||||||
|
3. 将 `ECSManager` 组件添加到该节点
|
||||||
|
4. 运行场景,ECS框架将自动启动
|
||||||
|
|
||||||
|
### 2. 查看控制台输出
|
||||||
|
|
||||||
|
如果一切正常,您将在控制台看到:
|
||||||
|
|
||||||
|
```
|
||||||
|
🎮 正在初始化ECS框架...
|
||||||
|
🔧 ECS调试模式已启用,可在Cocos Creator扩展面板中查看调试信息
|
||||||
|
🎯 游戏场景已创建
|
||||||
|
✅ ECS框架初始化成功!
|
||||||
|
🚀 游戏场景已启动
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用调试面板
|
||||||
|
|
||||||
|
ECS框架已启用调试功能,您可以:
|
||||||
|
|
||||||
|
1. 在Cocos Creator编辑器菜单中选择 "扩展" → "ECS Framework" → "调试面板"
|
||||||
|
2. 调试面板将显示实时的ECS运行状态:
|
||||||
|
- 实体数量和状态
|
||||||
|
- 系统执行信息
|
||||||
|
- 性能监控数据
|
||||||
|
- 组件统计信息
|
||||||
|
|
||||||
|
**注意**:调试功能会消耗一定性能,正式发布时建议关闭调试模式。
|
||||||
|
|
||||||
|
## 📚 下一步开发
|
||||||
|
|
||||||
|
### 创建您的第一个组件
|
||||||
|
|
||||||
|
在 `components/` 目录下创建组件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// components/PositionComponent.ts
|
||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
import { Vec3 } from 'cc';
|
||||||
|
|
||||||
|
export class PositionComponent extends Component {
|
||||||
|
public position: Vec3 = new Vec3();
|
||||||
|
|
||||||
|
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
||||||
|
super();
|
||||||
|
this.position.set(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建您的第一个系统
|
||||||
|
|
||||||
|
在 `systems/` 目录下创建系统:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// systems/MovementSystem.ts
|
||||||
|
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
||||||
|
import { PositionComponent } from '../components/PositionComponent';
|
||||||
|
|
||||||
|
export class MovementSystem extends EntitySystem {
|
||||||
|
constructor() {
|
||||||
|
super(Matcher.empty().all(PositionComponent));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
for (const entity of entities) {
|
||||||
|
const position = entity.getComponent(PositionComponent);
|
||||||
|
if (position) {
|
||||||
|
// TODO: 在这里编写移动逻辑
|
||||||
|
console.log(`实体 ${entity.name} 位置: ${position.position}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在场景中注册系统
|
||||||
|
|
||||||
|
在 `scenes/GameScene.ts` 的 `initialize()` 方法中添加:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { MovementSystem } from '../systems/MovementSystem';
|
||||||
|
|
||||||
|
public initialize(): void {
|
||||||
|
super.initialize();
|
||||||
|
this.name = "MainGameScene";
|
||||||
|
|
||||||
|
// 添加系统
|
||||||
|
this.addEntityProcessor(new MovementSystem());
|
||||||
|
|
||||||
|
// 创建测试实体
|
||||||
|
const testEntity = this.createEntity("TestEntity");
|
||||||
|
testEntity.addComponent(new PositionComponent(0, 0, 0));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔗 学习资源
|
||||||
|
|
||||||
|
- [ECS框架完整文档](https://github.com/esengine/ecs-framework)
|
||||||
|
- [ECS概念详解](https://github.com/esengine/ecs-framework/blob/master/docs/concepts-explained.md)
|
||||||
|
- [新手教程](https://github.com/esengine/ecs-framework/blob/master/docs/beginner-tutorials.md)
|
||||||
|
- [组件设计指南](https://github.com/esengine/ecs-framework/blob/master/docs/component-design-guide.md)
|
||||||
|
- [系统开发指南](https://github.com/esengine/ecs-framework/blob/master/docs/system-guide.md)
|
||||||
|
|
||||||
|
## 💡 开发提示
|
||||||
|
|
||||||
|
1. **组件只存储数据**:避免在组件中编写复杂逻辑
|
||||||
|
2. **系统处理逻辑**:所有业务逻辑应该在系统中实现
|
||||||
|
3. **使用Matcher过滤实体**:系统通过Matcher指定需要处理的实体类型
|
||||||
|
4. **性能优化**:大量实体时考虑使用位掩码查询和组件索引
|
||||||
|
|
||||||
|
## ❓ 常见问题
|
||||||
|
|
||||||
|
### Q: 如何创建实体?
|
||||||
|
A: 在场景中使用 `this.createEntity("实体名称")`
|
||||||
|
|
||||||
|
### Q: 如何给实体添加组件?
|
||||||
|
A: 使用 `entity.addComponent(new YourComponent())`
|
||||||
|
|
||||||
|
### Q: 如何获取实体的组件?
|
||||||
|
A: 使用 `entity.getComponent(YourComponent)`
|
||||||
|
|
||||||
|
### Q: 如何删除实体?
|
||||||
|
A: 使用 `entity.destroy()` 或 `this.destroyEntity(entity)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🎮 **开始您的ECS开发之旅吧!**
|
||||||
|
|
||||||
|
如有问题,请查阅官方文档或提交Issue。
|
||||||
11
extensions/cocos/cocos-ecs/assets/scripts/ecs/README.md.meta
Normal file
11
extensions/cocos/cocos-ecs/assets/scripts/ecs/README.md.meta
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.0.1",
|
||||||
|
"importer": "text",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "ca94b460-6c6a-4f72-9ec1-ab5fcd2e0e0a",
|
||||||
|
"files": [
|
||||||
|
".json"
|
||||||
|
],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.2.0",
|
||||||
|
"importer": "directory",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "3c7bd2b3-6781-482c-be41-21f3dde0e2ab",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命值组件
|
||||||
|
* 管理实体的生命值、最大生命值等
|
||||||
|
*/
|
||||||
|
export class Health extends Component {
|
||||||
|
/** 当前生命值 */
|
||||||
|
public currentHealth: number = 100;
|
||||||
|
|
||||||
|
/** 最大生命值 */
|
||||||
|
public maxHealth: number = 100;
|
||||||
|
|
||||||
|
/** 是否死亡 */
|
||||||
|
public isDead: boolean = false;
|
||||||
|
|
||||||
|
/** 生命值回复速度 (每秒) */
|
||||||
|
public regenRate: number = 0;
|
||||||
|
|
||||||
|
constructor(maxHealth: number = 100) {
|
||||||
|
super();
|
||||||
|
this.maxHealth = maxHealth;
|
||||||
|
this.currentHealth = maxHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 受到伤害
|
||||||
|
*/
|
||||||
|
public takeDamage(damage: number): void {
|
||||||
|
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
||||||
|
this.isDead = this.currentHealth <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 治疗
|
||||||
|
*/
|
||||||
|
public heal(amount: number): void {
|
||||||
|
if (!this.isDead) {
|
||||||
|
this.currentHealth = Math.min(this.maxHealth, this.currentHealth + amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复活
|
||||||
|
*/
|
||||||
|
public revive(healthPercent: number = 1.0): void {
|
||||||
|
this.isDead = false;
|
||||||
|
this.currentHealth = Math.floor(this.maxHealth * Math.max(0, Math.min(1, healthPercent)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取生命值百分比
|
||||||
|
*/
|
||||||
|
public getHealthPercent(): number {
|
||||||
|
return this.maxHealth > 0 ? this.currentHealth / this.maxHealth : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置组件
|
||||||
|
*/
|
||||||
|
public reset(): void {
|
||||||
|
this.currentHealth = this.maxHealth;
|
||||||
|
this.isDead = false;
|
||||||
|
this.regenRate = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "90369635-a6cb-4313-adf1-64117b50f2bc",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
import { Color } from 'cc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染组件
|
||||||
|
* 存储实体的渲染相关信息
|
||||||
|
*/
|
||||||
|
export class Renderer extends Component {
|
||||||
|
/** 颜色 */
|
||||||
|
public color: Color = new Color(255, 255, 255, 255);
|
||||||
|
|
||||||
|
/** 是否可见 */
|
||||||
|
public visible: boolean = true;
|
||||||
|
|
||||||
|
/** 渲染层级 */
|
||||||
|
public layer: number = 0;
|
||||||
|
|
||||||
|
/** 精灵名称或纹理路径 */
|
||||||
|
public spriteName: string = '';
|
||||||
|
|
||||||
|
/** 大小 */
|
||||||
|
public size: { width: number, height: number } = { width: 32, height: 32 };
|
||||||
|
|
||||||
|
/** 透明度 (0-1) */
|
||||||
|
public alpha: number = 1.0;
|
||||||
|
|
||||||
|
constructor(spriteName: string = '', color?: Color) {
|
||||||
|
super();
|
||||||
|
this.spriteName = spriteName;
|
||||||
|
if (color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置颜色
|
||||||
|
*/
|
||||||
|
public setColor(r: number, g: number, b: number, a: number = 255): void {
|
||||||
|
this.color.set(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置透明度
|
||||||
|
*/
|
||||||
|
public setAlpha(alpha: number): void {
|
||||||
|
this.alpha = Math.max(0, Math.min(1, alpha));
|
||||||
|
this.color.a = Math.floor(this.alpha * 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置大小
|
||||||
|
*/
|
||||||
|
public setSize(width: number, height: number): void {
|
||||||
|
this.size.width = width;
|
||||||
|
this.size.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示/隐藏
|
||||||
|
*/
|
||||||
|
public setVisible(visible: boolean): void {
|
||||||
|
this.visible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置组件
|
||||||
|
*/
|
||||||
|
public reset(): void {
|
||||||
|
this.color.set(255, 255, 255, 255);
|
||||||
|
this.visible = true;
|
||||||
|
this.layer = 0;
|
||||||
|
this.spriteName = '';
|
||||||
|
this.size = { width: 32, height: 32 };
|
||||||
|
this.alpha = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "bf51f134-6ea5-4a26-8b15-0ed20fe1d605",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
import { Vec3 } from 'cc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变换组件
|
||||||
|
* 存储实体的位置、旋转和缩放信息
|
||||||
|
*/
|
||||||
|
export class Transform extends Component {
|
||||||
|
/** 位置 */
|
||||||
|
public position: Vec3 = new Vec3(0, 0, 0);
|
||||||
|
|
||||||
|
/** 旋转 (度数) */
|
||||||
|
public rotation: Vec3 = new Vec3(0, 0, 0);
|
||||||
|
|
||||||
|
/** 缩放 */
|
||||||
|
public scale: Vec3 = new Vec3(1, 1, 1);
|
||||||
|
|
||||||
|
/** 移动速度 */
|
||||||
|
public speed: number = 100;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置位置
|
||||||
|
*/
|
||||||
|
public setPosition(x: number, y: number, z: number = 0): void {
|
||||||
|
this.position.set(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动
|
||||||
|
*/
|
||||||
|
public move(deltaX: number, deltaY: number, deltaZ: number = 0): void {
|
||||||
|
this.position.x += deltaX;
|
||||||
|
this.position.y += deltaY;
|
||||||
|
this.position.z += deltaZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置组件
|
||||||
|
*/
|
||||||
|
public reset(): void {
|
||||||
|
this.position.set(0, 0, 0);
|
||||||
|
this.rotation.set(0, 0, 0);
|
||||||
|
this.scale.set(1, 1, 1);
|
||||||
|
this.speed = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "09de6e5b-7bb7-4de8-8038-67be5ae955bc",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { Component } from '@esengine/ecs-framework';
|
||||||
|
import { Vec3 } from 'cc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 速度组件
|
||||||
|
* 存储实体的移动速度和方向
|
||||||
|
*/
|
||||||
|
export class Velocity extends Component {
|
||||||
|
/** 速度向量 */
|
||||||
|
public velocity: Vec3 = new Vec3(0, 0, 0);
|
||||||
|
|
||||||
|
/** 最大速度 */
|
||||||
|
public maxSpeed: number = 200;
|
||||||
|
|
||||||
|
/** 摩擦力 (0-1, 1表示无摩擦) */
|
||||||
|
public friction: number = 0.98;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置速度
|
||||||
|
*/
|
||||||
|
public setVelocity(x: number, y: number, z: number = 0): void {
|
||||||
|
this.velocity.set(x, y, z);
|
||||||
|
this.clampToMaxSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加速度
|
||||||
|
*/
|
||||||
|
public addVelocity(x: number, y: number, z: number = 0): void {
|
||||||
|
this.velocity.x += x;
|
||||||
|
this.velocity.y += y;
|
||||||
|
this.velocity.z += z;
|
||||||
|
this.clampToMaxSpeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用摩擦力
|
||||||
|
*/
|
||||||
|
public applyFriction(): void {
|
||||||
|
this.velocity.multiplyScalar(this.friction);
|
||||||
|
|
||||||
|
// 当速度很小时直接设为0,避免无限减小
|
||||||
|
if (this.velocity.length() < 0.1) {
|
||||||
|
this.velocity.set(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 限制到最大速度
|
||||||
|
*/
|
||||||
|
private clampToMaxSpeed(): void {
|
||||||
|
const currentSpeed = this.velocity.length();
|
||||||
|
if (currentSpeed > this.maxSpeed) {
|
||||||
|
this.velocity.normalize().multiplyScalar(this.maxSpeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前速度大小
|
||||||
|
*/
|
||||||
|
public getSpeed(): number {
|
||||||
|
return this.velocity.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置组件
|
||||||
|
*/
|
||||||
|
public reset(): void {
|
||||||
|
this.velocity.set(0, 0, 0);
|
||||||
|
this.maxSpeed = 200;
|
||||||
|
this.friction = 0.98;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "10c40371-267a-4016-a8b5-9f803e68e72b",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// 导出所有组件
|
||||||
|
export { Transform } from './Transform';
|
||||||
|
export { Health } from './Health';
|
||||||
|
export { Velocity } from './Velocity';
|
||||||
|
export { Renderer } from './Renderer';
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "dfc46d23-7ad6-4a21-914c-35d948185f93",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.2.0",
|
||||||
|
"importer": "directory",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "39da4804-e61e-440e-b73d-544963bd3901",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { Scene } from '@esengine/ecs-framework';
|
||||||
|
import { Color } from 'cc';
|
||||||
|
import { MovementSystem, HealthSystem, RandomMovementSystem } from '../systems';
|
||||||
|
import { Transform, Health, Velocity, Renderer } from '../components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏场景
|
||||||
|
*
|
||||||
|
* 这是您的主游戏场景。在这里可以:
|
||||||
|
* - 添加游戏系统
|
||||||
|
* - 创建初始实体
|
||||||
|
* - 设置场景参数
|
||||||
|
*/
|
||||||
|
export class GameScene extends Scene {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景初始化
|
||||||
|
* 在场景创建时调用,用于设置基础配置
|
||||||
|
*/
|
||||||
|
public initialize(): void {
|
||||||
|
super.initialize();
|
||||||
|
|
||||||
|
// 设置场景名称
|
||||||
|
this.name = "MainGameScene";
|
||||||
|
|
||||||
|
console.log('🎯 游戏场景已创建');
|
||||||
|
|
||||||
|
// 添加游戏系统
|
||||||
|
this.addEntityProcessor(new MovementSystem());
|
||||||
|
this.addEntityProcessor(new HealthSystem());
|
||||||
|
this.addEntityProcessor(new RandomMovementSystem());
|
||||||
|
|
||||||
|
// 创建测试实体
|
||||||
|
this.createTestEntities();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建测试实体
|
||||||
|
*/
|
||||||
|
private createTestEntities(): void {
|
||||||
|
console.log('🚀 开始创建测试实体...');
|
||||||
|
|
||||||
|
// 创建玩家实体
|
||||||
|
const player = this.createEntity("Player");
|
||||||
|
player.addComponent(new Transform());
|
||||||
|
player.addComponent(new Health(150));
|
||||||
|
player.addComponent(new Velocity());
|
||||||
|
player.addComponent(new Renderer("player", new Color(0, 255, 0, 255)));
|
||||||
|
|
||||||
|
const playerTransform = player.getComponent(Transform);
|
||||||
|
const playerHealth = player.getComponent(Health);
|
||||||
|
if (playerTransform) {
|
||||||
|
playerTransform.setPosition(0, 0);
|
||||||
|
playerTransform.speed = 120;
|
||||||
|
}
|
||||||
|
if (playerHealth) {
|
||||||
|
playerHealth.regenRate = 5; // 每秒回复5点生命值
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一些敌人实体
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const enemy = this.createEntity(`Enemy_${i}`);
|
||||||
|
enemy.addComponent(new Transform());
|
||||||
|
enemy.addComponent(new Health(80));
|
||||||
|
enemy.addComponent(new Velocity());
|
||||||
|
enemy.addComponent(new Renderer("enemy", new Color(255, 0, 0, 255)));
|
||||||
|
|
||||||
|
const enemyTransform = enemy.getComponent(Transform);
|
||||||
|
const enemyVelocity = enemy.getComponent(Velocity);
|
||||||
|
if (enemyTransform) {
|
||||||
|
// 随机位置
|
||||||
|
const x = (Math.random() - 0.5) * 800;
|
||||||
|
const y = (Math.random() - 0.5) * 600;
|
||||||
|
enemyTransform.setPosition(x, y);
|
||||||
|
enemyTransform.speed = 80;
|
||||||
|
}
|
||||||
|
if (enemyVelocity) {
|
||||||
|
enemyVelocity.maxSpeed = 120;
|
||||||
|
enemyVelocity.friction = 0.95;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一些中性实体(只移动,无生命值)
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const neutral = this.createEntity(`Neutral_${i}`);
|
||||||
|
neutral.addComponent(new Transform());
|
||||||
|
neutral.addComponent(new Velocity());
|
||||||
|
neutral.addComponent(new Renderer("neutral", new Color(255, 255, 0, 255)));
|
||||||
|
|
||||||
|
const neutralTransform = neutral.getComponent(Transform);
|
||||||
|
const neutralVelocity = neutral.getComponent(Velocity);
|
||||||
|
if (neutralTransform) {
|
||||||
|
const x = (Math.random() - 0.5) * 600;
|
||||||
|
const y = (Math.random() - 0.5) * 400;
|
||||||
|
neutralTransform.setPosition(x, y);
|
||||||
|
neutralTransform.speed = 60;
|
||||||
|
}
|
||||||
|
if (neutralVelocity) {
|
||||||
|
neutralVelocity.maxSpeed = 80;
|
||||||
|
neutralVelocity.friction = 0.99;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景开始运行
|
||||||
|
* 在场景开始时调用,用于执行启动逻辑
|
||||||
|
*/
|
||||||
|
public onStart(): void {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
console.log('🚀 游戏场景已启动');
|
||||||
|
|
||||||
|
// TODO: 在这里添加场景启动逻辑
|
||||||
|
// 例如:创建UI、播放音乐、初始化游戏状态等
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景卸载
|
||||||
|
* 在场景结束时调用,用于清理资源
|
||||||
|
*/
|
||||||
|
public unload(): void {
|
||||||
|
console.log('🛑 游戏场景已结束');
|
||||||
|
|
||||||
|
// TODO: 在这里添加清理逻辑
|
||||||
|
// 例如:清理缓存、释放资源等
|
||||||
|
|
||||||
|
super.unload();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "dbf17c21-6f4a-4f87-8568-7ac5a2ec10cd",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "1.2.0",
|
||||||
|
"importer": "directory",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "f8f0d97b-46f6-49ff-9fe5-b31eee989963",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
||||||
|
import { Health } from '../components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生命值系统
|
||||||
|
* 处理生命值回复、死亡检测等逻辑
|
||||||
|
*/
|
||||||
|
export class HealthSystem extends EntitySystem {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 处理具有Health组件的实体
|
||||||
|
super(Matcher.empty().all(Health));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理所有实体
|
||||||
|
*/
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
const deltaTime = Time.deltaTime;
|
||||||
|
|
||||||
|
for (const entity of entities) {
|
||||||
|
this.processEntity(entity, deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个实体
|
||||||
|
*/
|
||||||
|
private processEntity(entity: Entity, deltaTime: number): void {
|
||||||
|
const health = entity.getComponent(Health);
|
||||||
|
|
||||||
|
if (!health) return;
|
||||||
|
|
||||||
|
// 如果实体已死亡,跳过处理
|
||||||
|
if (health.isDead) return;
|
||||||
|
|
||||||
|
// 处理生命值回复
|
||||||
|
if (health.regenRate > 0) {
|
||||||
|
const regenAmount = health.regenRate * deltaTime;
|
||||||
|
health.heal(regenAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要标记为死亡
|
||||||
|
if (health.currentHealth <= 0 && !health.isDead) {
|
||||||
|
health.isDead = true;
|
||||||
|
this.onEntityDied(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当实体死亡时调用
|
||||||
|
*/
|
||||||
|
private onEntityDied(entity: Entity): void {
|
||||||
|
console.log(`💀 实体 ${entity.name} 已死亡`);
|
||||||
|
|
||||||
|
// 这里可以添加死亡相关的逻辑
|
||||||
|
// 比如播放死亡动画、掉落物品等
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统初始化时调用
|
||||||
|
*/
|
||||||
|
public initialize(): void {
|
||||||
|
super.initialize();
|
||||||
|
console.log('❤️ 生命值系统已启动');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "074b3e3a-351e-4d95-b502-5a7dab8efc8d",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
||||||
|
import { Transform, Velocity } from '../components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动系统
|
||||||
|
* 处理具有Transform和Velocity组件的实体移动
|
||||||
|
*/
|
||||||
|
export class MovementSystem extends EntitySystem {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 使用Matcher设置系统处理的组件类型
|
||||||
|
super(Matcher.empty().all(Transform, Velocity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理所有实体
|
||||||
|
*/
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
const deltaTime = Time.deltaTime;
|
||||||
|
|
||||||
|
for (const entity of entities) {
|
||||||
|
this.processEntity(entity, deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个实体
|
||||||
|
*/
|
||||||
|
private processEntity(entity: Entity, deltaTime: number): void {
|
||||||
|
const transform = entity.getComponent(Transform);
|
||||||
|
const velocity = entity.getComponent(Velocity);
|
||||||
|
|
||||||
|
if (!transform || !velocity) return;
|
||||||
|
|
||||||
|
// 应用摩擦力
|
||||||
|
velocity.applyFriction();
|
||||||
|
|
||||||
|
// 根据速度更新位置
|
||||||
|
const deltaX = velocity.velocity.x * deltaTime;
|
||||||
|
const deltaY = velocity.velocity.y * deltaTime;
|
||||||
|
const deltaZ = velocity.velocity.z * deltaTime;
|
||||||
|
|
||||||
|
transform.move(deltaX, deltaY, deltaZ);
|
||||||
|
|
||||||
|
// 简单的边界检查 (假设游戏世界是 -500 到 500)
|
||||||
|
const bounds = 500;
|
||||||
|
if (transform.position.x > bounds) {
|
||||||
|
transform.position.x = bounds;
|
||||||
|
velocity.velocity.x = -Math.abs(velocity.velocity.x) * 0.5; // 反弹并减速
|
||||||
|
} else if (transform.position.x < -bounds) {
|
||||||
|
transform.position.x = -bounds;
|
||||||
|
velocity.velocity.x = Math.abs(velocity.velocity.x) * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transform.position.y > bounds) {
|
||||||
|
transform.position.y = bounds;
|
||||||
|
velocity.velocity.y = -Math.abs(velocity.velocity.y) * 0.5;
|
||||||
|
} else if (transform.position.y < -bounds) {
|
||||||
|
transform.position.y = -bounds;
|
||||||
|
velocity.velocity.y = Math.abs(velocity.velocity.y) * 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统初始化时调用
|
||||||
|
*/
|
||||||
|
public initialize(): void {
|
||||||
|
super.initialize();
|
||||||
|
console.log('🏃 移动系统已启动');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "5e556e6d-ddd5-415c-b074-3cbdb59ed503",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
||||||
|
import { Transform, Velocity } from '../components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机移动系统
|
||||||
|
* 让实体随机改变移动方向
|
||||||
|
*/
|
||||||
|
export class RandomMovementSystem extends EntitySystem {
|
||||||
|
|
||||||
|
/** 每个实体的下次方向改变时间 */
|
||||||
|
private nextDirectionChangeTime: Map<number, number> = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 处理具有Transform和Velocity组件的实体
|
||||||
|
super(Matcher.empty().all(Transform, Velocity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理所有实体
|
||||||
|
*/
|
||||||
|
protected process(entities: Entity[]): void {
|
||||||
|
const currentTime = Time.totalTime;
|
||||||
|
|
||||||
|
for (const entity of entities) {
|
||||||
|
this.processEntity(entity, currentTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理单个实体
|
||||||
|
*/
|
||||||
|
private processEntity(entity: Entity, currentTime: number): void {
|
||||||
|
const velocity = entity.getComponent(Velocity);
|
||||||
|
|
||||||
|
if (!velocity) return;
|
||||||
|
|
||||||
|
// 检查是否需要改变方向
|
||||||
|
const nextChangeTime = this.nextDirectionChangeTime.get(entity.id) || 0;
|
||||||
|
|
||||||
|
if (currentTime >= nextChangeTime) {
|
||||||
|
// 随机生成新的移动方向
|
||||||
|
const angle = Math.random() * Math.PI * 2; // 0-360度
|
||||||
|
const speed = 50 + Math.random() * 100; // 50-150的随机速度
|
||||||
|
|
||||||
|
const newVelocityX = Math.cos(angle) * speed;
|
||||||
|
const newVelocityY = Math.sin(angle) * speed;
|
||||||
|
|
||||||
|
velocity.setVelocity(newVelocityX, newVelocityY);
|
||||||
|
|
||||||
|
// 设置下次改变方向的时间(1-3秒后)
|
||||||
|
const nextInterval = 1 + Math.random() * 2;
|
||||||
|
this.nextDirectionChangeTime.set(entity.id, currentTime + nextInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当实体被添加到系统时
|
||||||
|
*/
|
||||||
|
protected onAdded(entity: Entity): void {
|
||||||
|
// 为新实体设置初始方向改变时间
|
||||||
|
const initialDelay = Math.random() * 2; // 0-2秒的初始延迟
|
||||||
|
this.nextDirectionChangeTime.set(entity.id, Time.totalTime + initialDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当实体从系统中移除时
|
||||||
|
*/
|
||||||
|
protected onRemoved(entity: Entity): void {
|
||||||
|
// 清理实体的时间记录
|
||||||
|
this.nextDirectionChangeTime.delete(entity.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统初始化时调用
|
||||||
|
*/
|
||||||
|
public initialize(): void {
|
||||||
|
super.initialize();
|
||||||
|
console.log('🎲 随机移动系统已启动');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "ed1688a1-b44f-4588-ae6a-080a6af38a94",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// 导出所有系统
|
||||||
|
export { MovementSystem } from './MovementSystem';
|
||||||
|
export { HealthSystem } from './HealthSystem';
|
||||||
|
export { RandomMovementSystem } from './RandomMovementSystem';
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ver": "4.0.24",
|
||||||
|
"importer": "typescript",
|
||||||
|
"imported": true,
|
||||||
|
"uuid": "586d2e9b-054b-457b-b44c-dafda0a73b6e",
|
||||||
|
"files": [],
|
||||||
|
"subMetas": {},
|
||||||
|
"userData": {}
|
||||||
|
}
|
||||||
Submodule extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension updated: 7ed028a535...282c635093
Submodule extensions/cocos/cocos-ecs/extensions/cocos-terrain-gen updated: 77c10ab574...b7bbe89dcf
37
src/Core.ts
37
src/Core.ts
@@ -6,7 +6,7 @@ import { PerformanceMonitor } from './Utils/PerformanceMonitor';
|
|||||||
import { PoolManager } from './Utils/Pool';
|
import { PoolManager } from './Utils/Pool';
|
||||||
import { ECSFluentAPI, createECSAPI } from './ECS/Core/FluentAPI';
|
import { ECSFluentAPI, createECSAPI } from './ECS/Core/FluentAPI';
|
||||||
import { Scene } from './ECS/Scene';
|
import { Scene } from './ECS/Scene';
|
||||||
import { DebugReporter } from './Utils/DebugReporter';
|
import { DebugManager } from './Utils/Debug';
|
||||||
import { ICoreConfig, IECSDebugConfig } from './types';
|
import { ICoreConfig, IECSDebugConfig } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,11 +110,11 @@ export class Core {
|
|||||||
public _scene?: Scene;
|
public _scene?: Scene;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调试报告器
|
* 调试管理器
|
||||||
*
|
*
|
||||||
* 负责收集和发送调试数据。
|
* 负责收集和发送调试数据。
|
||||||
*/
|
*/
|
||||||
public _debugReporter?: DebugReporter;
|
public _debugManager?: DebugManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core配置
|
* Core配置
|
||||||
@@ -154,9 +154,9 @@ export class Core {
|
|||||||
Core.entitySystemsEnabled = this._config.enableEntitySystems || true;
|
Core.entitySystemsEnabled = this._config.enableEntitySystems || true;
|
||||||
this.debug = this._config.debug || true;
|
this.debug = this._config.debug || true;
|
||||||
|
|
||||||
// 初始化调试报告器
|
// 初始化调试管理器
|
||||||
if (this._config.debugConfig?.enabled) {
|
if (this._config.debugConfig?.enabled) {
|
||||||
this._debugReporter = new DebugReporter(this, this._config.debugConfig);
|
this._debugManager = new DebugManager(this, this._config.debugConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initialize();
|
this.initialize();
|
||||||
@@ -330,10 +330,10 @@ export class Core {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._instance._debugReporter) {
|
if (this._instance._debugManager) {
|
||||||
this._instance._debugReporter.updateConfig(config);
|
this._instance._debugManager.updateConfig(config);
|
||||||
} else {
|
} else {
|
||||||
this._instance._debugReporter = new DebugReporter(this._instance, config);
|
this._instance._debugManager = new DebugManager(this._instance, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新Core配置
|
// 更新Core配置
|
||||||
@@ -346,9 +346,9 @@ export class Core {
|
|||||||
public static disableDebug(): void {
|
public static disableDebug(): void {
|
||||||
if (!this._instance) return;
|
if (!this._instance) return;
|
||||||
|
|
||||||
if (this._instance._debugReporter) {
|
if (this._instance._debugManager) {
|
||||||
this._instance._debugReporter.stop();
|
this._instance._debugManager.stop();
|
||||||
this._instance._debugReporter = undefined;
|
this._instance._debugManager = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新Core配置
|
// 更新Core配置
|
||||||
@@ -363,11 +363,11 @@ export class Core {
|
|||||||
* @returns 当前调试数据,如果调试未启用则返回null
|
* @returns 当前调试数据,如果调试未启用则返回null
|
||||||
*/
|
*/
|
||||||
public static getDebugData(): any {
|
public static getDebugData(): any {
|
||||||
if (!this._instance?._debugReporter) {
|
if (!this._instance?._debugManager) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._instance._debugReporter.getDebugData();
|
return this._instance._debugManager.getDebugData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -393,9 +393,9 @@ export class Core {
|
|||||||
this._ecsAPI = createECSAPI(scene, scene.querySystem, scene.eventSystem);
|
this._ecsAPI = createECSAPI(scene, scene.querySystem, scene.eventSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通知调试报告器场景已变更
|
// 通知调试管理器场景已变更
|
||||||
if (this._debugReporter) {
|
if (this._debugManager) {
|
||||||
this._debugReporter.onSceneChanged();
|
this._debugManager.onSceneChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,6 +457,11 @@ export class Core {
|
|||||||
this._performanceMonitor.endMonitoring('Scene.update', sceneStartTime, entityCount);
|
this._performanceMonitor.endMonitoring('Scene.update', sceneStartTime, entityCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新调试管理器(基于FPS的数据发送)
|
||||||
|
if (this._debugManager) {
|
||||||
|
this._debugManager.onFrameUpdate(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
// 结束性能监控
|
// 结束性能监控
|
||||||
this._performanceMonitor.endMonitoring('Core.update', frameStartTime);
|
this._performanceMonitor.endMonitoring('Core.update', frameStartTime);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ export class DirtyTrackingSystem {
|
|||||||
try {
|
try {
|
||||||
listener.callback(dirtyData);
|
listener.callback(dirtyData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Dirty listener error:', error);
|
console.error('脏数据监听器错误:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +346,7 @@ export class DirtyTrackingSystem {
|
|||||||
try {
|
try {
|
||||||
listener.callback(dirtyData);
|
listener.callback(dirtyData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Dirty listener notification error:', error);
|
console.error('脏数据监听器通知错误:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export class EventBus implements IEventBus {
|
|||||||
const enhancedData = this.enhanceEventData(eventType, data);
|
const enhancedData = this.enhanceEventData(eventType, data);
|
||||||
|
|
||||||
if (this.isDebugMode) {
|
if (this.isDebugMode) {
|
||||||
console.log(`[EventBus] Emitting event: ${eventType}`, enhancedData);
|
console.log(`[EventBus] 发射事件: ${eventType}`, enhancedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventSystem.emitSync(eventType, enhancedData);
|
this.eventSystem.emitSync(eventType, enhancedData);
|
||||||
@@ -65,7 +65,7 @@ export class EventBus implements IEventBus {
|
|||||||
const enhancedData = this.enhanceEventData(eventType, data);
|
const enhancedData = this.enhanceEventData(eventType, data);
|
||||||
|
|
||||||
if (this.isDebugMode) {
|
if (this.isDebugMode) {
|
||||||
console.log(`[EventBus] Emitting async event: ${eventType}`, enhancedData);
|
console.log(`[EventBus] 发射异步事件: ${eventType}`, enhancedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.eventSystem.emit(eventType, enhancedData);
|
await this.eventSystem.emit(eventType, enhancedData);
|
||||||
@@ -93,7 +93,7 @@ export class EventBus implements IEventBus {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.isDebugMode) {
|
if (this.isDebugMode) {
|
||||||
console.log(`[EventBus] Adding listener for: ${eventType}`, eventConfig);
|
console.log(`[EventBus] 添加监听器: ${eventType}`, eventConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.eventSystem.on(eventType, handler, eventConfig);
|
return this.eventSystem.on(eventType, handler, eventConfig);
|
||||||
@@ -136,7 +136,7 @@ export class EventBus implements IEventBus {
|
|||||||
*/
|
*/
|
||||||
public off(eventType: string, listenerId: string): boolean {
|
public off(eventType: string, listenerId: string): boolean {
|
||||||
if (this.isDebugMode) {
|
if (this.isDebugMode) {
|
||||||
console.log(`[EventBus] Removing listener: ${listenerId} for event: ${eventType}`);
|
console.log(`[EventBus] 移除监听器: ${listenerId} 事件: ${eventType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.eventSystem.off(eventType, listenerId);
|
return this.eventSystem.off(eventType, listenerId);
|
||||||
@@ -148,7 +148,7 @@ export class EventBus implements IEventBus {
|
|||||||
*/
|
*/
|
||||||
public offAll(eventType: string): void {
|
public offAll(eventType: string): void {
|
||||||
if (this.isDebugMode) {
|
if (this.isDebugMode) {
|
||||||
console.log(`[EventBus] Removing all listeners for event: ${eventType}`);
|
console.log(`[EventBus] 移除所有监听器: ${eventType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventSystem.offAll(eventType);
|
this.eventSystem.offAll(eventType);
|
||||||
@@ -186,7 +186,7 @@ export class EventBus implements IEventBus {
|
|||||||
*/
|
*/
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
if (this.isDebugMode) {
|
if (this.isDebugMode) {
|
||||||
console.log('[EventBus] Clearing all listeners');
|
console.log('[EventBus] 清空所有监听器');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventSystem.clear();
|
this.eventSystem.clear();
|
||||||
@@ -379,7 +379,7 @@ export class EventBus implements IEventBus {
|
|||||||
private validateEventType(eventType: string): void {
|
private validateEventType(eventType: string): void {
|
||||||
if (!EventTypeValidator.isValid(eventType)) {
|
if (!EventTypeValidator.isValid(eventType)) {
|
||||||
if (this.isDebugMode) {
|
if (this.isDebugMode) {
|
||||||
console.warn(`[EventBus] Unknown event type: ${eventType}`);
|
console.warn(`[EventBus] 未知事件类型: ${eventType}`);
|
||||||
}
|
}
|
||||||
// 在调试模式下添加自定义事件类型
|
// 在调试模式下添加自定义事件类型
|
||||||
if (this.isDebugMode) {
|
if (this.isDebugMode) {
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ export class TypeSafeEventSystem {
|
|||||||
* @returns 监听器ID(用于移除)
|
* @returns 监听器ID(用于移除)
|
||||||
*/
|
*/
|
||||||
public on<T>(
|
public on<T>(
|
||||||
eventType: string,
|
eventType: string,
|
||||||
handler: EventHandler<T>,
|
handler: EventHandler<T>,
|
||||||
config: EventListenerConfig = {}
|
config: EventListenerConfig = {}
|
||||||
): string {
|
): string {
|
||||||
return this.addListener(eventType, handler, config);
|
return this.addListener(eventType, handler, config);
|
||||||
@@ -98,8 +98,8 @@ export class TypeSafeEventSystem {
|
|||||||
* @returns 监听器ID
|
* @returns 监听器ID
|
||||||
*/
|
*/
|
||||||
public once<T>(
|
public once<T>(
|
||||||
eventType: string,
|
eventType: string,
|
||||||
handler: EventHandler<T>,
|
handler: EventHandler<T>,
|
||||||
config: EventListenerConfig = {}
|
config: EventListenerConfig = {}
|
||||||
): string {
|
): string {
|
||||||
return this.addListener(eventType, handler, { ...config, once: true });
|
return this.addListener(eventType, handler, { ...config, once: true });
|
||||||
@@ -113,8 +113,8 @@ export class TypeSafeEventSystem {
|
|||||||
* @returns 监听器ID
|
* @returns 监听器ID
|
||||||
*/
|
*/
|
||||||
public onAsync<T>(
|
public onAsync<T>(
|
||||||
eventType: string,
|
eventType: string,
|
||||||
handler: AsyncEventHandler<T>,
|
handler: AsyncEventHandler<T>,
|
||||||
config: EventListenerConfig = {}
|
config: EventListenerConfig = {}
|
||||||
): string {
|
): string {
|
||||||
return this.addListener(eventType, handler, { ...config, async: true });
|
return this.addListener(eventType, handler, { ...config, async: true });
|
||||||
@@ -134,7 +134,7 @@ export class TypeSafeEventSystem {
|
|||||||
if (index === -1) return false;
|
if (index === -1) return false;
|
||||||
|
|
||||||
listeners.splice(index, 1);
|
listeners.splice(index, 1);
|
||||||
|
|
||||||
// 如果没有监听器了,清理相关数据
|
// 如果没有监听器了,清理相关数据
|
||||||
if (listeners.length === 0) {
|
if (listeners.length === 0) {
|
||||||
this.listeners.delete(eventType);
|
this.listeners.delete(eventType);
|
||||||
@@ -204,7 +204,7 @@ export class TypeSafeEventSystem {
|
|||||||
toRemove.push(listener.id);
|
toRemove.push(listener.id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error in event handler for ${eventType}:`, error);
|
console.error(`事件处理器执行错误 ${eventType}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ export class TypeSafeEventSystem {
|
|||||||
|
|
||||||
// 处理批处理事件
|
// 处理批处理事件
|
||||||
this.processBatch(eventType, batch);
|
this.processBatch(eventType, batch);
|
||||||
|
|
||||||
// 清空队列
|
// 清空队列
|
||||||
this.batchQueue.delete(eventType);
|
this.batchQueue.delete(eventType);
|
||||||
}
|
}
|
||||||
@@ -323,12 +323,12 @@ export class TypeSafeEventSystem {
|
|||||||
* @returns 监听器ID
|
* @returns 监听器ID
|
||||||
*/
|
*/
|
||||||
private addListener<T>(
|
private addListener<T>(
|
||||||
eventType: string,
|
eventType: string,
|
||||||
handler: EventHandler<T> | AsyncEventHandler<T>,
|
handler: EventHandler<T> | AsyncEventHandler<T>,
|
||||||
config: EventListenerConfig
|
config: EventListenerConfig
|
||||||
): string {
|
): string {
|
||||||
let listeners = this.listeners.get(eventType);
|
let listeners = this.listeners.get(eventType);
|
||||||
|
|
||||||
if (!listeners) {
|
if (!listeners) {
|
||||||
listeners = [];
|
listeners = [];
|
||||||
this.listeners.set(eventType, listeners);
|
this.listeners.set(eventType, listeners);
|
||||||
@@ -336,7 +336,7 @@ export class TypeSafeEventSystem {
|
|||||||
|
|
||||||
// 检查监听器数量限制
|
// 检查监听器数量限制
|
||||||
if (listeners.length >= this.maxListeners) {
|
if (listeners.length >= this.maxListeners) {
|
||||||
console.warn(`Maximum listeners (${this.maxListeners}) exceeded for event type: ${eventType}`);
|
console.warn(`事件类型 ${eventType} 的监听器数量超过最大限制 (${this.maxListeners})`);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +392,7 @@ export class TypeSafeEventSystem {
|
|||||||
toRemove.push(listener.id);
|
toRemove.push(listener.id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error in sync event handler for ${eventType}:`, error);
|
console.error(`同步事件处理器执行错误 ${eventType}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,7 +409,7 @@ export class TypeSafeEventSystem {
|
|||||||
toRemove.push(listener.id);
|
toRemove.push(listener.id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error in async event handler for ${eventType}:`, error);
|
console.error(`异步事件处理器执行错误 ${eventType}:`, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -472,7 +472,7 @@ export class TypeSafeEventSystem {
|
|||||||
batch.push(event);
|
batch.push(event);
|
||||||
|
|
||||||
const config = this.batchConfigs.get(eventType)!;
|
const config = this.batchConfigs.get(eventType)!;
|
||||||
|
|
||||||
// 如果达到批处理大小,立即处理
|
// 如果达到批处理大小,立即处理
|
||||||
if (batch.length >= config.batchSize) {
|
if (batch.length >= config.batchSize) {
|
||||||
this.flushBatch(eventType);
|
this.flushBatch(eventType);
|
||||||
@@ -484,7 +484,7 @@ export class TypeSafeEventSystem {
|
|||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
this.flushBatch(eventType);
|
this.flushBatch(eventType);
|
||||||
}, config.delay);
|
}, config.delay);
|
||||||
|
|
||||||
this.batchTimers.set(eventType, timer as any);
|
this.batchTimers.set(eventType, timer as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -513,7 +513,7 @@ export class TypeSafeEventSystem {
|
|||||||
*/
|
*/
|
||||||
private clearBatch(eventType: string): void {
|
private clearBatch(eventType: string): void {
|
||||||
this.batchQueue.delete(eventType);
|
this.batchQueue.delete(eventType);
|
||||||
|
|
||||||
const timer = this.batchTimers.get(eventType);
|
const timer = this.batchTimers.get(eventType);
|
||||||
if (timer) {
|
if (timer) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
@@ -526,7 +526,7 @@ export class TypeSafeEventSystem {
|
|||||||
*/
|
*/
|
||||||
private clearAllBatches(): void {
|
private clearAllBatches(): void {
|
||||||
this.batchQueue.clear();
|
this.batchQueue.clear();
|
||||||
|
|
||||||
for (const timer of this.batchTimers.values()) {
|
for (const timer of this.batchTimers.values()) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
@@ -583,10 +583,10 @@ export const GlobalEventSystem = new TypeSafeEventSystem();
|
|||||||
export function EventListener(eventType: string, config: EventListenerConfig = {}) {
|
export function EventListener(eventType: string, config: EventListenerConfig = {}) {
|
||||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||||
const originalMethod = descriptor.value;
|
const originalMethod = descriptor.value;
|
||||||
|
|
||||||
// 在类实例化时自动注册监听器
|
// 在类实例化时自动注册监听器
|
||||||
const initMethod = target.constructor.prototype.initEventListeners || function() {};
|
const initMethod = target.constructor.prototype.initEventListeners || function () { };
|
||||||
target.constructor.prototype.initEventListeners = function() {
|
target.constructor.prototype.initEventListeners = function () {
|
||||||
initMethod.call(this);
|
initMethod.call(this);
|
||||||
GlobalEventSystem.on(eventType, originalMethod.bind(this), config);
|
GlobalEventSystem.on(eventType, originalMethod.bind(this), config);
|
||||||
};
|
};
|
||||||
@@ -601,9 +601,9 @@ export function EventListener(eventType: string, config: EventListenerConfig = {
|
|||||||
export function AsyncEventListener(eventType: string, config: EventListenerConfig = {}) {
|
export function AsyncEventListener(eventType: string, config: EventListenerConfig = {}) {
|
||||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||||
const originalMethod = descriptor.value;
|
const originalMethod = descriptor.value;
|
||||||
|
|
||||||
const initMethod = target.constructor.prototype.initEventListeners || function() {};
|
const initMethod = target.constructor.prototype.initEventListeners || function () { };
|
||||||
target.constructor.prototype.initEventListeners = function() {
|
target.constructor.prototype.initEventListeners = function () {
|
||||||
initMethod.call(this);
|
initMethod.call(this);
|
||||||
GlobalEventSystem.onAsync(eventType, originalMethod.bind(this), config);
|
GlobalEventSystem.onAsync(eventType, originalMethod.bind(this), config);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -857,7 +857,7 @@ export class Entity {
|
|||||||
addedComponents.push(this.addComponent(component));
|
addedComponents.push(this.addComponent(component));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 如果某个组件添加失败,继续添加其他组件
|
// 如果某个组件添加失败,继续添加其他组件
|
||||||
console.warn(`Failed to add component ${component.constructor.name}:`, error);
|
console.warn(`添加组件失败 ${component.constructor.name}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -246,8 +246,10 @@ export interface IECSDebugConfig {
|
|||||||
websocketUrl: string;
|
websocketUrl: string;
|
||||||
/** 是否自动重连 */
|
/** 是否自动重连 */
|
||||||
autoReconnect?: boolean;
|
autoReconnect?: boolean;
|
||||||
/** 数据更新间隔(毫秒) */
|
/** 数据更新间隔(毫秒)- 已弃用,使用debugFrameRate替代 */
|
||||||
updateInterval?: number;
|
updateInterval?: number;
|
||||||
|
/** 调试数据发送帧率 (60fps, 30fps, 15fps) */
|
||||||
|
debugFrameRate?: 60 | 30 | 15;
|
||||||
/** 数据通道配置 */
|
/** 数据通道配置 */
|
||||||
channels: {
|
channels: {
|
||||||
entities: boolean;
|
entities: boolean;
|
||||||
@@ -330,6 +332,44 @@ export interface IEntityDebugData {
|
|||||||
componentCount: number;
|
componentCount: number;
|
||||||
components: string[];
|
components: string[];
|
||||||
}>;
|
}>;
|
||||||
|
/** 实体层次结构(根实体) */
|
||||||
|
entityHierarchy?: Array<{
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
active: boolean;
|
||||||
|
enabled: boolean;
|
||||||
|
activeInHierarchy: boolean;
|
||||||
|
componentCount: number;
|
||||||
|
componentTypes: string[];
|
||||||
|
parentId: number | null;
|
||||||
|
children: any[];
|
||||||
|
depth: number;
|
||||||
|
tag: number;
|
||||||
|
updateOrder: number;
|
||||||
|
}>;
|
||||||
|
/** 实体详细信息映射 */
|
||||||
|
entityDetailsMap?: Record<number, {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
active: boolean;
|
||||||
|
enabled: boolean;
|
||||||
|
activeInHierarchy: boolean;
|
||||||
|
destroyed: boolean;
|
||||||
|
tag: number;
|
||||||
|
updateOrder: number;
|
||||||
|
componentMask: string;
|
||||||
|
parentId: number | null;
|
||||||
|
parentName: string | null;
|
||||||
|
childCount: number;
|
||||||
|
childIds: number[];
|
||||||
|
depth: number;
|
||||||
|
components: Array<{
|
||||||
|
typeName: string;
|
||||||
|
properties: Record<string, any>;
|
||||||
|
}>;
|
||||||
|
componentCount: number;
|
||||||
|
componentTypes: string[];
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
185
src/Utils/Debug/ComponentDataCollector.ts
Normal file
185
src/Utils/Debug/ComponentDataCollector.ts
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
import { IComponentDebugData } from '../../types';
|
||||||
|
import { Core } from '../../Core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件数据收集器
|
||||||
|
*/
|
||||||
|
export class ComponentDataCollector {
|
||||||
|
/**
|
||||||
|
* 收集组件数据
|
||||||
|
*/
|
||||||
|
public collectComponentData(): IComponentDebugData {
|
||||||
|
const scene = Core.scene;
|
||||||
|
if (!scene) {
|
||||||
|
return {
|
||||||
|
componentTypes: 0,
|
||||||
|
componentInstances: 0,
|
||||||
|
componentStats: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityList = (scene as any).entities;
|
||||||
|
if (!entityList?.buffer) {
|
||||||
|
return {
|
||||||
|
componentTypes: 0,
|
||||||
|
componentInstances: 0,
|
||||||
|
componentStats: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentStats = new Map<string, { count: number; entities: number }>();
|
||||||
|
let totalInstances = 0;
|
||||||
|
|
||||||
|
entityList.buffer.forEach((entity: any) => {
|
||||||
|
if (entity.components) {
|
||||||
|
entity.components.forEach((component: any) => {
|
||||||
|
const typeName = component.constructor.name;
|
||||||
|
const stats = componentStats.get(typeName) || { count: 0, entities: 0 };
|
||||||
|
stats.count++;
|
||||||
|
totalInstances++;
|
||||||
|
componentStats.set(typeName, stats);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取池利用率信息
|
||||||
|
let poolUtilizations = new Map<string, number>();
|
||||||
|
let poolSizes = new Map<string, number>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { ComponentPoolManager } = require('../../ECS/Core/ComponentPool');
|
||||||
|
const poolManager = ComponentPoolManager.getInstance();
|
||||||
|
const poolStats = poolManager.getPoolStats();
|
||||||
|
const utilizations = poolManager.getPoolUtilization();
|
||||||
|
|
||||||
|
for (const [typeName, stats] of poolStats.entries()) {
|
||||||
|
poolSizes.set(typeName, stats.maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [typeName, util] of utilizations.entries()) {
|
||||||
|
poolUtilizations.set(typeName, util.utilization);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 如果无法获取池信息,使用默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
componentTypes: componentStats.size,
|
||||||
|
componentInstances: totalInstances,
|
||||||
|
componentStats: Array.from(componentStats.entries()).map(([typeName, stats]) => {
|
||||||
|
const poolSize = poolSizes.get(typeName) || 0;
|
||||||
|
const poolUtilization = poolUtilizations.get(typeName) || 0;
|
||||||
|
const memoryPerInstance = this.calculateComponentMemorySize(typeName);
|
||||||
|
|
||||||
|
return {
|
||||||
|
typeName,
|
||||||
|
instanceCount: stats.count,
|
||||||
|
memoryPerInstance: memoryPerInstance,
|
||||||
|
totalMemory: stats.count * memoryPerInstance,
|
||||||
|
poolSize: poolSize,
|
||||||
|
poolUtilization: poolUtilization,
|
||||||
|
averagePerEntity: stats.count / entityList.buffer.length
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算组件实际内存大小
|
||||||
|
*/
|
||||||
|
private calculateComponentMemorySize(typeName: string): number {
|
||||||
|
const scene = Core.scene;
|
||||||
|
if (!scene) return 32;
|
||||||
|
|
||||||
|
const entityList = (scene as any).entities;
|
||||||
|
if (!entityList?.buffer) return 32;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 找到第一个包含此组件的实体,分析组件大小
|
||||||
|
for (const entity of entityList.buffer) {
|
||||||
|
if (entity.components) {
|
||||||
|
const component = entity.components.find((c: any) => c.constructor.name === typeName);
|
||||||
|
if (component) {
|
||||||
|
return this.estimateObjectSize(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略错误,使用默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果无法计算,返回基础大小
|
||||||
|
return 32; // 基础对象开销
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 估算对象内存大小
|
||||||
|
*/
|
||||||
|
private estimateObjectSize(obj: any, visited = new WeakSet(), depth = 0): number {
|
||||||
|
if (obj === null || obj === undefined || depth > 50) return 0;
|
||||||
|
if (visited.has(obj)) return 0;
|
||||||
|
|
||||||
|
let size = 0;
|
||||||
|
const type = typeof obj;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'boolean':
|
||||||
|
size = 1;
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
size = 8;
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
const stringSize = 24 + (obj.length * 2);
|
||||||
|
size = Math.ceil(stringSize / 8) * 8;
|
||||||
|
break;
|
||||||
|
case 'object':
|
||||||
|
visited.add(obj);
|
||||||
|
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
size = 40 + (obj.length * 8);
|
||||||
|
for (let i = 0; i < obj.length; i++) {
|
||||||
|
size += this.estimateObjectSize(obj[i], visited, depth + 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
size = 32;
|
||||||
|
const allKeys = [
|
||||||
|
...Object.getOwnPropertyNames(obj),
|
||||||
|
...Object.getOwnPropertySymbols(obj)
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const key of allKeys) {
|
||||||
|
try {
|
||||||
|
if (typeof key === 'string' && (
|
||||||
|
key === 'constructor' ||
|
||||||
|
key === '__proto__' ||
|
||||||
|
key === 'entity' || key === '_entity'
|
||||||
|
)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
|
||||||
|
if (!descriptor) continue;
|
||||||
|
|
||||||
|
if (typeof key === 'string') {
|
||||||
|
size += 16 + (key.length * 2);
|
||||||
|
} else {
|
||||||
|
size += 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descriptor.value !== undefined) {
|
||||||
|
size += this.estimateObjectSize(descriptor.value, visited, depth + 1);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
size = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.ceil(size / 8) * 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/Utils/Debug/DebugDataFormatter.ts
Normal file
124
src/Utils/Debug/DebugDataFormatter.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { Component } from '../../ECS/Component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试数据格式化工具
|
||||||
|
*/
|
||||||
|
export class DebugDataFormatter {
|
||||||
|
/**
|
||||||
|
* 格式化属性值
|
||||||
|
*/
|
||||||
|
public static formatPropertyValue(value: any, depth: number = 0): any {
|
||||||
|
// 防止无限递归,限制最大深度
|
||||||
|
if (depth > 5) {
|
||||||
|
return value?.toString() || 'null';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
// 对于数组,总是返回完整数组,让前端决定如何显示
|
||||||
|
return value.map(item => this.formatPropertyValue(item, depth + 1));
|
||||||
|
} else {
|
||||||
|
// 通用对象处理:提取所有可枚举属性,不限制数量
|
||||||
|
try {
|
||||||
|
const keys = Object.keys(value);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: any = {};
|
||||||
|
keys.forEach(key => {
|
||||||
|
const propValue = value[key];
|
||||||
|
// 避免循环引用和函数属性
|
||||||
|
if (propValue !== value && typeof propValue !== 'function') {
|
||||||
|
try {
|
||||||
|
result[key] = this.formatPropertyValue(propValue, depth + 1);
|
||||||
|
} catch (error) {
|
||||||
|
// 如果属性访问失败,记录错误信息
|
||||||
|
result[key] = `[访问失败: ${error instanceof Error ? error.message : String(error)}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
return `[对象解析失败: ${error instanceof Error ? error.message : String(error)}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取组件详细信息
|
||||||
|
*/
|
||||||
|
public static extractComponentDetails(components: Component[]): Array<{
|
||||||
|
typeName: string;
|
||||||
|
properties: Record<string, any>;
|
||||||
|
}> {
|
||||||
|
return components.map((component: Component) => {
|
||||||
|
const componentDetail = {
|
||||||
|
typeName: component.constructor.name,
|
||||||
|
properties: {} as Record<string, any>
|
||||||
|
};
|
||||||
|
|
||||||
|
// 安全地提取组件属性
|
||||||
|
try {
|
||||||
|
const propertyKeys = Object.keys(component);
|
||||||
|
propertyKeys.forEach(propertyKey => {
|
||||||
|
// 跳过私有属性和实体引用,避免循环引用
|
||||||
|
if (!propertyKey.startsWith('_') && propertyKey !== 'entity') {
|
||||||
|
const propertyValue = (component as any)[propertyKey];
|
||||||
|
if (propertyValue !== undefined && propertyValue !== null) {
|
||||||
|
componentDetail.properties[propertyKey] = this.formatPropertyValue(propertyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
componentDetail.properties['_extractionError'] = '属性提取失败';
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentDetail;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算对象大小
|
||||||
|
*/
|
||||||
|
public static calculateObjectSize(obj: any, excludeKeys: string[] = []): number {
|
||||||
|
if (!obj || typeof obj !== 'object') return 0;
|
||||||
|
|
||||||
|
let size = 0;
|
||||||
|
const visited = new WeakSet();
|
||||||
|
|
||||||
|
const calculate = (item: any): number => {
|
||||||
|
if (!item || typeof item !== 'object' || visited.has(item)) return 0;
|
||||||
|
visited.add(item);
|
||||||
|
|
||||||
|
let itemSize = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const key in item) {
|
||||||
|
if (excludeKeys.includes(key)) continue;
|
||||||
|
|
||||||
|
const value = item[key];
|
||||||
|
itemSize += key.length * 2; // key size
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
itemSize += value.length * 2;
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
itemSize += 8;
|
||||||
|
} else if (typeof value === 'boolean') {
|
||||||
|
itemSize += 4;
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
itemSize += calculate(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略无法访问的属性
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
return calculate(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
615
src/Utils/Debug/DebugManager.ts
Normal file
615
src/Utils/Debug/DebugManager.ts
Normal file
@@ -0,0 +1,615 @@
|
|||||||
|
import { IECSDebugConfig, IECSDebugData } from '../../types';
|
||||||
|
import { EntityDataCollector } from './EntityDataCollector';
|
||||||
|
import { SystemDataCollector } from './SystemDataCollector';
|
||||||
|
import { PerformanceDataCollector } from './PerformanceDataCollector';
|
||||||
|
import { ComponentDataCollector } from './ComponentDataCollector';
|
||||||
|
import { SceneDataCollector } from './SceneDataCollector';
|
||||||
|
import { WebSocketManager } from './WebSocketManager';
|
||||||
|
import { Core } from '../../Core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调试管理器
|
||||||
|
*
|
||||||
|
* 整合所有调试数据收集器,负责收集和发送调试数据
|
||||||
|
*/
|
||||||
|
export class DebugManager {
|
||||||
|
private config: IECSDebugConfig;
|
||||||
|
private webSocketManager: WebSocketManager;
|
||||||
|
private entityCollector: EntityDataCollector;
|
||||||
|
private systemCollector: SystemDataCollector;
|
||||||
|
private performanceCollector: PerformanceDataCollector;
|
||||||
|
private componentCollector: ComponentDataCollector;
|
||||||
|
private sceneCollector: SceneDataCollector;
|
||||||
|
|
||||||
|
private frameCounter: number = 0;
|
||||||
|
private lastSendTime: number = 0;
|
||||||
|
private sendInterval: number;
|
||||||
|
private isRunning: boolean = false;
|
||||||
|
|
||||||
|
constructor(core: Core, config: IECSDebugConfig) {
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
// 初始化数据收集器
|
||||||
|
this.entityCollector = new EntityDataCollector();
|
||||||
|
this.systemCollector = new SystemDataCollector();
|
||||||
|
this.performanceCollector = new PerformanceDataCollector();
|
||||||
|
this.componentCollector = new ComponentDataCollector();
|
||||||
|
this.sceneCollector = new SceneDataCollector();
|
||||||
|
|
||||||
|
// 初始化WebSocket管理器
|
||||||
|
this.webSocketManager = new WebSocketManager(
|
||||||
|
config.websocketUrl,
|
||||||
|
config.autoReconnect !== false
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置消息处理回调
|
||||||
|
this.webSocketManager.setMessageHandler(this.handleMessage.bind(this));
|
||||||
|
|
||||||
|
// 计算发送间隔(基于帧率)
|
||||||
|
const debugFrameRate = config.debugFrameRate || 30;
|
||||||
|
this.sendInterval = 1000 / debugFrameRate;
|
||||||
|
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动调试管理器
|
||||||
|
*/
|
||||||
|
public start(): void {
|
||||||
|
if (this.isRunning) return;
|
||||||
|
|
||||||
|
this.isRunning = true;
|
||||||
|
this.connectWebSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止调试管理器
|
||||||
|
*/
|
||||||
|
public stop(): void {
|
||||||
|
if (!this.isRunning) return;
|
||||||
|
|
||||||
|
this.isRunning = false;
|
||||||
|
this.webSocketManager.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*/
|
||||||
|
public updateConfig(config: IECSDebugConfig): void {
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
// 更新发送间隔
|
||||||
|
const debugFrameRate = config.debugFrameRate || 30;
|
||||||
|
this.sendInterval = 1000 / debugFrameRate;
|
||||||
|
|
||||||
|
// 重新连接WebSocket(如果URL变化)
|
||||||
|
if (this.webSocketManager && config.websocketUrl) {
|
||||||
|
this.webSocketManager.disconnect();
|
||||||
|
this.webSocketManager = new WebSocketManager(
|
||||||
|
config.websocketUrl,
|
||||||
|
config.autoReconnect !== false
|
||||||
|
);
|
||||||
|
this.webSocketManager.setMessageHandler(this.handleMessage.bind(this));
|
||||||
|
this.connectWebSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 帧更新回调
|
||||||
|
*/
|
||||||
|
public onFrameUpdate(deltaTime: number): void {
|
||||||
|
if (!this.isRunning || !this.config.enabled) return;
|
||||||
|
|
||||||
|
this.frameCounter++;
|
||||||
|
const currentTime = Date.now();
|
||||||
|
|
||||||
|
// 基于配置的帧率发送数据
|
||||||
|
if (currentTime - this.lastSendTime >= this.sendInterval) {
|
||||||
|
this.sendDebugData();
|
||||||
|
this.lastSendTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景变更回调
|
||||||
|
*/
|
||||||
|
public onSceneChanged(): void {
|
||||||
|
// 场景变更时立即发送一次数据
|
||||||
|
if (this.isRunning && this.config.enabled) {
|
||||||
|
this.sendDebugData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理来自调试面板的消息
|
||||||
|
*/
|
||||||
|
private handleMessage(message: any): void {
|
||||||
|
try {
|
||||||
|
switch (message.type) {
|
||||||
|
case 'capture_memory_snapshot':
|
||||||
|
this.handleMemorySnapshotRequest();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'config_update':
|
||||||
|
if (message.config) {
|
||||||
|
this.updateConfig({ ...this.config, ...message.config });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ping':
|
||||||
|
this.webSocketManager.send({
|
||||||
|
type: 'pong',
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 未知消息类型,忽略
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('[ECS Debug] 处理消息失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理内存快照请求
|
||||||
|
*/
|
||||||
|
private handleMemorySnapshotRequest(): void {
|
||||||
|
try {
|
||||||
|
const memorySnapshot = this.captureMemorySnapshot();
|
||||||
|
this.webSocketManager.send({
|
||||||
|
type: 'memory_snapshot_response',
|
||||||
|
data: memorySnapshot
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.webSocketManager.send({
|
||||||
|
type: 'memory_snapshot_error',
|
||||||
|
error: error instanceof Error ? error.message : '内存快照捕获失败'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获内存快照
|
||||||
|
*/
|
||||||
|
private captureMemorySnapshot(): any {
|
||||||
|
const scene = Core.scene;
|
||||||
|
if (!scene) {
|
||||||
|
throw new Error('没有活跃的场景');
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityList = (scene as any).entities;
|
||||||
|
if (!entityList?.buffer) {
|
||||||
|
throw new Error('无法访问实体列表');
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = Date.now();
|
||||||
|
|
||||||
|
// 1. 收集基础内存信息
|
||||||
|
const baseMemoryInfo = this.collectBaseMemoryInfo();
|
||||||
|
|
||||||
|
// 2. 收集实体内存统计
|
||||||
|
const entityMemoryStats = this.collectEntityMemoryStats(entityList);
|
||||||
|
|
||||||
|
// 3. 收集组件内存统计
|
||||||
|
const componentMemoryStats = this.collectComponentMemoryStats(entityList);
|
||||||
|
|
||||||
|
// 4. 收集系统内存统计
|
||||||
|
const systemMemoryStats = this.collectSystemMemoryStats();
|
||||||
|
|
||||||
|
// 5. 收集对象池内存统计
|
||||||
|
const poolMemoryStats = this.collectPoolMemoryStats();
|
||||||
|
|
||||||
|
// 6. 收集性能监控器统计
|
||||||
|
const performanceStats = this.collectPerformanceStats();
|
||||||
|
|
||||||
|
return {
|
||||||
|
timestamp,
|
||||||
|
version: '2.0',
|
||||||
|
summary: {
|
||||||
|
totalEntities: entityList.buffer.length,
|
||||||
|
totalMemoryUsage: baseMemoryInfo.usedMemory,
|
||||||
|
totalMemoryLimit: baseMemoryInfo.totalMemory,
|
||||||
|
memoryUtilization: (baseMemoryInfo.usedMemory / baseMemoryInfo.totalMemory * 100),
|
||||||
|
gcCollections: baseMemoryInfo.gcCollections,
|
||||||
|
entityMemory: entityMemoryStats.totalMemory,
|
||||||
|
componentMemory: componentMemoryStats.totalMemory,
|
||||||
|
systemMemory: systemMemoryStats.totalMemory,
|
||||||
|
poolMemory: poolMemoryStats.totalMemory
|
||||||
|
},
|
||||||
|
baseMemory: baseMemoryInfo,
|
||||||
|
entities: entityMemoryStats,
|
||||||
|
components: componentMemoryStats,
|
||||||
|
systems: systemMemoryStats,
|
||||||
|
pools: poolMemoryStats,
|
||||||
|
performance: performanceStats,
|
||||||
|
// 保持向后兼容
|
||||||
|
totalEntities: entityList.buffer.length,
|
||||||
|
totalMemory: entityMemoryStats.totalMemory,
|
||||||
|
detailedArchetypes: entityMemoryStats.archetypes
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集基础内存信息
|
||||||
|
*/
|
||||||
|
private collectBaseMemoryInfo(): any {
|
||||||
|
const memoryInfo: any = {
|
||||||
|
totalMemory: 0,
|
||||||
|
usedMemory: 0,
|
||||||
|
freeMemory: 0,
|
||||||
|
gcCollections: 0,
|
||||||
|
heapInfo: null
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ((performance as any).memory) {
|
||||||
|
const perfMemory = (performance as any).memory;
|
||||||
|
memoryInfo.totalMemory = perfMemory.jsHeapSizeLimit || 512 * 1024 * 1024;
|
||||||
|
memoryInfo.usedMemory = perfMemory.usedJSHeapSize || 0;
|
||||||
|
memoryInfo.freeMemory = memoryInfo.totalMemory - memoryInfo.usedMemory;
|
||||||
|
memoryInfo.heapInfo = {
|
||||||
|
totalJSHeapSize: perfMemory.totalJSHeapSize || 0,
|
||||||
|
usedJSHeapSize: perfMemory.usedJSHeapSize || 0,
|
||||||
|
jsHeapSizeLimit: perfMemory.jsHeapSizeLimit || 0
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
memoryInfo.totalMemory = 512 * 1024 * 1024;
|
||||||
|
memoryInfo.freeMemory = 512 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试获取GC信息
|
||||||
|
if ((performance as any).measureUserAgentSpecificMemory) {
|
||||||
|
// 这是一个实验性API,可能不可用
|
||||||
|
(performance as any).measureUserAgentSpecificMemory().then((result: any) => {
|
||||||
|
memoryInfo.detailedMemory = result;
|
||||||
|
}).catch(() => {
|
||||||
|
// 忽略错误
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 使用默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
return memoryInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集实体内存统计
|
||||||
|
*/
|
||||||
|
private collectEntityMemoryStats(entityList: any): any {
|
||||||
|
const archetypeStats = new Map<string, { count: number; memory: number; entities: any[] }>();
|
||||||
|
const entitySizeDistribution = new Map<string, number>(); // 按大小范围分布
|
||||||
|
let totalMemory = 0;
|
||||||
|
let maxEntityMemory = 0;
|
||||||
|
let minEntityMemory = Number.MAX_VALUE;
|
||||||
|
const largestEntities: any[] = [];
|
||||||
|
|
||||||
|
for (const entity of entityList.buffer) {
|
||||||
|
if (!entity || entity.destroyed) continue;
|
||||||
|
|
||||||
|
// 生成组件签名
|
||||||
|
const componentTypes = entity.components ?
|
||||||
|
entity.components.map((c: any) => c.constructor.name).sort() : [];
|
||||||
|
const signature = componentTypes.length > 0 ? componentTypes.join(',') : 'Empty';
|
||||||
|
|
||||||
|
// 计算实体内存使用
|
||||||
|
const entityMemory = this.entityCollector.estimateEntityMemoryUsage(entity);
|
||||||
|
totalMemory += entityMemory;
|
||||||
|
maxEntityMemory = Math.max(maxEntityMemory, entityMemory);
|
||||||
|
minEntityMemory = Math.min(minEntityMemory, entityMemory);
|
||||||
|
|
||||||
|
// 收集大实体信息
|
||||||
|
largestEntities.push({
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name || `Entity_${entity.id}`,
|
||||||
|
memory: entityMemory,
|
||||||
|
componentCount: componentTypes.length,
|
||||||
|
componentTypes: componentTypes
|
||||||
|
});
|
||||||
|
|
||||||
|
// 内存大小分布统计
|
||||||
|
const sizeCategory = this.getMemorySizeCategory(entityMemory);
|
||||||
|
entitySizeDistribution.set(sizeCategory, (entitySizeDistribution.get(sizeCategory) || 0) + 1);
|
||||||
|
|
||||||
|
// 更新原型统计
|
||||||
|
if (!archetypeStats.has(signature)) {
|
||||||
|
archetypeStats.set(signature, { count: 0, memory: 0, entities: [] });
|
||||||
|
}
|
||||||
|
const stats = archetypeStats.get(signature)!;
|
||||||
|
stats.count++;
|
||||||
|
stats.memory += entityMemory;
|
||||||
|
stats.entities.push({
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name || `Entity_${entity.id}`,
|
||||||
|
memory: entityMemory,
|
||||||
|
componentCount: componentTypes.length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序并限制返回的实体数量
|
||||||
|
largestEntities.sort((a, b) => b.memory - a.memory);
|
||||||
|
|
||||||
|
// 转换原型统计
|
||||||
|
const archetypes = Array.from(archetypeStats.entries()).map(([signature, stats]) => ({
|
||||||
|
signature,
|
||||||
|
count: stats.count,
|
||||||
|
memory: stats.memory,
|
||||||
|
averageMemory: stats.memory / stats.count,
|
||||||
|
percentage: totalMemory > 0 ? (stats.memory / totalMemory * 100) : 0,
|
||||||
|
entities: stats.entities.sort((a, b) => b.memory - a.memory).slice(0, 5) // 只返回前5个最大的
|
||||||
|
})).sort((a, b) => b.memory - a.memory);
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalMemory,
|
||||||
|
entityCount: entityList.buffer.length,
|
||||||
|
averageEntityMemory: totalMemory / entityList.buffer.length,
|
||||||
|
maxEntityMemory,
|
||||||
|
minEntityMemory: minEntityMemory === Number.MAX_VALUE ? 0 : minEntityMemory,
|
||||||
|
archetypes,
|
||||||
|
largestEntities: largestEntities.slice(0, 10),
|
||||||
|
sizeDistribution: Array.from(entitySizeDistribution.entries()).map(([category, count]) => ({
|
||||||
|
category,
|
||||||
|
count,
|
||||||
|
percentage: (count / entityList.buffer.length * 100)
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集组件内存统计
|
||||||
|
*/
|
||||||
|
private collectComponentMemoryStats(entityList: any): any {
|
||||||
|
const componentStats = new Map<string, { count: number; totalMemory: number; instances: any[] }>();
|
||||||
|
let totalComponentMemory = 0;
|
||||||
|
|
||||||
|
for (const entity of entityList.buffer) {
|
||||||
|
if (!entity || entity.destroyed || !entity.components) continue;
|
||||||
|
|
||||||
|
for (const component of entity.components) {
|
||||||
|
const typeName = component.constructor.name;
|
||||||
|
const componentMemory = this.entityCollector.calculateObjectSize(component, ['entity']);
|
||||||
|
totalComponentMemory += componentMemory;
|
||||||
|
|
||||||
|
if (!componentStats.has(typeName)) {
|
||||||
|
componentStats.set(typeName, { count: 0, totalMemory: 0, instances: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = componentStats.get(typeName)!;
|
||||||
|
stats.count++;
|
||||||
|
stats.totalMemory += componentMemory;
|
||||||
|
stats.instances.push({
|
||||||
|
entityId: entity.id,
|
||||||
|
entityName: entity.name || `Entity_${entity.id}`,
|
||||||
|
memory: componentMemory
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentBreakdown = Array.from(componentStats.entries()).map(([typeName, stats]) => ({
|
||||||
|
typeName,
|
||||||
|
instanceCount: stats.count,
|
||||||
|
totalMemory: stats.totalMemory,
|
||||||
|
averageMemory: stats.totalMemory / stats.count,
|
||||||
|
percentage: totalComponentMemory > 0 ? (stats.totalMemory / totalComponentMemory * 100) : 0,
|
||||||
|
largestInstances: stats.instances.sort((a, b) => b.memory - a.memory).slice(0, 3)
|
||||||
|
})).sort((a, b) => b.totalMemory - a.totalMemory);
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalMemory: totalComponentMemory,
|
||||||
|
componentTypes: componentStats.size,
|
||||||
|
totalInstances: Array.from(componentStats.values()).reduce((sum, stats) => sum + stats.count, 0),
|
||||||
|
breakdown: componentBreakdown
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集系统内存统计
|
||||||
|
*/
|
||||||
|
private collectSystemMemoryStats(): any {
|
||||||
|
const scene = Core.scene;
|
||||||
|
let totalSystemMemory = 0;
|
||||||
|
const systemBreakdown: any[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const entityProcessors = (scene as any).entityProcessors;
|
||||||
|
if (entityProcessors && entityProcessors.processors) {
|
||||||
|
for (const system of entityProcessors.processors) {
|
||||||
|
const systemMemory = this.entityCollector.calculateObjectSize(system);
|
||||||
|
totalSystemMemory += systemMemory;
|
||||||
|
|
||||||
|
systemBreakdown.push({
|
||||||
|
name: system.constructor.name,
|
||||||
|
memory: systemMemory,
|
||||||
|
enabled: system.enabled !== false,
|
||||||
|
updateOrder: system.updateOrder || 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略错误
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalMemory: totalSystemMemory,
|
||||||
|
systemCount: systemBreakdown.length,
|
||||||
|
breakdown: systemBreakdown.sort((a, b) => b.memory - a.memory)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集对象池内存统计
|
||||||
|
*/
|
||||||
|
private collectPoolMemoryStats(): any {
|
||||||
|
let totalPoolMemory = 0;
|
||||||
|
const poolBreakdown: any[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 尝试获取组件池统计
|
||||||
|
const { ComponentPoolManager } = require('../../ECS/Core/ComponentPool');
|
||||||
|
const poolManager = ComponentPoolManager.getInstance();
|
||||||
|
const poolStats = poolManager.getPoolStats();
|
||||||
|
|
||||||
|
for (const [typeName, stats] of poolStats.entries()) {
|
||||||
|
const poolData = stats as any; // 类型断言
|
||||||
|
const poolMemory = poolData.maxSize * 32; // 估算每个对象32字节
|
||||||
|
totalPoolMemory += poolMemory;
|
||||||
|
|
||||||
|
poolBreakdown.push({
|
||||||
|
typeName,
|
||||||
|
maxSize: poolData.maxSize,
|
||||||
|
currentSize: poolData.currentSize || 0,
|
||||||
|
estimatedMemory: poolMemory,
|
||||||
|
utilization: poolData.currentSize ? (poolData.currentSize / poolData.maxSize * 100) : 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 如果无法获取池信息,使用默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 尝试获取通用对象池统计
|
||||||
|
const { Pool } = require('../../Utils/Pool');
|
||||||
|
const poolStats = Pool.getStats();
|
||||||
|
|
||||||
|
for (const [typeName, stats] of Object.entries(poolStats)) {
|
||||||
|
const poolData = stats as any; // 类型断言
|
||||||
|
totalPoolMemory += poolData.estimatedMemoryUsage;
|
||||||
|
poolBreakdown.push({
|
||||||
|
typeName: `Pool_${typeName}`,
|
||||||
|
maxSize: poolData.maxSize,
|
||||||
|
currentSize: poolData.size,
|
||||||
|
estimatedMemory: poolData.estimatedMemoryUsage,
|
||||||
|
utilization: poolData.size / poolData.maxSize * 100,
|
||||||
|
hitRate: poolData.hitRate * 100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略错误
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalMemory: totalPoolMemory,
|
||||||
|
poolCount: poolBreakdown.length,
|
||||||
|
breakdown: poolBreakdown.sort((a, b) => b.estimatedMemory - a.estimatedMemory)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集性能统计信息
|
||||||
|
*/
|
||||||
|
private collectPerformanceStats(): any {
|
||||||
|
try {
|
||||||
|
const performanceMonitor = (Core.Instance as any)._performanceMonitor;
|
||||||
|
if (!performanceMonitor) {
|
||||||
|
return { enabled: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = performanceMonitor.getAllSystemStats();
|
||||||
|
const warnings = performanceMonitor.getPerformanceWarnings();
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: performanceMonitor.enabled,
|
||||||
|
systemCount: stats.size,
|
||||||
|
warnings: warnings.slice(0, 10), // 最多10个警告
|
||||||
|
topSystems: Array.from(stats.entries() as any).map((entry: any) => {
|
||||||
|
const [name, stat] = entry;
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
averageTime: stat.averageTime,
|
||||||
|
maxTime: stat.maxTime,
|
||||||
|
samples: stat.executionCount
|
||||||
|
};
|
||||||
|
}).sort((a: any, b: any) => b.averageTime - a.averageTime).slice(0, 5)
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
return { enabled: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内存大小分类
|
||||||
|
*/
|
||||||
|
private getMemorySizeCategory(memoryBytes: number): string {
|
||||||
|
if (memoryBytes < 1024) return '< 1KB';
|
||||||
|
if (memoryBytes < 10 * 1024) return '1-10KB';
|
||||||
|
if (memoryBytes < 100 * 1024) return '10-100KB';
|
||||||
|
if (memoryBytes < 1024 * 1024) return '100KB-1MB';
|
||||||
|
return '> 1MB';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取调试数据
|
||||||
|
*/
|
||||||
|
public getDebugData(): IECSDebugData {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const scene = Core.scene;
|
||||||
|
|
||||||
|
const debugData: IECSDebugData = {
|
||||||
|
timestamp: currentTime,
|
||||||
|
frameworkVersion: '1.0.0', // 可以从package.json读取
|
||||||
|
isRunning: this.isRunning,
|
||||||
|
frameworkLoaded: true,
|
||||||
|
currentScene: scene?.name || 'Unknown'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 根据配置收集各种数据
|
||||||
|
if (this.config.channels.entities) {
|
||||||
|
debugData.entities = this.entityCollector.collectEntityData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.channels.systems) {
|
||||||
|
const performanceMonitor = (Core.Instance as any)._performanceMonitor;
|
||||||
|
debugData.systems = this.systemCollector.collectSystemData(performanceMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.channels.performance) {
|
||||||
|
const performanceMonitor = (Core.Instance as any)._performanceMonitor;
|
||||||
|
debugData.performance = this.performanceCollector.collectPerformanceData(performanceMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.channels.components) {
|
||||||
|
debugData.components = this.componentCollector.collectComponentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.channels.scenes) {
|
||||||
|
debugData.scenes = this.sceneCollector.collectSceneData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return debugData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接WebSocket
|
||||||
|
*/
|
||||||
|
private async connectWebSocket(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.webSocketManager.connect();
|
||||||
|
// console.log('[ECS Debug] 调试管理器已连接到调试服务器');
|
||||||
|
} catch (error) {
|
||||||
|
// console.warn('[ECS Debug] 无法连接到调试服务器:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送调试数据
|
||||||
|
*/
|
||||||
|
private sendDebugData(): void {
|
||||||
|
if (!this.webSocketManager.getConnectionStatus()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const debugData = this.getDebugData();
|
||||||
|
// 包装成调试面板期望的消息格式
|
||||||
|
const message = {
|
||||||
|
type: 'debug_data',
|
||||||
|
data: debugData
|
||||||
|
};
|
||||||
|
this.webSocketManager.send(message);
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('[ECS Debug] 发送调试数据失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
668
src/Utils/Debug/EntityDataCollector.ts
Normal file
668
src/Utils/Debug/EntityDataCollector.ts
Normal file
@@ -0,0 +1,668 @@
|
|||||||
|
import { IEntityDebugData } from '../../types';
|
||||||
|
import { Core } from '../../Core';
|
||||||
|
import { Entity } from '../../ECS/Entity';
|
||||||
|
import { Component } from '../../ECS/Component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体数据收集器
|
||||||
|
*/
|
||||||
|
export class EntityDataCollector {
|
||||||
|
/**
|
||||||
|
* 收集实体数据
|
||||||
|
*/
|
||||||
|
public collectEntityData(): IEntityDebugData {
|
||||||
|
const scene = Core.scene;
|
||||||
|
if (!scene) {
|
||||||
|
return this.getEmptyEntityDebugData();
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityList = (scene as any).entities;
|
||||||
|
if (!entityList) {
|
||||||
|
return this.getEmptyEntityDebugData();
|
||||||
|
}
|
||||||
|
|
||||||
|
let stats;
|
||||||
|
try {
|
||||||
|
stats = entityList.getStats ? entityList.getStats() : this.calculateFallbackEntityStats(entityList);
|
||||||
|
} catch (error) {
|
||||||
|
// console.warn('[ECS Debug] 获取实体统计失败:', error);
|
||||||
|
return {
|
||||||
|
totalEntities: 0,
|
||||||
|
activeEntities: 0,
|
||||||
|
pendingAdd: 0,
|
||||||
|
pendingRemove: 0,
|
||||||
|
entitiesPerArchetype: [],
|
||||||
|
topEntitiesByComponents: [],
|
||||||
|
entityHierarchy: [],
|
||||||
|
entityDetailsMap: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 利用ArchetypeSystem的缓存数据
|
||||||
|
const archetypeData = this.collectArchetypeData(scene);
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalEntities: stats.totalEntities,
|
||||||
|
activeEntities: stats.activeEntities,
|
||||||
|
pendingAdd: stats.pendingAdd || 0,
|
||||||
|
pendingRemove: stats.pendingRemove || 0,
|
||||||
|
entitiesPerArchetype: archetypeData.distribution,
|
||||||
|
topEntitiesByComponents: archetypeData.topEntities,
|
||||||
|
entityHierarchy: this.buildEntityHierarchyTree(entityList),
|
||||||
|
entityDetailsMap: this.buildEntityDetailsMap(entityList)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取空的实体数据
|
||||||
|
*/
|
||||||
|
private getEmptyEntityDebugData(): IEntityDebugData {
|
||||||
|
return {
|
||||||
|
totalEntities: 0,
|
||||||
|
activeEntities: 0,
|
||||||
|
pendingAdd: 0,
|
||||||
|
pendingRemove: 0,
|
||||||
|
entitiesPerArchetype: [],
|
||||||
|
topEntitiesByComponents: [],
|
||||||
|
entityHierarchy: [],
|
||||||
|
entityDetailsMap: {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算实体统计信息
|
||||||
|
*/
|
||||||
|
private calculateFallbackEntityStats(entityList: any): any {
|
||||||
|
const allEntities = entityList.buffer || [];
|
||||||
|
const activeEntities = allEntities.filter((entity: any) =>
|
||||||
|
entity.enabled && !entity._isDestroyed
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalEntities: allEntities.length,
|
||||||
|
activeEntities: activeEntities.length,
|
||||||
|
pendingAdd: 0,
|
||||||
|
pendingRemove: 0,
|
||||||
|
averageComponentsPerEntity: activeEntities.length > 0 ?
|
||||||
|
allEntities.reduce((sum: number, e: any) => sum + (e.components?.length || 0), 0) / activeEntities.length : 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集原型数据
|
||||||
|
*/
|
||||||
|
private collectArchetypeData(scene: any): {
|
||||||
|
distribution: Array<{ signature: string; count: number; memory: number }>;
|
||||||
|
topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>;
|
||||||
|
} {
|
||||||
|
// 利用ArchetypeSystem的缓存数据
|
||||||
|
if (scene && scene.archetypeSystem && typeof scene.archetypeSystem.getAllArchetypes === 'function') {
|
||||||
|
return this.extractArchetypeStatistics(scene.archetypeSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退到传统方法
|
||||||
|
const entityContainer = { entities: scene.entities?.buffer || [] };
|
||||||
|
return {
|
||||||
|
distribution: this.getArchetypeDistribution(entityContainer),
|
||||||
|
topEntities: this.getTopEntitiesByComponents(entityContainer)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取原型统计信息
|
||||||
|
*/
|
||||||
|
private extractArchetypeStatistics(archetypeSystem: any): {
|
||||||
|
distribution: Array<{ signature: string; count: number; memory: number }>;
|
||||||
|
topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }>;
|
||||||
|
} {
|
||||||
|
const archetypes = archetypeSystem.getAllArchetypes();
|
||||||
|
const distribution: Array<{ signature: string; count: number; memory: number }> = [];
|
||||||
|
const topEntities: Array<{ id: string; name: string; componentCount: number; memory: number }> = [];
|
||||||
|
|
||||||
|
archetypes.forEach((archetype: any) => {
|
||||||
|
const signature = archetype.componentTypes?.map((type: any) => type.name).join(',') || 'Unknown';
|
||||||
|
const entityCount = archetype.entities?.length || 0;
|
||||||
|
|
||||||
|
// 计算实际内存使用量
|
||||||
|
let actualMemory = 0;
|
||||||
|
if (archetype.entities && archetype.entities.length > 0) {
|
||||||
|
const sampleSize = Math.min(5, archetype.entities.length);
|
||||||
|
let sampleMemory = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < sampleSize; i++) {
|
||||||
|
sampleMemory += this.estimateEntityMemoryUsage(archetype.entities[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
actualMemory = (sampleMemory / sampleSize) * entityCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
distribution.push({
|
||||||
|
signature,
|
||||||
|
count: entityCount,
|
||||||
|
memory: actualMemory
|
||||||
|
});
|
||||||
|
|
||||||
|
// 收集组件数量最多的实体
|
||||||
|
if (archetype.entities) {
|
||||||
|
archetype.entities.slice(0, 5).forEach((entity: any) => {
|
||||||
|
topEntities.push({
|
||||||
|
id: entity.id.toString(),
|
||||||
|
name: entity.name || `Entity_${entity.id}`,
|
||||||
|
componentCount: entity.components?.length || 0,
|
||||||
|
memory: this.estimateEntityMemoryUsage(entity)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按实体数量排序
|
||||||
|
distribution.sort((a, b) => b.count - a.count);
|
||||||
|
topEntities.sort((a, b) => b.componentCount - a.componentCount);
|
||||||
|
|
||||||
|
return { distribution, topEntities };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算实体内存使用量
|
||||||
|
*/
|
||||||
|
public estimateEntityMemoryUsage(entity: any): number {
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
let totalSize = 0;
|
||||||
|
|
||||||
|
// 计算实体基础大小
|
||||||
|
totalSize += this.calculateObjectSize(entity, ['components', 'children', 'parent']);
|
||||||
|
|
||||||
|
// 计算组件大小
|
||||||
|
if (entity.components) {
|
||||||
|
entity.components.forEach((component: any) => {
|
||||||
|
totalSize += this.calculateObjectSize(component, ['entity']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录计算耗时
|
||||||
|
const executionTime = performance.now() - startTime;
|
||||||
|
if (executionTime > 1) {
|
||||||
|
// console.debug(`[ECS Debug] 实体${entity.id}内存计算耗时: ${executionTime.toFixed(2)}ms`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算对象大小
|
||||||
|
*/
|
||||||
|
public calculateObjectSize(obj: any, excludeKeys: string[] = []): number {
|
||||||
|
if (!obj || typeof obj !== 'object') return 0;
|
||||||
|
|
||||||
|
let size = 0;
|
||||||
|
const visited = new WeakSet();
|
||||||
|
|
||||||
|
const calculate = (item: any): number => {
|
||||||
|
if (!item || typeof item !== 'object' || visited.has(item)) return 0;
|
||||||
|
visited.add(item);
|
||||||
|
|
||||||
|
let itemSize = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const key in item) {
|
||||||
|
if (excludeKeys.includes(key)) continue;
|
||||||
|
|
||||||
|
const value = item[key];
|
||||||
|
itemSize += key.length * 2; // key size
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
itemSize += value.length * 2;
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
itemSize += 8;
|
||||||
|
} else if (typeof value === 'boolean') {
|
||||||
|
itemSize += 4;
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
itemSize += calculate(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略无法访问的属性
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
return calculate(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建实体层次结构树
|
||||||
|
*/
|
||||||
|
private buildEntityHierarchyTree(entityList: { buffer?: Entity[] }): Array<{
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
active: boolean;
|
||||||
|
enabled: boolean;
|
||||||
|
activeInHierarchy: boolean;
|
||||||
|
componentCount: number;
|
||||||
|
componentTypes: string[];
|
||||||
|
parentId: number | null;
|
||||||
|
children: any[];
|
||||||
|
depth: number;
|
||||||
|
tag: number;
|
||||||
|
updateOrder: number;
|
||||||
|
}> {
|
||||||
|
if (!entityList?.buffer) return [];
|
||||||
|
|
||||||
|
const rootEntities: any[] = [];
|
||||||
|
|
||||||
|
// 直接遍历实体缓冲区,只收集根实体
|
||||||
|
entityList.buffer.forEach((entity: Entity) => {
|
||||||
|
if (!entity.parent) {
|
||||||
|
const hierarchyNode = this.buildEntityHierarchyNode(entity);
|
||||||
|
rootEntities.push(hierarchyNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按实体名称排序,提供一致的显示顺序
|
||||||
|
rootEntities.sort((nodeA, nodeB) => {
|
||||||
|
if (nodeA.name < nodeB.name) return -1;
|
||||||
|
if (nodeA.name > nodeB.name) return 1;
|
||||||
|
return nodeA.id - nodeB.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return rootEntities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建实体层次结构节点
|
||||||
|
*/
|
||||||
|
private buildEntityHierarchyNode(entity: Entity): any {
|
||||||
|
let node = {
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name || `Entity_${entity.id}`,
|
||||||
|
active: entity.active !== false,
|
||||||
|
enabled: entity.enabled !== false,
|
||||||
|
activeInHierarchy: entity.activeInHierarchy !== false,
|
||||||
|
componentCount: entity.components.length,
|
||||||
|
componentTypes: entity.components.map((component: Component) => component.constructor.name),
|
||||||
|
parentId: entity.parent?.id || null,
|
||||||
|
children: [] as any[],
|
||||||
|
depth: entity.getDepth ? entity.getDepth() : 0,
|
||||||
|
tag: entity.tag || 0,
|
||||||
|
updateOrder: entity.updateOrder || 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// 递归构建子实体节点
|
||||||
|
if (entity.children && entity.children.length > 0) {
|
||||||
|
node.children = entity.children.map((child: Entity) => this.buildEntityHierarchyNode(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先使用Entity的getDebugInfo方法
|
||||||
|
if (typeof entity.getDebugInfo === 'function') {
|
||||||
|
const debugInfo = entity.getDebugInfo();
|
||||||
|
node = {
|
||||||
|
...node,
|
||||||
|
...debugInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集所有组件详细属性信息
|
||||||
|
if (entity.components && entity.components.length > 0) {
|
||||||
|
(node as any).componentDetails = this.extractComponentDetails(entity.components);
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建实体详情映射
|
||||||
|
*/
|
||||||
|
private buildEntityDetailsMap(entityList: { buffer?: Entity[] }): Record<number, any> {
|
||||||
|
if (!entityList?.buffer) return {};
|
||||||
|
|
||||||
|
const entityDetailsMap: Record<number, any> = {};
|
||||||
|
|
||||||
|
// 批量处理实体,减少函数调用开销
|
||||||
|
const entities = entityList.buffer;
|
||||||
|
const batchSize = 50;
|
||||||
|
|
||||||
|
for (let i = 0; i < entities.length; i += batchSize) {
|
||||||
|
const batch = entities.slice(i, i + batchSize);
|
||||||
|
|
||||||
|
batch.forEach((entity: Entity) => {
|
||||||
|
// 优先使用Entity的getDebugInfo方法
|
||||||
|
const baseDebugInfo = entity.getDebugInfo ?
|
||||||
|
entity.getDebugInfo() :
|
||||||
|
this.buildFallbackEntityInfo(entity);
|
||||||
|
|
||||||
|
// 利用组件缓存统计信息
|
||||||
|
const componentCacheStats = (entity as any).getComponentCacheStats ?
|
||||||
|
(entity as any).getComponentCacheStats() : null;
|
||||||
|
|
||||||
|
const componentDetails = this.extractComponentDetails(entity.components);
|
||||||
|
|
||||||
|
// 构建完整的实体详情对象
|
||||||
|
entityDetailsMap[entity.id] = {
|
||||||
|
...baseDebugInfo,
|
||||||
|
parentName: entity.parent?.name || null,
|
||||||
|
components: componentDetails,
|
||||||
|
componentTypes: baseDebugInfo.componentTypes ||
|
||||||
|
componentDetails.map((comp) => comp.typeName),
|
||||||
|
// 添加缓存性能信息
|
||||||
|
cachePerformance: componentCacheStats ? {
|
||||||
|
hitRate: componentCacheStats.cacheStats.hitRate,
|
||||||
|
size: componentCacheStats.cacheStats.size,
|
||||||
|
maxSize: componentCacheStats.cacheStats.maxSize
|
||||||
|
} : null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityDetailsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建实体基础信息
|
||||||
|
*/
|
||||||
|
private buildFallbackEntityInfo(entity: Entity): any {
|
||||||
|
return {
|
||||||
|
name: entity.name || `Entity_${entity.id}`,
|
||||||
|
id: entity.id,
|
||||||
|
enabled: entity.enabled !== false,
|
||||||
|
active: entity.active !== false,
|
||||||
|
activeInHierarchy: entity.activeInHierarchy !== false,
|
||||||
|
destroyed: entity.isDestroyed || false,
|
||||||
|
componentCount: entity.components.length,
|
||||||
|
componentTypes: entity.components.map((component: Component) => component.constructor.name),
|
||||||
|
componentMask: entity.componentMask?.toString() || '0',
|
||||||
|
parentId: entity.parent?.id || null,
|
||||||
|
childCount: entity.children?.length || 0,
|
||||||
|
childIds: entity.children.map((child: Entity) => child.id) || [],
|
||||||
|
depth: entity.getDepth ? entity.getDepth() : 0,
|
||||||
|
tag: entity.tag || 0,
|
||||||
|
updateOrder: entity.updateOrder || 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取组件详细信息
|
||||||
|
*/
|
||||||
|
private extractComponentDetails(components: Component[]): Array<{
|
||||||
|
typeName: string;
|
||||||
|
properties: Record<string, any>;
|
||||||
|
}> {
|
||||||
|
return components.map((component: Component) => {
|
||||||
|
// 获取组件类型名称,优先使用constructor.name
|
||||||
|
let typeName = component.constructor.name;
|
||||||
|
|
||||||
|
// 如果constructor.name为空或者是通用名称,尝试其他方法
|
||||||
|
if (!typeName || typeName === 'Object' || typeName === 'Function') {
|
||||||
|
// 尝试从类型管理器获取
|
||||||
|
try {
|
||||||
|
const { ComponentTypeManager } = require('../../ECS/Utils/ComponentTypeManager');
|
||||||
|
const typeManager = ComponentTypeManager.instance;
|
||||||
|
const componentType = component.constructor as any;
|
||||||
|
const typeId = typeManager.getTypeId(componentType);
|
||||||
|
typeName = typeManager.getTypeName(typeId);
|
||||||
|
} catch (error) {
|
||||||
|
// 如果类型管理器不可用,使用默认名称
|
||||||
|
typeName = 'UnknownComponent';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentDetail = {
|
||||||
|
typeName: typeName,
|
||||||
|
properties: {} as Record<string, any>
|
||||||
|
};
|
||||||
|
|
||||||
|
// 安全地提取组件属性
|
||||||
|
try {
|
||||||
|
const propertyKeys = Object.keys(component);
|
||||||
|
propertyKeys.forEach(propertyKey => {
|
||||||
|
// 跳过私有属性和实体引用,避免循环引用
|
||||||
|
if (!propertyKey.startsWith('_') && propertyKey !== 'entity') {
|
||||||
|
const propertyValue = (component as any)[propertyKey];
|
||||||
|
if (propertyValue !== undefined && propertyValue !== null) {
|
||||||
|
componentDetail.properties[propertyKey] = this.formatPropertyValue(propertyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
componentDetail.properties['_extractionError'] = '属性提取失败';
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentDetail;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化属性值
|
||||||
|
*/
|
||||||
|
private formatPropertyValue(value: any, depth: number = 0): any {
|
||||||
|
// 防止无限递归,限制最大深度
|
||||||
|
if (depth > 5) {
|
||||||
|
return value?.toString() || 'null';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
// 对于数组,总是返回完整数组,让前端决定如何显示
|
||||||
|
return value.map(item => this.formatPropertyValue(item, depth + 1));
|
||||||
|
} else {
|
||||||
|
// 通用对象处理:提取所有可枚举属性,不限制数量
|
||||||
|
try {
|
||||||
|
const keys = Object.keys(value);
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: any = {};
|
||||||
|
keys.forEach(key => {
|
||||||
|
const propValue = value[key];
|
||||||
|
// 避免循环引用和函数属性
|
||||||
|
if (propValue !== value && typeof propValue !== 'function') {
|
||||||
|
try {
|
||||||
|
result[key] = this.formatPropertyValue(propValue, depth + 1);
|
||||||
|
} catch (error) {
|
||||||
|
// 如果属性访问失败,记录错误信息
|
||||||
|
result[key] = `[访问失败: ${error instanceof Error ? error.message : String(error)}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
return `[对象解析失败: ${error instanceof Error ? error.message : String(error)}]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Archetype分布
|
||||||
|
*/
|
||||||
|
private getArchetypeDistribution(entityContainer: any): Array<{ signature: string; count: number; memory: number }> {
|
||||||
|
const distribution = new Map<string, { count: number; memory: number }>();
|
||||||
|
|
||||||
|
if (entityContainer && entityContainer.entities) {
|
||||||
|
entityContainer.entities.forEach((entity: any) => {
|
||||||
|
const signature = entity.componentMask?.toString() || '0';
|
||||||
|
const existing = distribution.get(signature);
|
||||||
|
const memory = this.estimateEntityMemoryUsage(entity);
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
existing.count++;
|
||||||
|
existing.memory += memory;
|
||||||
|
} else {
|
||||||
|
distribution.set(signature, { count: 1, memory });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(distribution.entries())
|
||||||
|
.map(([signature, data]) => ({ signature, ...data }))
|
||||||
|
.sort((a, b) => b.count - a.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取组件数量最多的实体
|
||||||
|
*/
|
||||||
|
private getTopEntitiesByComponents(entityContainer: any): Array<{ id: string; name: string; componentCount: number; memory: number }> {
|
||||||
|
if (!entityContainer || !entityContainer.entities) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityContainer.entities
|
||||||
|
.map((entity: any) => ({
|
||||||
|
id: entity.id.toString(),
|
||||||
|
name: entity.name || `Entity_${entity.id}`,
|
||||||
|
componentCount: entity.components?.length || 0,
|
||||||
|
memory: this.estimateEntityMemoryUsage(entity)
|
||||||
|
}))
|
||||||
|
.sort((a: any, b: any) => b.componentCount - a.componentCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取实体详细信息
|
||||||
|
*/
|
||||||
|
private extractEntityDetails(entity: any): any {
|
||||||
|
const components = entity.components || [];
|
||||||
|
const componentDetails = this.extractComponentDetails(components);
|
||||||
|
|
||||||
|
// 格式化组件掩码为更可读的形式
|
||||||
|
const componentMask = entity.componentMask || entity._componentMask || 0;
|
||||||
|
const componentMaskBinary = componentMask.toString(2).padStart(32, '0');
|
||||||
|
const componentMaskHex = '0x' + componentMask.toString(16).toUpperCase().padStart(8, '0');
|
||||||
|
|
||||||
|
// 生成组件掩码的可读描述
|
||||||
|
const maskDescription = this.generateComponentMaskDescription(componentMask, components);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: entity.id,
|
||||||
|
name: entity.name || `Entity_${entity.id}`,
|
||||||
|
active: entity.active !== false,
|
||||||
|
enabled: entity.enabled !== false,
|
||||||
|
activeInHierarchy: entity.activeInHierarchy !== false,
|
||||||
|
updateOrder: entity.updateOrder || 0,
|
||||||
|
tag: entity.tag || '未分配',
|
||||||
|
depth: entity.depth || 0,
|
||||||
|
componentMask: componentMask,
|
||||||
|
componentMaskHex: componentMaskHex,
|
||||||
|
componentMaskBinary: componentMaskBinary,
|
||||||
|
componentMaskDescription: maskDescription,
|
||||||
|
componentCount: components.length,
|
||||||
|
components: componentDetails,
|
||||||
|
|
||||||
|
// 层次关系信息
|
||||||
|
parentId: entity.parent?.id || null,
|
||||||
|
parentName: entity.parent?.name || null,
|
||||||
|
childCount: entity.children?.length || 0,
|
||||||
|
childIds: entity.children?.map((child: any) => child.id).slice(0, 10) || [], // 最多显示10个子实体ID
|
||||||
|
|
||||||
|
// 内存信息
|
||||||
|
estimatedMemory: this.estimateEntityMemoryUsage(entity),
|
||||||
|
|
||||||
|
// 额外的调试信息
|
||||||
|
destroyed: entity.destroyed || false,
|
||||||
|
scene: entity.scene?.name || '未知场景',
|
||||||
|
transform: this.extractTransformInfo(entity),
|
||||||
|
|
||||||
|
// 实体状态描述
|
||||||
|
statusDescription: this.generateEntityStatusDescription(entity)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成组件掩码的可读描述
|
||||||
|
*/
|
||||||
|
private generateComponentMaskDescription(mask: number, components: any[]): string {
|
||||||
|
if (mask === 0) {
|
||||||
|
return '无组件';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (components.length === 0) {
|
||||||
|
return `掩码值: ${mask} (组件信息不可用)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentNames = components.map(c => c.constructor.name);
|
||||||
|
if (componentNames.length <= 3) {
|
||||||
|
return `包含: ${componentNames.join(', ')}`;
|
||||||
|
} else {
|
||||||
|
return `包含: ${componentNames.slice(0, 3).join(', ')} 等${componentNames.length}个组件`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成实体状态描述
|
||||||
|
*/
|
||||||
|
private generateEntityStatusDescription(entity: any): string {
|
||||||
|
const statuses = [];
|
||||||
|
|
||||||
|
if (entity.destroyed) {
|
||||||
|
statuses.push('已销毁');
|
||||||
|
} else {
|
||||||
|
if (entity.active === false) {
|
||||||
|
statuses.push('非活跃');
|
||||||
|
}
|
||||||
|
if (entity.enabled === false) {
|
||||||
|
statuses.push('已禁用');
|
||||||
|
}
|
||||||
|
if (entity.activeInHierarchy === false) {
|
||||||
|
statuses.push('层次非活跃');
|
||||||
|
}
|
||||||
|
if (statuses.length === 0) {
|
||||||
|
statuses.push('正常运行');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statuses.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取变换信息
|
||||||
|
*/
|
||||||
|
private extractTransformInfo(entity: any): any {
|
||||||
|
try {
|
||||||
|
// 尝试获取Transform组件或position/rotation/scale信息
|
||||||
|
const transform = entity.transform || entity.getComponent?.('Transform') || null;
|
||||||
|
|
||||||
|
if (transform) {
|
||||||
|
return {
|
||||||
|
position: this.formatVector3(transform.position || transform.localPosition),
|
||||||
|
rotation: this.formatVector3(transform.rotation || transform.localRotation),
|
||||||
|
scale: this.formatVector3(transform.scale || transform.localScale),
|
||||||
|
worldPosition: this.formatVector3(transform.worldPosition),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有Transform组件,检查是否有直接的位置信息
|
||||||
|
if (entity.position || entity.x !== undefined) {
|
||||||
|
return {
|
||||||
|
position: this.formatVector3({
|
||||||
|
x: entity.x || entity.position?.x || 0,
|
||||||
|
y: entity.y || entity.position?.y || 0,
|
||||||
|
z: entity.z || entity.position?.z || 0
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化Vector3对象
|
||||||
|
*/
|
||||||
|
private formatVector3(vector: any): string | null {
|
||||||
|
if (!vector) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const x = typeof vector.x === 'number' ? vector.x.toFixed(2) : '0.00';
|
||||||
|
const y = typeof vector.y === 'number' ? vector.y.toFixed(2) : '0.00';
|
||||||
|
const z = typeof vector.z === 'number' ? vector.z.toFixed(2) : '0.00';
|
||||||
|
|
||||||
|
return `(${x}, ${y}, ${z})`;
|
||||||
|
} catch (error) {
|
||||||
|
return vector.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
234
src/Utils/Debug/PerformanceDataCollector.ts
Normal file
234
src/Utils/Debug/PerformanceDataCollector.ts
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
import { IPerformanceDebugData } from '../../types';
|
||||||
|
import { Time } from '../Time';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性能数据收集器
|
||||||
|
*/
|
||||||
|
export class PerformanceDataCollector {
|
||||||
|
private frameTimeHistory: number[] = [];
|
||||||
|
private maxHistoryLength: number = 60;
|
||||||
|
private lastGCCount: number = 0;
|
||||||
|
private gcCollections: number = 0;
|
||||||
|
private lastMemoryCheck: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集性能数据
|
||||||
|
*/
|
||||||
|
public collectPerformanceData(performanceMonitor: any): IPerformanceDebugData {
|
||||||
|
const frameTimeSeconds = Time.deltaTime;
|
||||||
|
const engineFrameTimeMs = frameTimeSeconds * 1000;
|
||||||
|
const currentFps = frameTimeSeconds > 0 ? Math.round(1 / frameTimeSeconds) : 0;
|
||||||
|
|
||||||
|
const ecsPerformanceData = this.getECSPerformanceData(performanceMonitor);
|
||||||
|
const ecsExecutionTimeMs = ecsPerformanceData.totalExecutionTime;
|
||||||
|
const ecsPercentage = engineFrameTimeMs > 0 ? (ecsExecutionTimeMs / engineFrameTimeMs * 100) : 0;
|
||||||
|
|
||||||
|
let memoryUsage = 0;
|
||||||
|
if ((performance as any).memory) {
|
||||||
|
memoryUsage = (performance as any).memory.usedJSHeapSize / 1024 / 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新ECS执行时间历史记录
|
||||||
|
this.frameTimeHistory.push(ecsExecutionTimeMs);
|
||||||
|
if (this.frameTimeHistory.length > this.maxHistoryLength) {
|
||||||
|
this.frameTimeHistory.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算ECS执行时间统计
|
||||||
|
const history = this.frameTimeHistory.filter(t => t >= 0);
|
||||||
|
const averageECSTime = history.length > 0 ? history.reduce((a, b) => a + b, 0) / history.length : ecsExecutionTimeMs;
|
||||||
|
const minECSTime = history.length > 0 ? Math.min(...history) : ecsExecutionTimeMs;
|
||||||
|
const maxECSTime = history.length > 0 ? Math.max(...history) : ecsExecutionTimeMs;
|
||||||
|
|
||||||
|
return {
|
||||||
|
frameTime: ecsExecutionTimeMs,
|
||||||
|
engineFrameTime: engineFrameTimeMs,
|
||||||
|
ecsPercentage: ecsPercentage,
|
||||||
|
memoryUsage: memoryUsage,
|
||||||
|
fps: currentFps,
|
||||||
|
averageFrameTime: averageECSTime,
|
||||||
|
minFrameTime: minECSTime,
|
||||||
|
maxFrameTime: maxECSTime,
|
||||||
|
frameTimeHistory: [...this.frameTimeHistory],
|
||||||
|
systemPerformance: this.getSystemPerformance(performanceMonitor),
|
||||||
|
systemBreakdown: ecsPerformanceData.systemBreakdown,
|
||||||
|
memoryDetails: this.getMemoryDetails()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取ECS框架整体性能数据
|
||||||
|
*/
|
||||||
|
private getECSPerformanceData(performanceMonitor: any): { totalExecutionTime: number; systemBreakdown: Array<any> } {
|
||||||
|
// 检查性能监视器是否存在
|
||||||
|
if (!performanceMonitor) {
|
||||||
|
// 尝试从Core实例获取性能监视器
|
||||||
|
try {
|
||||||
|
const { Core } = require('../../Core');
|
||||||
|
const coreInstance = Core.Instance;
|
||||||
|
if (coreInstance && (coreInstance as any)._performanceMonitor) {
|
||||||
|
performanceMonitor = (coreInstance as any)._performanceMonitor;
|
||||||
|
} else {
|
||||||
|
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!performanceMonitor.enabled) {
|
||||||
|
// 尝试启用性能监视器
|
||||||
|
try {
|
||||||
|
performanceMonitor.enabled = true;
|
||||||
|
} catch (error) {
|
||||||
|
// 如果无法启用,返回默认值
|
||||||
|
}
|
||||||
|
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let totalTime = 0;
|
||||||
|
const systemBreakdown = [];
|
||||||
|
|
||||||
|
const stats = performanceMonitor.getAllSystemStats();
|
||||||
|
|
||||||
|
if (stats.size === 0) {
|
||||||
|
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算各系统的执行时间
|
||||||
|
for (const [systemName, stat] of stats.entries()) {
|
||||||
|
// 使用最近的执行时间而不是平均时间,这样更能反映当前状态
|
||||||
|
const systemTime = stat.recentTimes && stat.recentTimes.length > 0 ?
|
||||||
|
stat.recentTimes[stat.recentTimes.length - 1] :
|
||||||
|
(stat.averageTime || 0);
|
||||||
|
|
||||||
|
totalTime += systemTime;
|
||||||
|
systemBreakdown.push({
|
||||||
|
systemName: systemName,
|
||||||
|
executionTime: systemTime,
|
||||||
|
percentage: 0 // 后面计算
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算各系统占ECS总时间的百分比
|
||||||
|
systemBreakdown.forEach(system => {
|
||||||
|
system.percentage = totalTime > 0 ? (system.executionTime / totalTime * 100) : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按执行时间排序
|
||||||
|
systemBreakdown.sort((a, b) => b.executionTime - a.executionTime);
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalExecutionTime: totalTime,
|
||||||
|
systemBreakdown: systemBreakdown
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统性能数据
|
||||||
|
*/
|
||||||
|
private getSystemPerformance(performanceMonitor: any): Array<any> {
|
||||||
|
if (!performanceMonitor) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = performanceMonitor.getAllSystemStats();
|
||||||
|
const systemData = performanceMonitor.getAllSystemData();
|
||||||
|
|
||||||
|
return Array.from(stats.entries() as Iterable<[string, any]>).map(([systemName, stat]) => {
|
||||||
|
const data = systemData.get(systemName);
|
||||||
|
return {
|
||||||
|
systemName: systemName,
|
||||||
|
averageTime: stat.averageTime || 0,
|
||||||
|
maxTime: stat.maxTime || 0,
|
||||||
|
minTime: stat.minTime === Number.MAX_VALUE ? 0 : (stat.minTime || 0),
|
||||||
|
samples: stat.executionCount || 0,
|
||||||
|
percentage: 0,
|
||||||
|
entityCount: data?.entityCount || 0,
|
||||||
|
lastExecutionTime: data?.executionTime || 0
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内存详情
|
||||||
|
*/
|
||||||
|
private getMemoryDetails(): any {
|
||||||
|
const memoryInfo: any = {
|
||||||
|
entities: 0,
|
||||||
|
components: 0,
|
||||||
|
systems: 0,
|
||||||
|
pooled: 0,
|
||||||
|
totalMemory: 0,
|
||||||
|
usedMemory: 0,
|
||||||
|
freeMemory: 0,
|
||||||
|
gcCollections: this.updateGCCount()
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ((performance as any).memory) {
|
||||||
|
const perfMemory = (performance as any).memory;
|
||||||
|
memoryInfo.totalMemory = perfMemory.jsHeapSizeLimit || 512 * 1024 * 1024;
|
||||||
|
memoryInfo.usedMemory = perfMemory.usedJSHeapSize || 0;
|
||||||
|
memoryInfo.freeMemory = memoryInfo.totalMemory - memoryInfo.usedMemory;
|
||||||
|
|
||||||
|
// 检测GC:如果使用的内存突然大幅减少,可能发生了GC
|
||||||
|
if (this.lastMemoryCheck > 0) {
|
||||||
|
const memoryDrop = this.lastMemoryCheck - memoryInfo.usedMemory;
|
||||||
|
if (memoryDrop > 1024 * 1024) { // 内存减少超过1MB
|
||||||
|
this.gcCollections++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.lastMemoryCheck = memoryInfo.usedMemory;
|
||||||
|
} else {
|
||||||
|
memoryInfo.totalMemory = 512 * 1024 * 1024;
|
||||||
|
memoryInfo.freeMemory = 512 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
totalMemory: 0,
|
||||||
|
usedMemory: 0,
|
||||||
|
freeMemory: 0,
|
||||||
|
entityMemory: 0,
|
||||||
|
componentMemory: 0,
|
||||||
|
systemMemory: 0,
|
||||||
|
pooledMemory: 0,
|
||||||
|
gcCollections: this.gcCollections
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return memoryInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新GC计数
|
||||||
|
*/
|
||||||
|
private updateGCCount(): number {
|
||||||
|
try {
|
||||||
|
// 尝试使用PerformanceObserver来检测GC
|
||||||
|
if (typeof PerformanceObserver !== 'undefined') {
|
||||||
|
// 这是一个简化的GC检测方法
|
||||||
|
// 实际的GC检测需要更复杂的逻辑
|
||||||
|
return this.gcCollections;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有其他GC检测API,可以在这里添加
|
||||||
|
if ((performance as any).measureUserAgentSpecificMemory) {
|
||||||
|
// 实验性API,可能不可用
|
||||||
|
return this.gcCollections;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.gcCollections;
|
||||||
|
} catch (error) {
|
||||||
|
return this.gcCollections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/Utils/Debug/SceneDataCollector.ts
Normal file
50
src/Utils/Debug/SceneDataCollector.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { ISceneDebugData } from '../../types';
|
||||||
|
import { Core } from '../../Core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景数据收集器
|
||||||
|
*/
|
||||||
|
export class SceneDataCollector {
|
||||||
|
private sceneStartTime: number = Date.now();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集场景数据
|
||||||
|
*/
|
||||||
|
public collectSceneData(): ISceneDebugData {
|
||||||
|
const scene = Core.scene;
|
||||||
|
if (!scene) {
|
||||||
|
return {
|
||||||
|
currentSceneName: 'No Scene',
|
||||||
|
isInitialized: false,
|
||||||
|
sceneRunTime: 0,
|
||||||
|
sceneEntityCount: 0,
|
||||||
|
sceneSystemCount: 0,
|
||||||
|
sceneMemory: 0,
|
||||||
|
sceneUptime: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const runTime = (currentTime - this.sceneStartTime) / 1000;
|
||||||
|
|
||||||
|
const entityList = (scene as any).entities;
|
||||||
|
const entityProcessors = (scene as any).entityProcessors;
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentSceneName: (scene as any).name || 'Unnamed Scene',
|
||||||
|
isInitialized: (scene as any)._didSceneBegin || false,
|
||||||
|
sceneRunTime: runTime,
|
||||||
|
sceneEntityCount: entityList?.buffer?.length || 0,
|
||||||
|
sceneSystemCount: entityProcessors?.processors?.length || 0,
|
||||||
|
sceneMemory: 0, // TODO: 计算实际场景内存
|
||||||
|
sceneUptime: runTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置场景开始时间
|
||||||
|
*/
|
||||||
|
public setSceneStartTime(time: number): void {
|
||||||
|
this.sceneStartTime = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/Utils/Debug/SystemDataCollector.ts
Normal file
65
src/Utils/Debug/SystemDataCollector.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { ISystemDebugData } from '../../types';
|
||||||
|
import { Core } from '../../Core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统数据收集器
|
||||||
|
*/
|
||||||
|
export class SystemDataCollector {
|
||||||
|
/**
|
||||||
|
* 收集系统数据
|
||||||
|
*/
|
||||||
|
public collectSystemData(performanceMonitor: any): ISystemDebugData {
|
||||||
|
const scene = Core.scene;
|
||||||
|
if (!scene) {
|
||||||
|
return {
|
||||||
|
totalSystems: 0,
|
||||||
|
systemsInfo: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityProcessors = (scene as any).entityProcessors;
|
||||||
|
if (!entityProcessors) {
|
||||||
|
return {
|
||||||
|
totalSystems: 0,
|
||||||
|
systemsInfo: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const systems = entityProcessors.processors || [];
|
||||||
|
|
||||||
|
// 获取性能监控数据
|
||||||
|
let systemStats: Map<string, any> = new Map();
|
||||||
|
let systemData: Map<string, any> = new Map();
|
||||||
|
|
||||||
|
if (performanceMonitor) {
|
||||||
|
try {
|
||||||
|
systemStats = performanceMonitor.getAllSystemStats();
|
||||||
|
systemData = performanceMonitor.getAllSystemData();
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略错误,使用空的Map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalSystems: systems.length,
|
||||||
|
systemsInfo: systems.map((system: any) => {
|
||||||
|
const systemName = system.systemName || system.constructor.name;
|
||||||
|
const stats = systemStats.get(systemName);
|
||||||
|
const data = systemData.get(systemName);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: systemName,
|
||||||
|
type: system.constructor.name,
|
||||||
|
entityCount: system.entities?.length || 0,
|
||||||
|
executionTime: stats?.averageTime || data?.executionTime || 0,
|
||||||
|
minExecutionTime: stats?.minTime === Number.MAX_VALUE ? 0 : (stats?.minTime || 0),
|
||||||
|
maxExecutionTime: stats?.maxTime || 0,
|
||||||
|
executionTimeHistory: stats?.recentTimes || [],
|
||||||
|
updateOrder: system.updateOrder || 0,
|
||||||
|
enabled: system.enabled !== false,
|
||||||
|
lastUpdateTime: data?.lastUpdateTime || 0
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
177
src/Utils/Debug/WebSocketManager.ts
Normal file
177
src/Utils/Debug/WebSocketManager.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/**
|
||||||
|
* WebSocket连接管理器
|
||||||
|
*/
|
||||||
|
export class WebSocketManager {
|
||||||
|
private ws?: WebSocket;
|
||||||
|
private isConnected: boolean = false;
|
||||||
|
private reconnectAttempts: number = 0;
|
||||||
|
private maxReconnectAttempts: number = 5;
|
||||||
|
private reconnectInterval: number = 2000;
|
||||||
|
private url: string;
|
||||||
|
private autoReconnect: boolean;
|
||||||
|
private reconnectTimer?: NodeJS.Timeout;
|
||||||
|
private onOpen?: (event: Event) => void;
|
||||||
|
private onClose?: (event: CloseEvent) => void;
|
||||||
|
private onError?: (error: Event | any) => void;
|
||||||
|
private messageHandler?: (message: any) => void;
|
||||||
|
|
||||||
|
constructor(url: string, autoReconnect: boolean = true) {
|
||||||
|
this.url = url;
|
||||||
|
this.autoReconnect = autoReconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置消息处理回调
|
||||||
|
*/
|
||||||
|
public setMessageHandler(handler: (message: any) => void): void {
|
||||||
|
this.messageHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接WebSocket
|
||||||
|
*/
|
||||||
|
public connect(): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
this.ws = new WebSocket(this.url);
|
||||||
|
|
||||||
|
this.ws.onopen = (event) => {
|
||||||
|
this.handleOpen(event);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onclose = (event) => {
|
||||||
|
this.handleClose(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onerror = (error) => {
|
||||||
|
this.handleError(error);
|
||||||
|
reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.ws.onmessage = (event) => {
|
||||||
|
this.handleMessage(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.handleConnectionFailure(error);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开连接
|
||||||
|
*/
|
||||||
|
public disconnect(): void {
|
||||||
|
if (this.ws) {
|
||||||
|
this.autoReconnect = false; // 主动断开时不自动重连
|
||||||
|
this.ws.close();
|
||||||
|
this.ws = undefined;
|
||||||
|
}
|
||||||
|
this.isConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送数据
|
||||||
|
*/
|
||||||
|
public send(data: any): void {
|
||||||
|
if (!this.isConnected || !this.ws) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const message = typeof data === 'string' ? data : JSON.stringify(data);
|
||||||
|
this.ws.send(message);
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接状态
|
||||||
|
*/
|
||||||
|
public getConnectionStatus(): boolean {
|
||||||
|
return this.isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置最大重连次数
|
||||||
|
*/
|
||||||
|
public setMaxReconnectAttempts(attempts: number): void {
|
||||||
|
this.maxReconnectAttempts = attempts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置重连间隔
|
||||||
|
*/
|
||||||
|
public setReconnectInterval(interval: number): void {
|
||||||
|
this.reconnectInterval = interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计划重连
|
||||||
|
*/
|
||||||
|
private scheduleReconnect(): void {
|
||||||
|
if (this.reconnectTimer) {
|
||||||
|
clearTimeout(this.reconnectTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
|
||||||
|
this.reconnectTimer = setTimeout(() => {
|
||||||
|
this.connect().catch(error => {
|
||||||
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
|
this.scheduleReconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理接收到的消息
|
||||||
|
*/
|
||||||
|
private handleMessage(event: MessageEvent): void {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
// 调用消息处理回调
|
||||||
|
if (this.messageHandler) {
|
||||||
|
this.messageHandler(message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleOpen(event: Event): void {
|
||||||
|
this.isConnected = true;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
|
||||||
|
if (this.onOpen) {
|
||||||
|
this.onOpen(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleClose(event: CloseEvent): void {
|
||||||
|
this.isConnected = false;
|
||||||
|
|
||||||
|
if (this.onClose) {
|
||||||
|
this.onClose(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
|
this.scheduleReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(error: Event): void {
|
||||||
|
if (this.onError) {
|
||||||
|
this.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleConnectionFailure(error: any): void {
|
||||||
|
if (this.onError) {
|
||||||
|
this.onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/Utils/Debug/index.ts
Normal file
7
src/Utils/Debug/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export { EntityDataCollector } from './EntityDataCollector';
|
||||||
|
export { SystemDataCollector } from './SystemDataCollector';
|
||||||
|
export { PerformanceDataCollector } from './PerformanceDataCollector';
|
||||||
|
export { ComponentDataCollector } from './ComponentDataCollector';
|
||||||
|
export { SceneDataCollector } from './SceneDataCollector';
|
||||||
|
export { WebSocketManager } from './WebSocketManager';
|
||||||
|
export { DebugManager } from './DebugManager';
|
||||||
@@ -1,971 +0,0 @@
|
|||||||
import {
|
|
||||||
IECSDebugConfig,
|
|
||||||
IECSDebugData,
|
|
||||||
IEntityDebugData,
|
|
||||||
ISystemDebugData,
|
|
||||||
IPerformanceDebugData,
|
|
||||||
IComponentDebugData,
|
|
||||||
ISceneDebugData
|
|
||||||
} from '../types';
|
|
||||||
import { Core } from '../Core';
|
|
||||||
import { Time } from './Time';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ECS调试报告器 - WebSocket模式
|
|
||||||
*
|
|
||||||
* 负责收集ECS框架的运行时调试数据并通过WebSocket发送到调试服务器
|
|
||||||
*/
|
|
||||||
export class DebugReporter {
|
|
||||||
private config: IECSDebugConfig;
|
|
||||||
private core: Core;
|
|
||||||
private timer?: any;
|
|
||||||
private ws?: WebSocket;
|
|
||||||
private lastFrameTime: number = 0;
|
|
||||||
private frameCount: number = 0;
|
|
||||||
private lastFpsTime: number = 0;
|
|
||||||
private fps: number = 0;
|
|
||||||
private sceneStartTime: number = 0;
|
|
||||||
private isConnected: boolean = false;
|
|
||||||
private reconnectAttempts: number = 0;
|
|
||||||
private maxReconnectAttempts: number = 5;
|
|
||||||
private frameTimeHistory: number[] = [];
|
|
||||||
private maxHistoryLength: number = 60; // 保存60帧的历史数据
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造函数
|
|
||||||
* @param core Core实例
|
|
||||||
* @param config 调试配置
|
|
||||||
*/
|
|
||||||
constructor(core: Core, config: IECSDebugConfig) {
|
|
||||||
this.core = core;
|
|
||||||
this.config = config;
|
|
||||||
this.sceneStartTime = Date.now();
|
|
||||||
|
|
||||||
// 确保性能监控器在调试模式下被启用
|
|
||||||
if (this.config.enabled && this.config.channels.performance) {
|
|
||||||
if (!this.core._performanceMonitor.isEnabled) {
|
|
||||||
this.core._performanceMonitor.enable();
|
|
||||||
console.log('[ECS Debug] Performance monitor enabled for debugging');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.enabled) {
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动调试报告器
|
|
||||||
*/
|
|
||||||
private start(): void {
|
|
||||||
this.connectWebSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止调试报告器
|
|
||||||
*/
|
|
||||||
public stop(): void {
|
|
||||||
if (this.timer) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.close();
|
|
||||||
this.ws = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isConnected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新配置
|
|
||||||
* @param newConfig 新配置
|
|
||||||
*/
|
|
||||||
public updateConfig(newConfig: IECSDebugConfig): void {
|
|
||||||
const wasEnabled = this.config.enabled;
|
|
||||||
const urlChanged = this.config.websocketUrl !== newConfig.websocketUrl;
|
|
||||||
|
|
||||||
this.config = newConfig;
|
|
||||||
|
|
||||||
// 根据配置启用或禁用性能监控器
|
|
||||||
if (newConfig.enabled && newConfig.channels.performance) {
|
|
||||||
if (!this.core._performanceMonitor.isEnabled) {
|
|
||||||
this.core._performanceMonitor.enable();
|
|
||||||
console.log('[ECS Debug] Performance monitor enabled for debugging');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newConfig.enabled && wasEnabled) {
|
|
||||||
this.stop();
|
|
||||||
} else if (newConfig.enabled && (!wasEnabled || urlChanged)) {
|
|
||||||
this.stop();
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 连接WebSocket
|
|
||||||
*/
|
|
||||||
private connectWebSocket(): void {
|
|
||||||
if (!this.config.websocketUrl) {
|
|
||||||
console.error('[ECS Debug] WebSocket URL not provided');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.ws = new WebSocket(this.config.websocketUrl);
|
|
||||||
|
|
||||||
this.ws.onopen = () => {
|
|
||||||
this.isConnected = true;
|
|
||||||
this.reconnectAttempts = 0;
|
|
||||||
this.startDataStream();
|
|
||||||
|
|
||||||
// 发送初始化消息
|
|
||||||
this.send({
|
|
||||||
type: 'init',
|
|
||||||
data: {
|
|
||||||
frameworkVersion: this.getFrameworkVersion(),
|
|
||||||
timestamp: Date.now()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onmessage = (event) => {
|
|
||||||
try {
|
|
||||||
const message = JSON.parse(event.data);
|
|
||||||
this.handleMessage(message);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[ECS Debug] Failed to parse message:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onclose = (event) => {
|
|
||||||
this.isConnected = false;
|
|
||||||
|
|
||||||
if (this.timer) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自动重连
|
|
||||||
if (this.config.autoReconnect !== false && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
||||||
this.reconnectAttempts++;
|
|
||||||
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 10000);
|
|
||||||
setTimeout(() => this.connectWebSocket(), delay);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onerror = (error) => {
|
|
||||||
console.error('[ECS Debug] WebSocket error:', error);
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[ECS Debug] Failed to create WebSocket:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动数据流
|
|
||||||
*/
|
|
||||||
private startDataStream(): void {
|
|
||||||
const interval = this.config.updateInterval || 1000;
|
|
||||||
|
|
||||||
this.timer = setInterval(() => {
|
|
||||||
if (this.isConnected) {
|
|
||||||
try {
|
|
||||||
const data = this.collectDebugData();
|
|
||||||
this.send({ type: 'debug_data', data });
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[ECS Debug] Failed to send debug data:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息
|
|
||||||
*/
|
|
||||||
private send(message: any): void {
|
|
||||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
||||||
this.ws.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理接收到的消息
|
|
||||||
* @param message 消息内容
|
|
||||||
*/
|
|
||||||
private handleMessage(message: any): void {
|
|
||||||
switch (message.type) {
|
|
||||||
case 'get_debug_data':
|
|
||||||
const data = this.collectDebugData();
|
|
||||||
this.send({ type: 'debug_data_response', data, requestId: message.requestId });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'update_config':
|
|
||||||
if (message.config) {
|
|
||||||
this.updateConfig(message.config);
|
|
||||||
this.send({ type: 'config_updated', success: true });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ping':
|
|
||||||
this.send({ type: 'pong', timestamp: Date.now() });
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.warn('[ECS Debug] Unknown message type:', message.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集调试数据
|
|
||||||
* @returns 调试数据对象
|
|
||||||
*/
|
|
||||||
private collectDebugData(): IECSDebugData {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
|
|
||||||
// 更新FPS计算
|
|
||||||
this.updateFPS(currentTime);
|
|
||||||
|
|
||||||
const data: IECSDebugData = {
|
|
||||||
timestamp: currentTime,
|
|
||||||
frameworkVersion: this.getFrameworkVersion(),
|
|
||||||
isRunning: true,
|
|
||||||
frameworkLoaded: true,
|
|
||||||
currentScene: this.getCurrentSceneName()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据配置收集不同类型的数据
|
|
||||||
if (this.config.channels.entities) {
|
|
||||||
data.entities = this.collectEntityData();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.channels.systems) {
|
|
||||||
data.systems = this.collectSystemData();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.channels.performance) {
|
|
||||||
data.performance = this.collectPerformanceData();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.channels.components) {
|
|
||||||
data.components = this.collectComponentData();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.config.channels.scenes) {
|
|
||||||
data.scenes = this.collectSceneData();
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新FPS计算
|
|
||||||
*/
|
|
||||||
private updateFPS(currentTime: number): void {
|
|
||||||
this.frameCount++;
|
|
||||||
|
|
||||||
if (currentTime - this.lastFpsTime >= 1000) {
|
|
||||||
this.fps = this.frameCount;
|
|
||||||
this.frameCount = 0;
|
|
||||||
this.lastFpsTime = currentTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取框架版本
|
|
||||||
*/
|
|
||||||
private getFrameworkVersion(): string {
|
|
||||||
return '1.0.0';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前场景名称
|
|
||||||
*/
|
|
||||||
private getCurrentSceneName(): string {
|
|
||||||
const scene = Core.scene;
|
|
||||||
return scene ? (scene as any).name || 'Unnamed Scene' : 'No Scene';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集实体数据
|
|
||||||
*/
|
|
||||||
private collectEntityData(): IEntityDebugData {
|
|
||||||
const scene = Core.scene;
|
|
||||||
if (!scene) {
|
|
||||||
return {
|
|
||||||
totalEntities: 0,
|
|
||||||
activeEntities: 0,
|
|
||||||
pendingAdd: 0,
|
|
||||||
pendingRemove: 0,
|
|
||||||
entitiesPerArchetype: [],
|
|
||||||
topEntitiesByComponents: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const entityList = (scene as any).entities;
|
|
||||||
if (!entityList) {
|
|
||||||
return {
|
|
||||||
totalEntities: 0,
|
|
||||||
activeEntities: 0,
|
|
||||||
pendingAdd: 0,
|
|
||||||
pendingRemove: 0,
|
|
||||||
entitiesPerArchetype: [],
|
|
||||||
topEntitiesByComponents: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const allEntities = entityList.buffer || [];
|
|
||||||
const activeEntities = allEntities.filter((e: any) => e.enabled && !e._isDestroyed);
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalEntities: allEntities.length,
|
|
||||||
activeEntities: activeEntities.length,
|
|
||||||
pendingAdd: entityList.toAdd?.length || 0,
|
|
||||||
pendingRemove: entityList.toRemove?.length || 0,
|
|
||||||
entitiesPerArchetype: this.getArchetypeDistribution({ entities: allEntities }),
|
|
||||||
topEntitiesByComponents: this.getTopEntitiesByComponents({ entities: allEntities }),
|
|
||||||
entityDetails: this.getEntityDetails({ entities: allEntities })
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实体详情
|
|
||||||
*/
|
|
||||||
private getEntityDetails(entityContainer: any): Array<any> {
|
|
||||||
if (!entityContainer.entities) return [];
|
|
||||||
|
|
||||||
return entityContainer.entities.slice(0, 100).map((entity: any) => ({
|
|
||||||
id: entity.id,
|
|
||||||
name: entity.name || `Entity_${entity.id}`,
|
|
||||||
tag: entity.tag,
|
|
||||||
enabled: entity.enabled,
|
|
||||||
componentCount: entity.components?.length || 0,
|
|
||||||
components: entity.components?.map((c: any) => c.constructor.name) || []
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集系统数据
|
|
||||||
*/
|
|
||||||
private collectSystemData(): ISystemDebugData {
|
|
||||||
const scene = Core.scene;
|
|
||||||
if (!scene) {
|
|
||||||
return {
|
|
||||||
totalSystems: 0,
|
|
||||||
systemsInfo: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const entityProcessors = (scene as any).entityProcessors;
|
|
||||||
if (!entityProcessors) {
|
|
||||||
return {
|
|
||||||
totalSystems: 0,
|
|
||||||
systemsInfo: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const systems = entityProcessors.processors || [];
|
|
||||||
|
|
||||||
// 获取性能监控数据
|
|
||||||
const monitor = this.core._performanceMonitor;
|
|
||||||
let systemStats: Map<string, any> = new Map();
|
|
||||||
let systemData: Map<string, any> = new Map();
|
|
||||||
|
|
||||||
if (monitor) {
|
|
||||||
try {
|
|
||||||
systemStats = monitor.getAllSystemStats();
|
|
||||||
systemData = monitor.getAllSystemData();
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,使用空的Map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalSystems: systems.length,
|
|
||||||
systemsInfo: systems.map((system: any) => {
|
|
||||||
const systemName = system.systemName || system.constructor.name;
|
|
||||||
const stats = systemStats.get(systemName);
|
|
||||||
const data = systemData.get(systemName);
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: systemName,
|
|
||||||
type: system.constructor.name,
|
|
||||||
entityCount: system.entities?.length || 0,
|
|
||||||
executionTime: stats?.averageTime || data?.executionTime || 0,
|
|
||||||
minExecutionTime: stats?.minTime === Number.MAX_VALUE ? 0 : (stats?.minTime || 0),
|
|
||||||
maxExecutionTime: stats?.maxTime || 0,
|
|
||||||
executionTimeHistory: stats?.recentTimes || [],
|
|
||||||
updateOrder: system.updateOrder || 0,
|
|
||||||
enabled: system.enabled !== false,
|
|
||||||
lastUpdateTime: data?.lastUpdateTime || 0
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集性能数据
|
|
||||||
*/
|
|
||||||
private collectPerformanceData(): IPerformanceDebugData {
|
|
||||||
const frameTimeSeconds = Time.deltaTime;
|
|
||||||
const engineFrameTimeMs = frameTimeSeconds * 1000;
|
|
||||||
const currentFps = frameTimeSeconds > 0 ? Math.round(1 / frameTimeSeconds) : 0;
|
|
||||||
|
|
||||||
const ecsPerformanceData = this.getECSPerformanceData();
|
|
||||||
const ecsExecutionTimeMs = ecsPerformanceData.totalExecutionTime;
|
|
||||||
const ecsPercentage = engineFrameTimeMs > 0 ? (ecsExecutionTimeMs / engineFrameTimeMs * 100) : 0;
|
|
||||||
|
|
||||||
let memoryUsage = 0;
|
|
||||||
if ((performance as any).memory) {
|
|
||||||
memoryUsage = (performance as any).memory.usedJSHeapSize / 1024 / 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新ECS执行时间历史记录
|
|
||||||
this.frameTimeHistory.push(ecsExecutionTimeMs);
|
|
||||||
if (this.frameTimeHistory.length > this.maxHistoryLength) {
|
|
||||||
this.frameTimeHistory.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算ECS执行时间统计
|
|
||||||
const history = this.frameTimeHistory.filter(t => t >= 0);
|
|
||||||
const averageECSTime = history.length > 0 ? history.reduce((a, b) => a + b, 0) / history.length : ecsExecutionTimeMs;
|
|
||||||
const minECSTime = history.length > 0 ? Math.min(...history) : ecsExecutionTimeMs;
|
|
||||||
const maxECSTime = history.length > 0 ? Math.max(...history) : ecsExecutionTimeMs;
|
|
||||||
|
|
||||||
return {
|
|
||||||
frameTime: ecsExecutionTimeMs,
|
|
||||||
engineFrameTime: engineFrameTimeMs,
|
|
||||||
ecsPercentage: ecsPercentage,
|
|
||||||
memoryUsage: memoryUsage,
|
|
||||||
fps: currentFps,
|
|
||||||
averageFrameTime: averageECSTime, // ECS平均执行时间
|
|
||||||
minFrameTime: minECSTime, // ECS最短执行时间
|
|
||||||
maxFrameTime: maxECSTime, // ECS最长执行时间
|
|
||||||
frameTimeHistory: [...this.frameTimeHistory],
|
|
||||||
systemPerformance: this.getSystemPerformance(),
|
|
||||||
systemBreakdown: ecsPerformanceData.systemBreakdown,
|
|
||||||
memoryDetails: this.getMemoryDetails()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取ECS框架整体性能数据
|
|
||||||
*/
|
|
||||||
private getECSPerformanceData(): { totalExecutionTime: number; systemBreakdown: Array<any> } {
|
|
||||||
const monitor = this.core._performanceMonitor;
|
|
||||||
if (!monitor) {
|
|
||||||
console.warn('[ECS Debug] Performance monitor not found');
|
|
||||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!monitor.isEnabled) {
|
|
||||||
console.warn('[ECS Debug] Performance monitor is disabled. Enable it to see ECS performance data.');
|
|
||||||
// 尝试启用性能监控器
|
|
||||||
monitor.enable();
|
|
||||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let totalTime = 0;
|
|
||||||
const systemBreakdown = [];
|
|
||||||
|
|
||||||
const stats = monitor.getAllSystemStats();
|
|
||||||
|
|
||||||
if (stats.size === 0) {
|
|
||||||
console.log('[ECS Debug] No system performance data available yet. This is normal on first frames.');
|
|
||||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算各系统的执行时间
|
|
||||||
for (const [systemName, stat] of stats.entries()) {
|
|
||||||
const systemTime = stat.averageTime || 0;
|
|
||||||
|
|
||||||
totalTime += systemTime;
|
|
||||||
systemBreakdown.push({
|
|
||||||
systemName: systemName,
|
|
||||||
executionTime: systemTime,
|
|
||||||
percentage: 0 // 后面计算
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算各系统占ECS总时间的百分比
|
|
||||||
systemBreakdown.forEach(system => {
|
|
||||||
system.percentage = totalTime > 0 ? (system.executionTime / totalTime * 100) : 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 按执行时间排序
|
|
||||||
systemBreakdown.sort((a, b) => b.executionTime - a.executionTime);
|
|
||||||
|
|
||||||
console.log(`[ECS Debug] Performance data: ${stats.size} systems, total time: ${totalTime.toFixed(2)}ms`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalExecutionTime: totalTime,
|
|
||||||
systemBreakdown: systemBreakdown
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[ECS Debug] Error getting ECS performance data:', error);
|
|
||||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统性能数据
|
|
||||||
*/
|
|
||||||
private getSystemPerformance(): Array<any> {
|
|
||||||
const monitor = this.core._performanceMonitor;
|
|
||||||
if (!monitor) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const stats = monitor.getAllSystemStats();
|
|
||||||
const systemData = monitor.getAllSystemData();
|
|
||||||
|
|
||||||
return Array.from(stats.entries()).map(([systemName, stat]) => {
|
|
||||||
const data = systemData.get(systemName);
|
|
||||||
return {
|
|
||||||
systemName: systemName,
|
|
||||||
averageTime: stat.averageTime,
|
|
||||||
maxTime: stat.maxTime,
|
|
||||||
minTime: stat.minTime === Number.MAX_VALUE ? 0 : stat.minTime,
|
|
||||||
samples: stat.executionCount,
|
|
||||||
percentage: 0, // 在getECSPerformanceData中计算
|
|
||||||
entityCount: data?.entityCount || 0,
|
|
||||||
lastExecutionTime: data?.executionTime || 0
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取内存详情
|
|
||||||
*/
|
|
||||||
private getMemoryDetails(): any {
|
|
||||||
const scene = Core.scene;
|
|
||||||
if (!scene) {
|
|
||||||
return {
|
|
||||||
entities: 0,
|
|
||||||
components: 0,
|
|
||||||
systems: 0,
|
|
||||||
pooled: 0,
|
|
||||||
totalMemory: 0,
|
|
||||||
usedMemory: 0,
|
|
||||||
freeMemory: 0,
|
|
||||||
gcCollections: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let entityMemory = 0;
|
|
||||||
let componentMemory = 0;
|
|
||||||
let systemMemory = 0;
|
|
||||||
let pooledMemory = 0;
|
|
||||||
|
|
||||||
const entityManager = (scene as any).entityManager;
|
|
||||||
if (entityManager?.entities) {
|
|
||||||
// 计算实体和组件内存
|
|
||||||
entityManager.entities.forEach((entity: any) => {
|
|
||||||
entityMemory += this.estimateObjectSize(entity);
|
|
||||||
|
|
||||||
if (entity.components) {
|
|
||||||
entity.components.forEach((component: any) => {
|
|
||||||
componentMemory += this.estimateObjectSize(component);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 如果entityManager不存在,尝试从场景直接获取
|
|
||||||
const entityList = (scene as any).entities;
|
|
||||||
if (entityList?.buffer) {
|
|
||||||
entityList.buffer.forEach((entity: any) => {
|
|
||||||
entityMemory += this.estimateObjectSize(entity);
|
|
||||||
|
|
||||||
if (entity.components) {
|
|
||||||
entity.components.forEach((component: any) => {
|
|
||||||
componentMemory += this.estimateObjectSize(component);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算系统内存
|
|
||||||
const entitySystems = (scene as any).entitySystems;
|
|
||||||
if (entitySystems?.systems) {
|
|
||||||
entitySystems.systems.forEach((system: any) => {
|
|
||||||
systemMemory += this.estimateObjectSize(system);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// 尝试从entityProcessors获取
|
|
||||||
const entityProcessors = (scene as any).entityProcessors;
|
|
||||||
if (entityProcessors?.processors) {
|
|
||||||
entityProcessors.processors.forEach((system: any) => {
|
|
||||||
systemMemory += this.estimateObjectSize(system);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算对象池内存
|
|
||||||
try {
|
|
||||||
const { ComponentPoolManager } = require('../ECS/Core/ComponentPool');
|
|
||||||
const poolManager = ComponentPoolManager.getInstance();
|
|
||||||
const poolStats = poolManager.getPoolStats();
|
|
||||||
|
|
||||||
for (const [typeName, stats] of poolStats.entries()) {
|
|
||||||
// 估算每个组件实例的大小
|
|
||||||
const estimatedComponentSize = this.calculateComponentMemorySize(typeName);
|
|
||||||
pooledMemory += stats.available * estimatedComponentSize;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 如果无法访问ComponentPoolManager,使用估算值
|
|
||||||
pooledMemory = 512 * 1024; // 512KB估算值
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取浏览器内存信息
|
|
||||||
let totalMemory = 512 * 1024 * 1024;
|
|
||||||
let usedMemory = entityMemory + componentMemory + systemMemory + pooledMemory;
|
|
||||||
let gcCollections = 0;
|
|
||||||
|
|
||||||
if ((performance as any).memory) {
|
|
||||||
const perfMemory = (performance as any).memory;
|
|
||||||
totalMemory = perfMemory.jsHeapSizeLimit || totalMemory;
|
|
||||||
usedMemory = perfMemory.usedJSHeapSize || usedMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
entities: entityMemory,
|
|
||||||
components: componentMemory,
|
|
||||||
systems: systemMemory,
|
|
||||||
pooled: pooledMemory,
|
|
||||||
totalMemory: totalMemory,
|
|
||||||
usedMemory: usedMemory,
|
|
||||||
freeMemory: totalMemory - usedMemory,
|
|
||||||
gcCollections: gcCollections
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
entities: 0,
|
|
||||||
components: 0,
|
|
||||||
systems: 0,
|
|
||||||
pooled: 0,
|
|
||||||
totalMemory: 512 * 1024 * 1024,
|
|
||||||
usedMemory: 0,
|
|
||||||
freeMemory: 512 * 1024 * 1024,
|
|
||||||
gcCollections: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集组件数据
|
|
||||||
*/
|
|
||||||
private collectComponentData(): IComponentDebugData {
|
|
||||||
const scene = Core.scene;
|
|
||||||
if (!scene) {
|
|
||||||
return {
|
|
||||||
componentTypes: 0,
|
|
||||||
componentInstances: 0,
|
|
||||||
componentStats: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const entityList = (scene as any).entities;
|
|
||||||
if (!entityList?.buffer) {
|
|
||||||
return {
|
|
||||||
componentTypes: 0,
|
|
||||||
componentInstances: 0,
|
|
||||||
componentStats: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const componentStats = new Map<string, { count: number; entities: number }>();
|
|
||||||
let totalInstances = 0;
|
|
||||||
|
|
||||||
entityList.buffer.forEach((entity: any) => {
|
|
||||||
if (entity.components) {
|
|
||||||
entity.components.forEach((component: any) => {
|
|
||||||
const typeName = component.constructor.name;
|
|
||||||
const stats = componentStats.get(typeName) || { count: 0, entities: 0 };
|
|
||||||
stats.count++;
|
|
||||||
totalInstances++;
|
|
||||||
componentStats.set(typeName, stats);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取池利用率信息
|
|
||||||
let poolUtilizations = new Map<string, number>();
|
|
||||||
let poolSizes = new Map<string, number>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { ComponentPoolManager } = require('../ECS/Core/ComponentPool');
|
|
||||||
const poolManager = ComponentPoolManager.getInstance();
|
|
||||||
const poolStats = poolManager.getPoolStats();
|
|
||||||
const utilizations = poolManager.getPoolUtilization();
|
|
||||||
|
|
||||||
for (const [typeName, stats] of poolStats.entries()) {
|
|
||||||
poolSizes.set(typeName, stats.maxSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [typeName, util] of utilizations.entries()) {
|
|
||||||
poolUtilizations.set(typeName, util.utilization);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 如果无法获取池信息,使用默认值
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
componentTypes: componentStats.size,
|
|
||||||
componentInstances: totalInstances,
|
|
||||||
componentStats: Array.from(componentStats.entries()).map(([typeName, stats]) => {
|
|
||||||
const poolSize = poolSizes.get(typeName) || 0;
|
|
||||||
const poolUtilization = poolUtilizations.get(typeName) || 0;
|
|
||||||
const memoryPerInstance = this.calculateComponentMemorySize(typeName);
|
|
||||||
|
|
||||||
return {
|
|
||||||
typeName,
|
|
||||||
instanceCount: stats.count,
|
|
||||||
memoryPerInstance: memoryPerInstance,
|
|
||||||
totalMemory: stats.count * memoryPerInstance,
|
|
||||||
poolSize: poolSize,
|
|
||||||
poolUtilization: poolUtilization,
|
|
||||||
averagePerEntity: stats.count / entityList.buffer.length
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算组件实际内存大小
|
|
||||||
*/
|
|
||||||
private calculateComponentMemorySize(typeName: string): number {
|
|
||||||
const scene = Core.scene;
|
|
||||||
if (!scene) return 32;
|
|
||||||
|
|
||||||
const entityList = (scene as any).entities;
|
|
||||||
if (!entityList?.buffer) return 32;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 找到第一个包含此组件的实体,分析组件大小
|
|
||||||
for (const entity of entityList.buffer) {
|
|
||||||
if (entity.components) {
|
|
||||||
const component = entity.components.find((c: any) => c.constructor.name === typeName);
|
|
||||||
if (component) {
|
|
||||||
return this.estimateObjectSize(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略错误,使用默认值
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果无法计算,返回基础大小
|
|
||||||
return 32; // 基础对象开销
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 估算对象内存大小(字节)
|
|
||||||
*/
|
|
||||||
private estimateObjectSize(obj: any, visited = new WeakSet(), depth = 0): number {
|
|
||||||
// 防止无限递归:限制深度和检测循环引用
|
|
||||||
if (obj === null || obj === undefined || depth > 10) return 0;
|
|
||||||
if (visited.has(obj)) return 0; // 已访问过,避免循环引用
|
|
||||||
|
|
||||||
let size = 0;
|
|
||||||
const type = typeof obj;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'boolean':
|
|
||||||
size = 1;
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
size = 8; // JavaScript中数字都是64位浮点数
|
|
||||||
break;
|
|
||||||
case 'string':
|
|
||||||
size = Math.min(obj.length * 2, 1024); // UTF-16编码,每字符2字节,限制最大1KB
|
|
||||||
break;
|
|
||||||
case 'object':
|
|
||||||
visited.add(obj); // 标记为已访问
|
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
size = 24; // 数组基础开销
|
|
||||||
// 只处理前100个元素,避免大数组导致性能问题
|
|
||||||
const maxItems = Math.min(obj.length, 100);
|
|
||||||
for (let i = 0; i < maxItems; i++) {
|
|
||||||
size += this.estimateObjectSize(obj[i], visited, depth + 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
size = 24; // 对象基础开销
|
|
||||||
let propertyCount = 0;
|
|
||||||
|
|
||||||
for (const key in obj) {
|
|
||||||
// 只处理前50个属性,避免复杂对象导致性能问题
|
|
||||||
if (propertyCount >= 50) break;
|
|
||||||
|
|
||||||
if (obj.hasOwnProperty(key)) {
|
|
||||||
// 跳过一些可能导致问题的属性
|
|
||||||
if (key === 'scene' || key === 'parent' || key === 'children' ||
|
|
||||||
key === '_scene' || key === '_parent' || key === '_children' ||
|
|
||||||
key === 'entity' || key === '_entity') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
size += key.length * 2; // 属性名
|
|
||||||
size += this.estimateObjectSize(obj[key], visited, depth + 1); // 属性值
|
|
||||||
propertyCount++;
|
|
||||||
} catch (error) {
|
|
||||||
// 忽略访问错误的属性
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
size = 8; // 其他类型默认8字节
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.min(size, 10240); // 限制单个对象最大10KB
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收集场景数据
|
|
||||||
*/
|
|
||||||
private collectSceneData(): ISceneDebugData {
|
|
||||||
const scene = Core.scene;
|
|
||||||
if (!scene) {
|
|
||||||
return {
|
|
||||||
currentSceneName: 'No Scene',
|
|
||||||
isInitialized: false,
|
|
||||||
sceneRunTime: 0,
|
|
||||||
sceneEntityCount: 0,
|
|
||||||
sceneSystemCount: 0,
|
|
||||||
sceneMemory: 0,
|
|
||||||
sceneUptime: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentTime = Date.now();
|
|
||||||
const runTime = (currentTime - this.sceneStartTime) / 1000;
|
|
||||||
|
|
||||||
const entityList = (scene as any).entities;
|
|
||||||
const entityProcessors = (scene as any).entityProcessors;
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentSceneName: (scene as any).name || 'Unnamed Scene',
|
|
||||||
isInitialized: (scene as any)._didSceneBegin || false,
|
|
||||||
sceneRunTime: runTime,
|
|
||||||
sceneEntityCount: entityList?.buffer?.length || 0,
|
|
||||||
sceneSystemCount: entityProcessors?.processors?.length || 0,
|
|
||||||
sceneMemory: 0, // TODO: 计算实际场景内存
|
|
||||||
sceneUptime: runTime
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动触发数据收集
|
|
||||||
* @returns 当前调试数据
|
|
||||||
*/
|
|
||||||
public getDebugData(): IECSDebugData {
|
|
||||||
return this.collectDebugData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置场景时间
|
|
||||||
*/
|
|
||||||
public onSceneChanged(): void {
|
|
||||||
this.sceneStartTime = Date.now();
|
|
||||||
|
|
||||||
// 发送场景切换事件
|
|
||||||
if (this.isConnected) {
|
|
||||||
this.send({
|
|
||||||
type: 'scene_changed',
|
|
||||||
data: {
|
|
||||||
sceneName: this.getCurrentSceneName(),
|
|
||||||
timestamp: Date.now()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接状态
|
|
||||||
*/
|
|
||||||
public get connected(): boolean {
|
|
||||||
return this.isConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手动重连
|
|
||||||
*/
|
|
||||||
public reconnect(): void {
|
|
||||||
if (this.ws) {
|
|
||||||
this.ws.close();
|
|
||||||
}
|
|
||||||
this.reconnectAttempts = 0;
|
|
||||||
this.connectWebSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取Archetype分布
|
|
||||||
*/
|
|
||||||
private getArchetypeDistribution(entityContainer: any): Array<{ signature: string; count: number; memory: number }> {
|
|
||||||
if (!entityContainer.entities) return [];
|
|
||||||
|
|
||||||
const archetypes = new Map<string, { count: number; memory: number }>();
|
|
||||||
|
|
||||||
entityContainer.entities.forEach((entity: any) => {
|
|
||||||
const components = entity.components || [];
|
|
||||||
const signature = components.map((c: any) => c.constructor.name).sort().join(',') || 'Empty';
|
|
||||||
|
|
||||||
const existing = archetypes.get(signature) || { count: 0, memory: 0 };
|
|
||||||
existing.count++;
|
|
||||||
|
|
||||||
// 计算每个组件的实际内存大小
|
|
||||||
let entityMemory = 0;
|
|
||||||
components.forEach((component: any) => {
|
|
||||||
entityMemory += this.estimateObjectSize(component);
|
|
||||||
});
|
|
||||||
existing.memory += entityMemory;
|
|
||||||
|
|
||||||
archetypes.set(signature, existing);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Array.from(archetypes.entries())
|
|
||||||
.map(([signature, data]) => ({ signature, ...data }))
|
|
||||||
.sort((a: any, b: any) => b.count - a.count)
|
|
||||||
.slice(0, 10); // 只返回前10个
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组件数量最多的实体
|
|
||||||
*/
|
|
||||||
private getTopEntitiesByComponents(entityContainer: any): Array<{ id: string; name: string; componentCount: number; memory: number }> {
|
|
||||||
if (!entityContainer.entities) return [];
|
|
||||||
|
|
||||||
return entityContainer.entities
|
|
||||||
.map((entity: any) => {
|
|
||||||
const components = entity.components || [];
|
|
||||||
let memory = 0;
|
|
||||||
|
|
||||||
// 计算实际内存使用
|
|
||||||
components.forEach((component: any) => {
|
|
||||||
memory += this.estimateObjectSize(component);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: entity.id?.toString() || 'unknown',
|
|
||||||
name: entity.name || `Entity_${entity.id}`,
|
|
||||||
componentCount: components.length,
|
|
||||||
memory: memory
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort((a: any, b: any) => b.componentCount - a.componentCount)
|
|
||||||
.slice(0, 10); // 只返回前10个
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,4 +3,5 @@ export * from './Pool';
|
|||||||
export * from './Emitter';
|
export * from './Emitter';
|
||||||
export * from './GlobalManager';
|
export * from './GlobalManager';
|
||||||
export * from './PerformanceMonitor';
|
export * from './PerformanceMonitor';
|
||||||
export { Time } from './Time';
|
export { Time } from './Time';
|
||||||
|
export * from './Debug';
|
||||||
Reference in New Issue
Block a user