使用Lerna 和 monorepo管理项目结构
This commit is contained in:
271
packages/core/src/Utils/Debug/ComponentDataCollector.ts
Normal file
271
packages/core/src/Utils/Debug/ComponentDataCollector.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import { IComponentDebugData } from '../../Types';
|
||||
import { Core } from '../../Core';
|
||||
|
||||
/**
|
||||
* 组件数据收集器
|
||||
*/
|
||||
export class ComponentDataCollector {
|
||||
private static componentSizeCache = new Map<string, number>();
|
||||
|
||||
/**
|
||||
* 收集组件数据(轻量版,不计算实际内存大小)
|
||||
*/
|
||||
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.getEstimatedComponentSize(typeName);
|
||||
|
||||
return {
|
||||
typeName,
|
||||
instanceCount: stats.count,
|
||||
memoryPerInstance: memoryPerInstance,
|
||||
totalMemory: stats.count * memoryPerInstance,
|
||||
poolSize: poolSize,
|
||||
poolUtilization: poolUtilization,
|
||||
averagePerEntity: stats.count / entityList.buffer.length
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件类型的估算内存大小(基于预设值,不进行实际计算)
|
||||
*/
|
||||
private getEstimatedComponentSize(typeName: string): number {
|
||||
if (ComponentDataCollector.componentSizeCache.has(typeName)) {
|
||||
return ComponentDataCollector.componentSizeCache.get(typeName)!;
|
||||
}
|
||||
|
||||
const scene = Core.scene;
|
||||
if (!scene) return 64;
|
||||
|
||||
const entityList = (scene as any).entities;
|
||||
if (!entityList?.buffer) return 64;
|
||||
|
||||
let calculatedSize = 64;
|
||||
|
||||
try {
|
||||
for (const entity of entityList.buffer) {
|
||||
if (entity.components) {
|
||||
const component = entity.components.find((c: any) => c.constructor.name === typeName);
|
||||
if (component) {
|
||||
calculatedSize = this.calculateQuickObjectSize(component);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
calculatedSize = 64;
|
||||
}
|
||||
|
||||
ComponentDataCollector.componentSizeCache.set(typeName, calculatedSize);
|
||||
return calculatedSize;
|
||||
}
|
||||
|
||||
private calculateQuickObjectSize(obj: any): number {
|
||||
if (!obj || typeof obj !== 'object') return 8;
|
||||
|
||||
let size = 32;
|
||||
const visited = new WeakSet();
|
||||
|
||||
const calculate = (item: any, depth: number = 0): number => {
|
||||
if (!item || typeof item !== 'object' || visited.has(item) || depth > 3) {
|
||||
return 0;
|
||||
}
|
||||
visited.add(item);
|
||||
|
||||
let itemSize = 0;
|
||||
|
||||
try {
|
||||
const keys = Object.keys(item);
|
||||
for (let i = 0; i < Math.min(keys.length, 20); i++) {
|
||||
const key = keys[i];
|
||||
if (key === 'entity' || key === '_entity' || key === 'constructor') continue;
|
||||
|
||||
const value = item[key];
|
||||
itemSize += key.length * 2;
|
||||
|
||||
if (typeof value === 'string') {
|
||||
itemSize += Math.min(value.length * 2, 200);
|
||||
} else if (typeof value === 'number') {
|
||||
itemSize += 8;
|
||||
} else if (typeof value === 'boolean') {
|
||||
itemSize += 4;
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
itemSize += calculate(value, depth + 1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return 32;
|
||||
}
|
||||
|
||||
return itemSize;
|
||||
};
|
||||
|
||||
size += calculate(obj);
|
||||
return Math.max(size, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为内存快照功能提供的详细内存计算
|
||||
* 只在用户主动请求内存快照时调用
|
||||
*/
|
||||
public calculateDetailedComponentMemory(typeName: string): number {
|
||||
const scene = Core.scene;
|
||||
if (!scene) return this.getEstimatedComponentSize(typeName);
|
||||
|
||||
const entityList = (scene as any).entities;
|
||||
if (!entityList?.buffer) return this.getEstimatedComponentSize(typeName);
|
||||
|
||||
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 this.getEstimatedComponentSize(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算对象内存大小(仅用于内存快照)
|
||||
* 优化版本:减少递归深度,提高性能
|
||||
*/
|
||||
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 = 4;
|
||||
break;
|
||||
case 'number':
|
||||
size = 8;
|
||||
break;
|
||||
case 'string':
|
||||
size = 24 + Math.min(obj.length * 2, 1000);
|
||||
break;
|
||||
case 'object':
|
||||
visited.add(obj);
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
size = 40 + (obj.length * 8);
|
||||
const maxElements = Math.min(obj.length, 50);
|
||||
for (let i = 0; i < maxElements; i++) {
|
||||
size += this.estimateObjectSize(obj[i], visited, depth + 1);
|
||||
}
|
||||
} else {
|
||||
size = 32;
|
||||
|
||||
try {
|
||||
const ownKeys = Object.getOwnPropertyNames(obj);
|
||||
const maxProps = Math.min(ownKeys.length, 30);
|
||||
|
||||
for (let i = 0; i < maxProps; i++) {
|
||||
const key = ownKeys[i];
|
||||
|
||||
if (key === 'constructor' ||
|
||||
key === '__proto__' ||
|
||||
key === 'entity' ||
|
||||
key === '_entity' ||
|
||||
key.startsWith('_cc_') ||
|
||||
key.startsWith('__')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
size += 16 + (key.length * 2);
|
||||
|
||||
const value = obj[key];
|
||||
if (value !== undefined && value !== null) {
|
||||
size += this.estimateObjectSize(value, visited, depth + 1);
|
||||
}
|
||||
} catch (error) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
size = 128;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
size = 8;
|
||||
}
|
||||
|
||||
return Math.ceil(size / 8) * 8;
|
||||
}
|
||||
|
||||
public static clearCache(): void {
|
||||
ComponentDataCollector.componentSizeCache.clear();
|
||||
}
|
||||
}
|
||||
124
packages/core/src/Utils/Debug/DebugDataFormatter.ts
Normal file
124
packages/core/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);
|
||||
}
|
||||
}
|
||||
732
packages/core/src/Utils/Debug/DebugManager.ts
Normal file
732
packages/core/src/Utils/Debug/DebugManager.ts
Normal file
@@ -0,0 +1,732 @@
|
||||
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 'expand_lazy_object':
|
||||
this.handleExpandLazyObjectRequest(message);
|
||||
break;
|
||||
|
||||
case 'get_component_properties':
|
||||
this.handleGetComponentPropertiesRequest(message);
|
||||
break;
|
||||
|
||||
case 'get_raw_entity_list':
|
||||
this.handleGetRawEntityListRequest(message);
|
||||
break;
|
||||
|
||||
case 'get_entity_details':
|
||||
this.handleGetEntityDetailsRequest(message);
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
this.webSocketManager.send({
|
||||
type: 'pong',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
// 未知消息类型,忽略
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error('[ECS Debug] 处理消息失败:', error);
|
||||
if (message.requestId) {
|
||||
this.webSocketManager.send({
|
||||
type: 'error_response',
|
||||
requestId: message.requestId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理展开懒加载对象请求
|
||||
*/
|
||||
private handleExpandLazyObjectRequest(message: any): void {
|
||||
try {
|
||||
const { entityId, componentIndex, propertyPath, requestId } = message;
|
||||
|
||||
if (entityId === undefined || componentIndex === undefined || !propertyPath) {
|
||||
this.webSocketManager.send({
|
||||
type: 'expand_lazy_object_response',
|
||||
requestId,
|
||||
error: '缺少必要参数'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const expandedData = this.entityCollector.expandLazyObject(entityId, componentIndex, propertyPath);
|
||||
|
||||
this.webSocketManager.send({
|
||||
type: 'expand_lazy_object_response',
|
||||
requestId,
|
||||
data: expandedData
|
||||
});
|
||||
} catch (error) {
|
||||
this.webSocketManager.send({
|
||||
type: 'expand_lazy_object_response',
|
||||
requestId: message.requestId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理获取组件属性请求
|
||||
*/
|
||||
private handleGetComponentPropertiesRequest(message: any): void {
|
||||
try {
|
||||
const { entityId, componentIndex, requestId } = message;
|
||||
|
||||
if (entityId === undefined || componentIndex === undefined) {
|
||||
this.webSocketManager.send({
|
||||
type: 'get_component_properties_response',
|
||||
requestId,
|
||||
error: '缺少必要参数'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const properties = this.entityCollector.getComponentProperties(entityId, componentIndex);
|
||||
|
||||
this.webSocketManager.send({
|
||||
type: 'get_component_properties_response',
|
||||
requestId,
|
||||
data: properties
|
||||
});
|
||||
} catch (error) {
|
||||
this.webSocketManager.send({
|
||||
type: 'get_component_properties_response',
|
||||
requestId: message.requestId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理获取原始实体列表请求
|
||||
*/
|
||||
private handleGetRawEntityListRequest(message: any): void {
|
||||
try {
|
||||
const { requestId } = message;
|
||||
|
||||
const rawEntityList = this.entityCollector.getRawEntityList();
|
||||
|
||||
this.webSocketManager.send({
|
||||
type: 'get_raw_entity_list_response',
|
||||
requestId,
|
||||
data: rawEntityList
|
||||
});
|
||||
} catch (error) {
|
||||
this.webSocketManager.send({
|
||||
type: 'get_raw_entity_list_response',
|
||||
requestId: message.requestId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理获取实体详情请求
|
||||
*/
|
||||
private handleGetEntityDetailsRequest(message: any): void {
|
||||
try {
|
||||
const { entityId, requestId } = message;
|
||||
|
||||
if (entityId === undefined) {
|
||||
this.webSocketManager.send({
|
||||
type: 'get_entity_details_response',
|
||||
requestId,
|
||||
error: '缺少实体ID参数'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const entityDetails = this.entityCollector.getEntityDetails(entityId);
|
||||
|
||||
this.webSocketManager.send({
|
||||
type: 'get_entity_details_response',
|
||||
requestId,
|
||||
data: entityDetails
|
||||
});
|
||||
} catch (error) {
|
||||
this.webSocketManager.send({
|
||||
type: 'get_entity_details_response',
|
||||
requestId: message.requestId,
|
||||
error: error instanceof Error ? error.message : String(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 timestamp = Date.now();
|
||||
|
||||
// 使用专门的内存计算方法收集实体数据
|
||||
const entityData = this.entityCollector.collectEntityDataWithMemory();
|
||||
|
||||
// 收集其他内存统计
|
||||
const baseMemoryInfo = this.collectBaseMemoryInfo();
|
||||
const componentMemoryStats = this.collectComponentMemoryStats((Core.scene as any)?.entities);
|
||||
const systemMemoryStats = this.collectSystemMemoryStats();
|
||||
const poolMemoryStats = this.collectPoolMemoryStats();
|
||||
const performanceStats = this.collectPerformanceStats();
|
||||
|
||||
// 计算总内存使用量
|
||||
const totalEntityMemory = entityData.entitiesPerArchetype.reduce((sum, arch) => sum + arch.memory, 0);
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
version: '2.0',
|
||||
summary: {
|
||||
totalEntities: entityData.totalEntities,
|
||||
totalMemoryUsage: baseMemoryInfo.usedMemory,
|
||||
totalMemoryLimit: baseMemoryInfo.totalMemory,
|
||||
memoryUtilization: (baseMemoryInfo.usedMemory / baseMemoryInfo.totalMemory * 100),
|
||||
gcCollections: baseMemoryInfo.gcCollections,
|
||||
entityMemory: totalEntityMemory,
|
||||
componentMemory: componentMemoryStats.totalMemory,
|
||||
systemMemory: systemMemoryStats.totalMemory,
|
||||
poolMemory: poolMemoryStats.totalMemory
|
||||
},
|
||||
baseMemory: baseMemoryInfo,
|
||||
entities: {
|
||||
totalMemory: totalEntityMemory,
|
||||
entityCount: entityData.totalEntities,
|
||||
archetypes: entityData.entitiesPerArchetype,
|
||||
largestEntities: entityData.topEntitiesByComponents
|
||||
},
|
||||
components: componentMemoryStats,
|
||||
systems: systemMemoryStats,
|
||||
pools: poolMemoryStats,
|
||||
performance: performanceStats
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集基础内存信息
|
||||
*/
|
||||
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 collectComponentMemoryStats(entityList: any): any {
|
||||
const componentStats = new Map<string, { count: number; totalMemory: number; instances: any[] }>();
|
||||
let totalComponentMemory = 0;
|
||||
|
||||
// 首先统计组件类型和数量
|
||||
const componentTypeCounts = new Map<string, number>();
|
||||
for (const entity of entityList.buffer) {
|
||||
if (!entity || entity.destroyed || !entity.components) continue;
|
||||
|
||||
for (const component of entity.components) {
|
||||
const typeName = component.constructor.name;
|
||||
componentTypeCounts.set(typeName, (componentTypeCounts.get(typeName) || 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 为每种组件类型计算详细内存(只计算一次,然后乘以数量)
|
||||
for (const [typeName, count] of componentTypeCounts.entries()) {
|
||||
const detailedMemoryPerInstance = this.componentCollector.calculateDetailedComponentMemory(typeName);
|
||||
const totalMemoryForType = detailedMemoryPerInstance * count;
|
||||
totalComponentMemory += totalMemoryForType;
|
||||
|
||||
// 收集该类型组件的实例信息(用于显示最大的几个实例)
|
||||
const instances: any[] = [];
|
||||
let instanceCount = 0;
|
||||
|
||||
for (const entity of entityList.buffer) {
|
||||
if (!entity || entity.destroyed || !entity.components) continue;
|
||||
|
||||
for (const component of entity.components) {
|
||||
if (component.constructor.name === typeName) {
|
||||
instances.push({
|
||||
entityId: entity.id,
|
||||
entityName: entity.name || `Entity_${entity.id}`,
|
||||
memory: detailedMemoryPerInstance // 使用统一的详细计算结果
|
||||
});
|
||||
instanceCount++;
|
||||
|
||||
// 限制收集的实例数量,避免过多数据
|
||||
if (instanceCount >= 100) break;
|
||||
}
|
||||
}
|
||||
if (instanceCount >= 100) break;
|
||||
}
|
||||
|
||||
componentStats.set(typeName, {
|
||||
count: count,
|
||||
totalMemory: totalMemoryForType,
|
||||
instances: instances.slice(0, 10) // 只保留前10个实例用于显示
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
const systemTypeMemoryCache = new Map<string, number>();
|
||||
|
||||
for (const system of entityProcessors.processors) {
|
||||
const systemTypeName = system.constructor.name;
|
||||
|
||||
let systemMemory: number;
|
||||
if (systemTypeMemoryCache.has(systemTypeName)) {
|
||||
systemMemory = systemTypeMemoryCache.get(systemTypeName)!;
|
||||
} else {
|
||||
systemMemory = this.calculateQuickSystemSize(system);
|
||||
systemTypeMemoryCache.set(systemTypeName, systemMemory);
|
||||
}
|
||||
|
||||
totalSystemMemory += systemMemory;
|
||||
|
||||
systemBreakdown.push({
|
||||
name: systemTypeName,
|
||||
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 calculateQuickSystemSize(system: any): number {
|
||||
if (!system || typeof system !== 'object') return 64;
|
||||
|
||||
let size = 128;
|
||||
|
||||
try {
|
||||
const keys = Object.keys(system);
|
||||
for (let i = 0; i < Math.min(keys.length, 15); i++) {
|
||||
const key = keys[i];
|
||||
if (key === 'entities' || key === 'scene' || key === 'constructor') continue;
|
||||
|
||||
const value = system[key];
|
||||
size += key.length * 2;
|
||||
|
||||
if (typeof value === 'string') {
|
||||
size += Math.min(value.length * 2, 100);
|
||||
} else if (typeof value === 'number') {
|
||||
size += 8;
|
||||
} else if (typeof value === 'boolean') {
|
||||
size += 4;
|
||||
} else if (Array.isArray(value)) {
|
||||
size += 40 + Math.min(value.length * 8, 200);
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
size += 64;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return 128;
|
||||
}
|
||||
|
||||
return Math.max(size, 64);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集对象池内存统计
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
1014
packages/core/src/Utils/Debug/EntityDataCollector.ts
Normal file
1014
packages/core/src/Utils/Debug/EntityDataCollector.ts
Normal file
File diff suppressed because it is too large
Load Diff
234
packages/core/src/Utils/Debug/PerformanceDataCollector.ts
Normal file
234
packages/core/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
packages/core/src/Utils/Debug/SceneDataCollector.ts
Normal file
50
packages/core/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
packages/core/src/Utils/Debug/SystemDataCollector.ts
Normal file
65
packages/core/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
packages/core/src/Utils/Debug/WebSocketManager.ts
Normal file
177
packages/core/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
packages/core/src/Utils/Debug/index.ts
Normal file
7
packages/core/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';
|
||||
81
packages/core/src/Utils/Emitter.ts
Normal file
81
packages/core/src/Utils/Emitter.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 用于包装事件的一个小类
|
||||
*/
|
||||
export class FuncPack<TContext = unknown> {
|
||||
/** 函数 */
|
||||
public func: Function;
|
||||
/** 上下文 */
|
||||
public context: TContext;
|
||||
|
||||
constructor(func: Function, context: TContext) {
|
||||
this.func = func;
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于事件管理
|
||||
*/
|
||||
export class Emitter<T, TContext = unknown> {
|
||||
private _messageTable: Map<T, FuncPack<TContext>[]>;
|
||||
|
||||
constructor() {
|
||||
this._messageTable = new Map<T, FuncPack<TContext>[]>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监听项
|
||||
* @param eventType 监听类型
|
||||
* @param handler 监听函数
|
||||
* @param context 监听上下文
|
||||
*/
|
||||
public addObserver(eventType: T, handler: Function, context: TContext) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (!list) {
|
||||
list = [];
|
||||
this._messageTable.set(eventType, list);
|
||||
}
|
||||
|
||||
if (!this.hasObserver(eventType, handler)) {
|
||||
list.push(new FuncPack<TContext>(handler, context));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听项
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public removeObserver(eventType: T, handler: Function) {
|
||||
let messageData = this._messageTable.get(eventType);
|
||||
if (messageData) {
|
||||
let index = messageData.findIndex(data => data.func == handler);
|
||||
if (index != -1)
|
||||
messageData.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发该事件
|
||||
* @param eventType 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
public emit<TData = unknown>(eventType: T, ...data: TData[]) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (list) {
|
||||
for (let observer of list) {
|
||||
observer.func.call(observer.context, ...data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在该类型的观察者
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public hasObserver(eventType: T, handler: Function): boolean {
|
||||
let list = this._messageTable.get(eventType);
|
||||
return list ? list.some(observer => observer.func === handler) : false;
|
||||
}
|
||||
}
|
||||
15
packages/core/src/Utils/Extensions/NumberExtension.ts
Normal file
15
packages/core/src/Utils/Extensions/NumberExtension.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 数字扩展工具类
|
||||
* 提供数字转换的实用方法
|
||||
*/
|
||||
export class NumberExtension {
|
||||
/**
|
||||
* 将值转换为数字
|
||||
* @param value 要转换的值
|
||||
* @returns 转换后的数字,如果值为undefined则返回0
|
||||
*/
|
||||
public static toNumber(value: unknown): number {
|
||||
if (value == undefined) return 0;
|
||||
return Number(value);
|
||||
}
|
||||
}
|
||||
14
packages/core/src/Utils/Extensions/TypeUtils.ts
Normal file
14
packages/core/src/Utils/Extensions/TypeUtils.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 类型工具类
|
||||
* 提供类型相关的实用方法
|
||||
*/
|
||||
export class TypeUtils {
|
||||
/**
|
||||
* 获取对象的类型
|
||||
* @param obj 对象
|
||||
* @returns 对象的构造函数
|
||||
*/
|
||||
public static getType(obj: any) {
|
||||
return obj.constructor;
|
||||
}
|
||||
}
|
||||
3
packages/core/src/Utils/Extensions/index.ts
Normal file
3
packages/core/src/Utils/Extensions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// 扩展工具类导出
|
||||
export { TypeUtils } from './TypeUtils';
|
||||
export { NumberExtension } from './NumberExtension';
|
||||
55
packages/core/src/Utils/GlobalManager.ts
Normal file
55
packages/core/src/Utils/GlobalManager.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 全局管理器的基类。所有全局管理器都应该从此类继承。
|
||||
*/
|
||||
export class GlobalManager {
|
||||
/**
|
||||
* 表示管理器是否启用
|
||||
*/
|
||||
public _enabled: boolean = false;
|
||||
|
||||
/**
|
||||
* 获取或设置管理器是否启用
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置管理器是否启用
|
||||
* @param isEnabled 如果为true,则启用管理器;否则禁用管理器
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
if (this._enabled) {
|
||||
// 如果启用了管理器,则调用onEnabled方法
|
||||
this.onEnabled();
|
||||
} else {
|
||||
// 如果禁用了管理器,则调用onDisabled方法
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在启用管理器时调用的回调方法
|
||||
*/
|
||||
protected onEnabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在禁用管理器时调用的回调方法
|
||||
*/
|
||||
protected onDisabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新管理器状态的方法
|
||||
*/
|
||||
public update() {
|
||||
}
|
||||
}
|
||||
403
packages/core/src/Utils/PerformanceMonitor.ts
Normal file
403
packages/core/src/Utils/PerformanceMonitor.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* 性能监控数据
|
||||
*/
|
||||
export interface PerformanceData {
|
||||
/** 系统名称 */
|
||||
name: string;
|
||||
/** 执行时间(毫秒) */
|
||||
executionTime: number;
|
||||
/** 处理的实体数量 */
|
||||
entityCount: number;
|
||||
/** 平均每个实体的处理时间 */
|
||||
averageTimePerEntity: number;
|
||||
/** 最后更新时间戳 */
|
||||
lastUpdateTime: number;
|
||||
/** 内存使用量(字节) */
|
||||
memoryUsage?: number;
|
||||
/** CPU使用率(百分比) */
|
||||
cpuUsage?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能统计信息
|
||||
*/
|
||||
export interface PerformanceStats {
|
||||
/** 总执行时间 */
|
||||
totalTime: number;
|
||||
/** 平均执行时间 */
|
||||
averageTime: number;
|
||||
/** 最小执行时间 */
|
||||
minTime: number;
|
||||
/** 最大执行时间 */
|
||||
maxTime: number;
|
||||
/** 执行次数 */
|
||||
executionCount: number;
|
||||
/** 最近的执行时间列表 */
|
||||
recentTimes: number[];
|
||||
/** 标准差 */
|
||||
standardDeviation: number;
|
||||
/** 95百分位数 */
|
||||
percentile95: number;
|
||||
/** 99百分位数 */
|
||||
percentile99: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能警告类型
|
||||
*/
|
||||
export enum PerformanceWarningType {
|
||||
HIGH_EXECUTION_TIME = 'high_execution_time',
|
||||
HIGH_MEMORY_USAGE = 'high_memory_usage',
|
||||
HIGH_CPU_USAGE = 'high_cpu_usage',
|
||||
FREQUENT_GC = 'frequent_gc',
|
||||
LOW_FPS = 'low_fps',
|
||||
HIGH_ENTITY_COUNT = 'high_entity_count'
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能警告
|
||||
*/
|
||||
export interface PerformanceWarning {
|
||||
type: PerformanceWarningType;
|
||||
systemName: string;
|
||||
message: string;
|
||||
severity: 'low' | 'medium' | 'high' | 'critical';
|
||||
timestamp: number;
|
||||
value: number;
|
||||
threshold: number;
|
||||
suggestion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能阈值配置
|
||||
*/
|
||||
export interface PerformanceThresholds {
|
||||
/** 执行时间阈值(毫秒) */
|
||||
executionTime: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
/** 内存使用阈值(MB) */
|
||||
memoryUsage: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
/** CPU使用率阈值(百分比) */
|
||||
cpuUsage: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
/** FPS阈值 */
|
||||
fps: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
/** 实体数量阈值 */
|
||||
entityCount: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 高性能监控器
|
||||
* 用于监控ECS系统的性能表现,提供详细的分析和优化建议
|
||||
*/
|
||||
export class PerformanceMonitor {
|
||||
private static _instance: PerformanceMonitor;
|
||||
|
||||
private _systemData = new Map<string, PerformanceData>();
|
||||
private _systemStats = new Map<string, PerformanceStats>();
|
||||
private _warnings: PerformanceWarning[] = [];
|
||||
private _isEnabled = false;
|
||||
private _maxRecentSamples = 60; // 保留最近60帧的数据
|
||||
private _maxWarnings = 100; // 最大警告数量
|
||||
|
||||
// 性能阈值配置
|
||||
private _thresholds: PerformanceThresholds = {
|
||||
executionTime: { warning: 16.67, critical: 33.33 }, // 60fps和30fps对应的帧时间
|
||||
memoryUsage: { warning: 100, critical: 200 }, // MB
|
||||
cpuUsage: { warning: 70, critical: 90 }, // 百分比
|
||||
fps: { warning: 45, critical: 30 },
|
||||
entityCount: { warning: 1000, critical: 5000 }
|
||||
};
|
||||
|
||||
// FPS监控
|
||||
private _fpsHistory: number[] = [];
|
||||
private _lastFrameTime = 0;
|
||||
private _frameCount = 0;
|
||||
private _fpsUpdateInterval = 1000; // 1秒更新一次FPS
|
||||
private _lastFpsUpdate = 0;
|
||||
private _currentFps = 60;
|
||||
|
||||
// 内存监控
|
||||
private _memoryCheckInterval = 5000; // 5秒检查一次内存
|
||||
private _lastMemoryCheck = 0;
|
||||
private _memoryHistory: number[] = [];
|
||||
|
||||
// GC监控
|
||||
private _gcCount = 0;
|
||||
private _lastGcCheck = 0;
|
||||
private _gcCheckInterval = 1000;
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
public static get instance(): PerformanceMonitor {
|
||||
if (!PerformanceMonitor._instance) {
|
||||
PerformanceMonitor._instance = new PerformanceMonitor();
|
||||
}
|
||||
return PerformanceMonitor._instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 启用性能监控
|
||||
*/
|
||||
public enable(): void {
|
||||
this._isEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用性能监控
|
||||
*/
|
||||
public disable(): void {
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否启用了性能监控
|
||||
*/
|
||||
public get isEnabled(): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监控系统性能
|
||||
* @param systemName 系统名称
|
||||
* @returns 开始时间戳
|
||||
*/
|
||||
public startMonitoring(systemName: string): number {
|
||||
if (!this._isEnabled) {
|
||||
return 0;
|
||||
}
|
||||
return performance.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束监控并记录性能数据
|
||||
* @param systemName 系统名称
|
||||
* @param startTime 开始时间戳
|
||||
* @param entityCount 处理的实体数量
|
||||
*/
|
||||
public endMonitoring(systemName: string, startTime: number, entityCount: number = 0): void {
|
||||
if (!this._isEnabled || startTime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const executionTime = endTime - startTime;
|
||||
const averageTimePerEntity = entityCount > 0 ? executionTime / entityCount : 0;
|
||||
|
||||
// 更新当前性能数据
|
||||
const data: PerformanceData = {
|
||||
name: systemName,
|
||||
executionTime,
|
||||
entityCount,
|
||||
averageTimePerEntity,
|
||||
lastUpdateTime: endTime
|
||||
};
|
||||
|
||||
this._systemData.set(systemName, data);
|
||||
|
||||
// 更新统计信息
|
||||
this.updateStats(systemName, executionTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统统计信息
|
||||
* @param systemName 系统名称
|
||||
* @param executionTime 执行时间
|
||||
*/
|
||||
private updateStats(systemName: string, executionTime: number): void {
|
||||
let stats = this._systemStats.get(systemName);
|
||||
|
||||
if (!stats) {
|
||||
stats = {
|
||||
totalTime: 0,
|
||||
averageTime: 0,
|
||||
minTime: Number.MAX_VALUE,
|
||||
maxTime: 0,
|
||||
executionCount: 0,
|
||||
recentTimes: [],
|
||||
standardDeviation: 0,
|
||||
percentile95: 0,
|
||||
percentile99: 0
|
||||
};
|
||||
this._systemStats.set(systemName, stats);
|
||||
}
|
||||
|
||||
// 更新基本统计
|
||||
stats.totalTime += executionTime;
|
||||
stats.executionCount++;
|
||||
stats.averageTime = stats.totalTime / stats.executionCount;
|
||||
stats.minTime = Math.min(stats.minTime, executionTime);
|
||||
stats.maxTime = Math.max(stats.maxTime, executionTime);
|
||||
|
||||
// 更新最近时间列表
|
||||
stats.recentTimes.push(executionTime);
|
||||
if (stats.recentTimes.length > this._maxRecentSamples) {
|
||||
stats.recentTimes.shift();
|
||||
}
|
||||
|
||||
// 计算高级统计信息
|
||||
this.calculateAdvancedStats(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算高级统计信息
|
||||
* @param stats 统计信息对象
|
||||
*/
|
||||
private calculateAdvancedStats(stats: PerformanceStats): void {
|
||||
if (stats.recentTimes.length === 0) return;
|
||||
|
||||
// 计算标准差
|
||||
const mean = stats.recentTimes.reduce((a, b) => a + b, 0) / stats.recentTimes.length;
|
||||
const variance = stats.recentTimes.reduce((acc, time) => acc + Math.pow(time - mean, 2), 0) / stats.recentTimes.length;
|
||||
stats.standardDeviation = Math.sqrt(variance);
|
||||
|
||||
// 计算百分位数
|
||||
const sortedTimes = [...stats.recentTimes].sort((a, b) => a - b);
|
||||
const len = sortedTimes.length;
|
||||
|
||||
stats.percentile95 = sortedTimes[Math.floor(len * 0.95)] || 0;
|
||||
stats.percentile99 = sortedTimes[Math.floor(len * 0.99)] || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的当前性能数据
|
||||
* @param systemName 系统名称
|
||||
* @returns 性能数据或undefined
|
||||
*/
|
||||
public getSystemData(systemName: string): PerformanceData | undefined {
|
||||
return this._systemData.get(systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的统计信息
|
||||
* @param systemName 系统名称
|
||||
* @returns 统计信息或undefined
|
||||
*/
|
||||
public getSystemStats(systemName: string): PerformanceStats | undefined {
|
||||
return this._systemStats.get(systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有系统的性能数据
|
||||
* @returns 所有系统的性能数据
|
||||
*/
|
||||
public getAllSystemData(): Map<string, PerformanceData> {
|
||||
return new Map(this._systemData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有系统的统计信息
|
||||
* @returns 所有系统的统计信息
|
||||
*/
|
||||
public getAllSystemStats(): Map<string, PerformanceStats> {
|
||||
return new Map(this._systemStats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性能报告
|
||||
* @returns 格式化的性能报告字符串
|
||||
*/
|
||||
public getPerformanceReport(): string {
|
||||
if (!this._isEnabled) {
|
||||
return "Performance monitoring is disabled.";
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push("=== ECS Performance Report ===");
|
||||
lines.push("");
|
||||
|
||||
// 按平均执行时间排序
|
||||
const sortedSystems = Array.from(this._systemStats.entries())
|
||||
.sort((a, b) => b[1].averageTime - a[1].averageTime);
|
||||
|
||||
for (const [systemName, stats] of sortedSystems) {
|
||||
const data = this._systemData.get(systemName);
|
||||
|
||||
lines.push(`System: ${systemName}`);
|
||||
lines.push(` Current: ${data?.executionTime.toFixed(2)}ms (${data?.entityCount} entities)`);
|
||||
lines.push(` Average: ${stats.averageTime.toFixed(2)}ms`);
|
||||
lines.push(` Min/Max: ${stats.minTime.toFixed(2)}ms / ${stats.maxTime.toFixed(2)}ms`);
|
||||
lines.push(` Total: ${stats.totalTime.toFixed(2)}ms (${stats.executionCount} calls)`);
|
||||
|
||||
if (data?.averageTimePerEntity && data.averageTimePerEntity > 0) {
|
||||
lines.push(` Per Entity: ${data.averageTimePerEntity.toFixed(4)}ms`);
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// 总体统计
|
||||
const totalCurrentTime = Array.from(this._systemData.values())
|
||||
.reduce((sum, data) => sum + data.executionTime, 0);
|
||||
|
||||
lines.push(`Total Frame Time: ${totalCurrentTime.toFixed(2)}ms`);
|
||||
lines.push(`Systems Count: ${this._systemData.size}`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置所有性能数据
|
||||
*/
|
||||
public reset(): void {
|
||||
this._systemData.clear();
|
||||
this._systemStats.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置指定系统的性能数据
|
||||
* @param systemName 系统名称
|
||||
*/
|
||||
public resetSystem(systemName: string): void {
|
||||
this._systemData.delete(systemName);
|
||||
this._systemStats.delete(systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性能警告
|
||||
* @param thresholdMs 警告阈值(毫秒)
|
||||
* @returns 超过阈值的系统列表
|
||||
*/
|
||||
public getPerformanceWarnings(thresholdMs: number = 16.67): string[] {
|
||||
const warnings: string[] = [];
|
||||
|
||||
for (const [systemName, data] of this._systemData.entries()) {
|
||||
if (data.executionTime > thresholdMs) {
|
||||
warnings.push(`${systemName}: ${data.executionTime.toFixed(2)}ms (>${thresholdMs}ms)`);
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大保留样本数
|
||||
* @param maxSamples 最大样本数
|
||||
*/
|
||||
public setMaxRecentSamples(maxSamples: number): void {
|
||||
this._maxRecentSamples = maxSamples;
|
||||
|
||||
// 裁剪现有数据
|
||||
for (const stats of this._systemStats.values()) {
|
||||
while (stats.recentTimes.length > maxSamples) {
|
||||
stats.recentTimes.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
565
packages/core/src/Utils/Pool.ts
Normal file
565
packages/core/src/Utils/Pool.ts
Normal file
@@ -0,0 +1,565 @@
|
||||
/**
|
||||
* 可池化对象接口
|
||||
*/
|
||||
export interface IPoolable {
|
||||
/**
|
||||
* 重置对象状态,准备重用
|
||||
*/
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象池统计信息
|
||||
*/
|
||||
export interface PoolStats {
|
||||
/** 池中对象数量 */
|
||||
size: number;
|
||||
/** 池的最大大小 */
|
||||
maxSize: number;
|
||||
/** 总共创建的对象数量 */
|
||||
totalCreated: number;
|
||||
/** 总共获取的次数 */
|
||||
totalObtained: number;
|
||||
/** 总共释放的次数 */
|
||||
totalReleased: number;
|
||||
/** 命中率(从池中获取的比例) */
|
||||
hitRate: number;
|
||||
/** 内存使用估算(字节) */
|
||||
estimatedMemoryUsage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高性能通用对象池
|
||||
* 支持任意类型的对象池化,包含详细的统计信息
|
||||
*/
|
||||
export class Pool<T extends IPoolable> {
|
||||
private static _pools = new Map<Function, Pool<any>>();
|
||||
|
||||
private _objects: T[] = [];
|
||||
private _createFn: () => T;
|
||||
private _maxSize: number;
|
||||
private _stats: PoolStats;
|
||||
private _objectSize: number; // 估算的单个对象大小
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param createFn 创建对象的函数
|
||||
* @param maxSize 池的最大大小,默认100
|
||||
* @param estimatedObjectSize 估算的单个对象大小(字节),默认1024
|
||||
*/
|
||||
constructor(createFn: () => T, maxSize: number = 100, estimatedObjectSize: number = 1024) {
|
||||
this._createFn = createFn;
|
||||
this._maxSize = maxSize;
|
||||
this._objectSize = estimatedObjectSize;
|
||||
this._stats = {
|
||||
size: 0,
|
||||
maxSize,
|
||||
totalCreated: 0,
|
||||
totalObtained: 0,
|
||||
totalReleased: 0,
|
||||
hitRate: 0,
|
||||
estimatedMemoryUsage: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的对象池
|
||||
* @param type 对象类型
|
||||
* @param maxSize 池的最大大小
|
||||
* @param estimatedObjectSize 估算的单个对象大小
|
||||
* @returns 对象池实例
|
||||
*/
|
||||
public static getPool<T extends IPoolable>(
|
||||
type: new (...args: unknown[]) => T,
|
||||
maxSize: number = 100,
|
||||
estimatedObjectSize: number = 1024
|
||||
): Pool<T> {
|
||||
let pool = this._pools.get(type);
|
||||
|
||||
if (!pool) {
|
||||
pool = new Pool<T>(() => new type(), maxSize, estimatedObjectSize);
|
||||
this._pools.set(type, pool);
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从池中获取对象
|
||||
* @returns 对象实例
|
||||
*/
|
||||
public obtain(): T {
|
||||
this._stats.totalObtained++;
|
||||
|
||||
if (this._objects.length > 0) {
|
||||
const obj = this._objects.pop()!;
|
||||
this._stats.size--;
|
||||
this._updateHitRate();
|
||||
this._updateMemoryUsage();
|
||||
return obj;
|
||||
}
|
||||
|
||||
// 池中没有对象,创建新的
|
||||
const obj = this._createFn();
|
||||
this._stats.totalCreated++;
|
||||
this._updateHitRate();
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象归还到池中
|
||||
* @param obj 要归还的对象
|
||||
*/
|
||||
public free(obj: T): void {
|
||||
if (this._objects.length < this._maxSize) {
|
||||
obj.reset();
|
||||
this._objects.push(obj);
|
||||
this._stats.size++;
|
||||
this._stats.totalReleased++;
|
||||
this._updateMemoryUsage();
|
||||
}
|
||||
// 如果池已满,对象会被丢弃(由GC回收)
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热池,创建指定数量的对象
|
||||
* @param count 要创建的对象数量
|
||||
*/
|
||||
public warmUp(count: number): void {
|
||||
const targetSize = Math.min(count, this._maxSize);
|
||||
|
||||
while (this._objects.length < targetSize) {
|
||||
const obj = this._createFn();
|
||||
this._stats.totalCreated++;
|
||||
this._objects.push(obj);
|
||||
this._stats.size++;
|
||||
}
|
||||
|
||||
this._updateMemoryUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空池
|
||||
*/
|
||||
public clear(): void {
|
||||
this._objects.length = 0;
|
||||
this._stats.size = 0;
|
||||
this._updateMemoryUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池中对象数量
|
||||
*/
|
||||
public get size(): number {
|
||||
return this._objects.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池的最大大小
|
||||
*/
|
||||
public get maxSize(): number {
|
||||
return this._maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置池的最大大小
|
||||
*/
|
||||
public set maxSize(value: number) {
|
||||
this._maxSize = value;
|
||||
this._stats.maxSize = value;
|
||||
|
||||
// 如果当前池大小超过新的最大值,则移除多余的对象
|
||||
while (this._objects.length > this._maxSize) {
|
||||
this._objects.pop();
|
||||
this._stats.size--;
|
||||
}
|
||||
|
||||
this._updateMemoryUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池的统计信息
|
||||
*/
|
||||
public getStats(): PoolStats {
|
||||
return { ...this._stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this._stats.totalCreated = 0;
|
||||
this._stats.totalObtained = 0;
|
||||
this._stats.totalReleased = 0;
|
||||
this._stats.hitRate = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新命中率
|
||||
*/
|
||||
private _updateHitRate(): void {
|
||||
if (this._stats.totalObtained > 0) {
|
||||
const hits = this._stats.totalObtained - this._stats.totalCreated;
|
||||
this._stats.hitRate = hits / this._stats.totalObtained;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新内存使用估算
|
||||
*/
|
||||
private _updateMemoryUsage(): void {
|
||||
this._stats.estimatedMemoryUsage = this._stats.size * this._objectSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:从指定类型的池中获取对象
|
||||
* @param type 对象类型
|
||||
* @returns 对象实例
|
||||
*/
|
||||
public static obtain<T extends IPoolable>(type: new (...args: unknown[]) => T): T {
|
||||
return this.getPool(type).obtain();
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:将对象归还到对应类型的池中
|
||||
* @param type 对象类型
|
||||
* @param obj 要归还的对象
|
||||
*/
|
||||
public static free<T extends IPoolable>(type: new (...args: unknown[]) => T, obj: T): void {
|
||||
this.getPool(type).free(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:预热指定类型的池
|
||||
* @param type 对象类型
|
||||
* @param count 要创建的对象数量
|
||||
*/
|
||||
public static warmUp<T extends IPoolable>(type: new (...args: unknown[]) => T, count: number): void {
|
||||
this.getPool(type).warmUp(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:清空指定类型的池
|
||||
* @param type 对象类型
|
||||
*/
|
||||
public static clearPool<T extends IPoolable>(type: new (...args: unknown[]) => T): void {
|
||||
const pool = this._pools.get(type);
|
||||
if (pool) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:清空所有池
|
||||
*/
|
||||
public static clearAllPools(): void {
|
||||
for (const pool of this._pools.values()) {
|
||||
pool.clear();
|
||||
}
|
||||
this._pools.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:获取池的统计信息
|
||||
* @returns 池的统计信息
|
||||
*/
|
||||
public static getStats(): { [typeName: string]: PoolStats } {
|
||||
const stats: { [typeName: string]: PoolStats } = {};
|
||||
|
||||
for (const [type, pool] of this._pools.entries()) {
|
||||
const typeName = (type as any).name || 'Unknown';
|
||||
stats[typeName] = pool.getStats();
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:获取所有池的总内存使用量
|
||||
* @returns 总内存使用量(字节)
|
||||
*/
|
||||
public static getTotalMemoryUsage(): number {
|
||||
let total = 0;
|
||||
for (const pool of this._pools.values()) {
|
||||
total += pool.getStats().estimatedMemoryUsage;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:获取性能报告
|
||||
* @returns 格式化的性能报告
|
||||
*/
|
||||
public static getPerformanceReport(): string {
|
||||
const stats = this.getStats();
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push('=== Object Pool Performance Report ===');
|
||||
lines.push(`Total Memory Usage: ${(this.getTotalMemoryUsage() / 1024 / 1024).toFixed(2)} MB`);
|
||||
lines.push('');
|
||||
|
||||
for (const [typeName, stat] of Object.entries(stats)) {
|
||||
lines.push(`${typeName}:`);
|
||||
lines.push(` Size: ${stat.size}/${stat.maxSize}`);
|
||||
lines.push(` Hit Rate: ${(stat.hitRate * 100).toFixed(1)}%`);
|
||||
lines.push(` Total Created: ${stat.totalCreated}`);
|
||||
lines.push(` Total Obtained: ${stat.totalObtained}`);
|
||||
lines.push(` Memory: ${(stat.estimatedMemoryUsage / 1024).toFixed(1)} KB`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分层对象池
|
||||
* 使用多个不同大小的池来优化内存使用
|
||||
*/
|
||||
export class TieredObjectPool<T extends IPoolable> {
|
||||
private pools: Pool<T>[] = [];
|
||||
private createFn: () => T;
|
||||
private resetFn: (obj: T) => void;
|
||||
private tierSizes: number[];
|
||||
private totalObtained = 0;
|
||||
private totalReleased = 0;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param createFn 创建对象的函数
|
||||
* @param resetFn 重置对象的函数
|
||||
* @param tierSizes 各层级的大小,默认[10, 50, 200]
|
||||
* @param estimatedObjectSize 估算的单个对象大小
|
||||
*/
|
||||
constructor(
|
||||
createFn: () => T,
|
||||
resetFn: (obj: T) => void,
|
||||
tierSizes: number[] = [10, 50, 200],
|
||||
estimatedObjectSize: number = 1024
|
||||
) {
|
||||
this.createFn = createFn;
|
||||
this.resetFn = resetFn;
|
||||
this.tierSizes = tierSizes;
|
||||
|
||||
// 初始化不同层级的池
|
||||
for (const size of tierSizes) {
|
||||
this.pools.push(new Pool(createFn, size, estimatedObjectSize));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象
|
||||
* @returns 对象实例
|
||||
*/
|
||||
public obtain(): T {
|
||||
this.totalObtained++;
|
||||
|
||||
// 从最小的池开始尝试获取
|
||||
for (const pool of this.pools) {
|
||||
if (pool.size > 0) {
|
||||
return pool.obtain();
|
||||
}
|
||||
}
|
||||
|
||||
// 所有池都空了,创建新对象
|
||||
return this.createFn();
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放对象
|
||||
* @param obj 要释放的对象
|
||||
*/
|
||||
public release(obj: T): void {
|
||||
this.totalReleased++;
|
||||
this.resetFn(obj);
|
||||
|
||||
// 放入第一个有空间的池
|
||||
for (const pool of this.pools) {
|
||||
if (pool.size < pool.maxSize) {
|
||||
pool.free(obj);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 所有池都满了,直接丢弃
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热所有池
|
||||
* @param totalCount 总预热数量
|
||||
*/
|
||||
public warmUp(totalCount: number): void {
|
||||
let remaining = totalCount;
|
||||
|
||||
for (const pool of this.pools) {
|
||||
const warmUpCount = Math.min(remaining, pool.maxSize);
|
||||
pool.warmUp(warmUpCount);
|
||||
remaining -= warmUpCount;
|
||||
|
||||
if (remaining <= 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有池
|
||||
*/
|
||||
public clear(): void {
|
||||
for (const pool of this.pools) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
totalSize: number;
|
||||
totalMaxSize: number;
|
||||
totalMemoryUsage: number;
|
||||
tierStats: PoolStats[];
|
||||
hitRate: number;
|
||||
} {
|
||||
let totalSize = 0;
|
||||
let totalMaxSize = 0;
|
||||
let totalMemoryUsage = 0;
|
||||
const tierStats: PoolStats[] = [];
|
||||
|
||||
for (const pool of this.pools) {
|
||||
const stats = pool.getStats();
|
||||
tierStats.push(stats);
|
||||
totalSize += stats.size;
|
||||
totalMaxSize += stats.maxSize;
|
||||
totalMemoryUsage += stats.estimatedMemoryUsage;
|
||||
}
|
||||
|
||||
const hitRate = this.totalObtained > 0 ?
|
||||
(this.totalObtained - this.getTotalCreated()) / this.totalObtained : 0;
|
||||
|
||||
return {
|
||||
totalSize,
|
||||
totalMaxSize,
|
||||
totalMemoryUsage,
|
||||
tierStats,
|
||||
hitRate
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总创建数量
|
||||
*/
|
||||
private getTotalCreated(): number {
|
||||
return this.pools.reduce((total, pool) => total + pool.getStats().totalCreated, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 池管理器
|
||||
* 统一管理所有对象池
|
||||
*/
|
||||
export class PoolManager {
|
||||
private static instance: PoolManager;
|
||||
private pools = new Map<string, Pool<any> | TieredObjectPool<any>>();
|
||||
private autoCompactInterval = 60000; // 60秒
|
||||
private lastCompactTime = 0;
|
||||
|
||||
public static getInstance(): PoolManager {
|
||||
if (!PoolManager.instance) {
|
||||
PoolManager.instance = new PoolManager();
|
||||
}
|
||||
return PoolManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册池
|
||||
* @param name 池名称
|
||||
* @param pool 池实例
|
||||
*/
|
||||
public registerPool<T extends IPoolable>(name: string, pool: Pool<T> | TieredObjectPool<T>): void {
|
||||
this.pools.set(name, pool);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池
|
||||
* @param name 池名称
|
||||
* @returns 池实例
|
||||
*/
|
||||
public getPool<T extends IPoolable>(name: string): Pool<T> | TieredObjectPool<T> | null {
|
||||
return this.pools.get(name) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新池管理器(应在游戏循环中调用)
|
||||
*/
|
||||
public update(): void {
|
||||
const now = Date.now();
|
||||
|
||||
if (now - this.lastCompactTime > this.autoCompactInterval) {
|
||||
this.compactAllPools();
|
||||
this.lastCompactTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩所有池(清理碎片)
|
||||
*/
|
||||
public compactAllPools(): void {
|
||||
// 对于标准池,可以考虑清理一些长时间未使用的对象
|
||||
// 这里简单实现为重置统计信息
|
||||
for (const pool of this.pools.values()) {
|
||||
if (pool instanceof Pool) {
|
||||
pool.resetStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的统计信息
|
||||
*/
|
||||
public getAllStats(): Map<string, any> {
|
||||
const stats = new Map<string, any>();
|
||||
|
||||
for (const [name, pool] of this.pools.entries()) {
|
||||
if (pool instanceof Pool) {
|
||||
stats.set(name, pool.getStats());
|
||||
} else if (pool instanceof TieredObjectPool) {
|
||||
stats.set(name, pool.getStats());
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成性能报告
|
||||
*/
|
||||
public generateReport(): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('=== Pool Manager Report ===');
|
||||
|
||||
let totalMemory = 0;
|
||||
|
||||
for (const [name, pool] of this.pools.entries()) {
|
||||
lines.push(`\n${name}:`);
|
||||
|
||||
if (pool instanceof Pool) {
|
||||
const stats = pool.getStats();
|
||||
lines.push(` Type: Standard Pool`);
|
||||
lines.push(` Size: ${stats.size}/${stats.maxSize}`);
|
||||
lines.push(` Hit Rate: ${(stats.hitRate * 100).toFixed(1)}%`);
|
||||
lines.push(` Memory: ${(stats.estimatedMemoryUsage / 1024).toFixed(1)} KB`);
|
||||
totalMemory += stats.estimatedMemoryUsage;
|
||||
} else if (pool instanceof TieredObjectPool) {
|
||||
const stats = pool.getStats();
|
||||
lines.push(` Type: Tiered Pool`);
|
||||
lines.push(` Total Size: ${stats.totalSize}/${stats.totalMaxSize}`);
|
||||
lines.push(` Hit Rate: ${(stats.hitRate * 100).toFixed(1)}%`);
|
||||
lines.push(` Memory: ${(stats.totalMemoryUsage / 1024).toFixed(1)} KB`);
|
||||
totalMemory += stats.totalMemoryUsage;
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(`\nTotal Memory Usage: ${(totalMemory / 1024 / 1024).toFixed(2)} MB`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
73
packages/core/src/Utils/Time.ts
Normal file
73
packages/core/src/Utils/Time.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 时间管理工具类
|
||||
* 提供游戏时间相关的功能,包括帧时间、总时间、时间缩放等
|
||||
*/
|
||||
export class Time {
|
||||
/**
|
||||
* 上一帧到当前帧的时间间隔(秒)
|
||||
*/
|
||||
public static deltaTime: number = 0;
|
||||
|
||||
/**
|
||||
* 未缩放的帧时间间隔(秒)
|
||||
*/
|
||||
public static unscaledDeltaTime: number = 0;
|
||||
|
||||
/**
|
||||
* 游戏开始以来的总时间(秒)
|
||||
*/
|
||||
public static totalTime: number = 0;
|
||||
|
||||
/**
|
||||
* 未缩放的总时间(秒)
|
||||
*/
|
||||
public static unscaledTotalTime: number = 0;
|
||||
|
||||
/**
|
||||
* 时间缩放比例
|
||||
*/
|
||||
public static timeScale: number = 1;
|
||||
|
||||
/**
|
||||
* 当前帧数
|
||||
*/
|
||||
public static frameCount: number = 0;
|
||||
|
||||
/**
|
||||
* 使用外部引擎提供的deltaTime更新时间信息
|
||||
* @param deltaTime 外部引擎提供的帧时间间隔(秒)
|
||||
*/
|
||||
public static update(deltaTime: number): void {
|
||||
// 设置未缩放的帧时间
|
||||
this.unscaledDeltaTime = deltaTime;
|
||||
this.deltaTime = deltaTime * this.timeScale;
|
||||
|
||||
// 更新总时间
|
||||
this.unscaledTotalTime += this.unscaledDeltaTime;
|
||||
this.totalTime += this.deltaTime;
|
||||
|
||||
// 更新帧数
|
||||
this.frameCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景改变时重置时间
|
||||
*/
|
||||
public static sceneChanged(): void {
|
||||
this.frameCount = 0;
|
||||
this.totalTime = 0;
|
||||
this.unscaledTotalTime = 0;
|
||||
this.deltaTime = 0;
|
||||
this.unscaledDeltaTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定的时间间隔是否已经过去
|
||||
* @param interval 时间间隔(秒)
|
||||
* @param lastTime 上次检查的时间
|
||||
* @returns 是否已经过去指定时间
|
||||
*/
|
||||
public static checkEvery(interval: number, lastTime: number): boolean {
|
||||
return this.totalTime - lastTime >= interval;
|
||||
}
|
||||
}
|
||||
18
packages/core/src/Utils/Timers/ITimer.ts
Normal file
18
packages/core/src/Utils/Timers/ITimer.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface ITimer<TContext = unknown> {
|
||||
context: TContext;
|
||||
|
||||
/**
|
||||
* 调用stop以停止此计时器再次运行。这对非重复计时器没有影响。
|
||||
*/
|
||||
stop(): void;
|
||||
|
||||
/**
|
||||
* 将计时器的运行时间重置为0
|
||||
*/
|
||||
reset(): void;
|
||||
|
||||
/**
|
||||
* 返回投向T的上下文,作为方便
|
||||
*/
|
||||
getContext<T>(): T;
|
||||
}
|
||||
70
packages/core/src/Utils/Timers/Timer.ts
Normal file
70
packages/core/src/Utils/Timers/Timer.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ITimer } from './ITimer';
|
||||
import { Time } from '../Time';
|
||||
|
||||
/**
|
||||
* 私有类隐藏ITimer的实现
|
||||
*/
|
||||
export class Timer<TContext = unknown> implements ITimer<TContext>{
|
||||
public context!: TContext;
|
||||
public _timeInSeconds: number = 0;
|
||||
public _repeats: boolean = false;
|
||||
public _onTime!: (timer: ITimer<TContext>) => void;
|
||||
public _isDone: boolean = false;
|
||||
public _elapsedTime: number = 0;
|
||||
|
||||
public getContext<T>(): T {
|
||||
return this.context as unknown as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时器是否已完成
|
||||
*/
|
||||
public get isDone(): boolean {
|
||||
return this._isDone;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时器已运行的时间
|
||||
*/
|
||||
public get elapsedTime(): number {
|
||||
return this._elapsedTime;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._elapsedTime = 0;
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
this._isDone = true;
|
||||
}
|
||||
|
||||
public tick(){
|
||||
// 如果stop在tick之前被调用,那么isDone将为true,我们不应该再做任何事情
|
||||
if (!this._isDone && this._elapsedTime > this._timeInSeconds){
|
||||
this._elapsedTime -= this._timeInSeconds;
|
||||
this._onTime(this);
|
||||
|
||||
if (!this._isDone && !this._repeats)
|
||||
this._isDone = true;
|
||||
}
|
||||
|
||||
this._elapsedTime += Time.deltaTime;
|
||||
|
||||
return this._isDone;
|
||||
}
|
||||
|
||||
public initialize(timeInsSeconds: number, repeats: boolean, context: TContext, onTime: (timer: ITimer<TContext>)=>void){
|
||||
this._timeInSeconds = timeInsSeconds;
|
||||
this._repeats = repeats;
|
||||
this.context = context;
|
||||
this._onTime = onTime.bind(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 空出对象引用,以便在js需要时GC可以清理它们的引用
|
||||
*/
|
||||
public unload(){
|
||||
this.context = null as unknown as TContext;
|
||||
this._onTime = null!;
|
||||
}
|
||||
}
|
||||
34
packages/core/src/Utils/Timers/TimerManager.ts
Normal file
34
packages/core/src/Utils/Timers/TimerManager.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { GlobalManager } from '../GlobalManager';
|
||||
import { Timer } from './Timer';
|
||||
import { ITimer } from './ITimer';
|
||||
|
||||
/**
|
||||
* 允许动作的延迟和重复执行
|
||||
*/
|
||||
export class TimerManager extends GlobalManager {
|
||||
public _timers: Array<Timer<unknown>> = [];
|
||||
|
||||
public override update() {
|
||||
for (let i = this._timers.length - 1; i >= 0; i --){
|
||||
if (this._timers[i].tick()){
|
||||
this._timers[i].unload();
|
||||
this._timers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度一个一次性或重复的计时器,该计时器将调用已传递的动作
|
||||
* @param timeInSeconds
|
||||
* @param repeats
|
||||
* @param context
|
||||
* @param onTime
|
||||
*/
|
||||
public schedule<TContext = unknown>(timeInSeconds: number, repeats: boolean, context: TContext, onTime: (timer: ITimer<TContext>)=>void): Timer<TContext> {
|
||||
let timer = new Timer<TContext>();
|
||||
timer.initialize(timeInSeconds, repeats, context, onTime);
|
||||
this._timers.push(timer as Timer<unknown>);
|
||||
|
||||
return timer;
|
||||
}
|
||||
}
|
||||
7
packages/core/src/Utils/index.ts
Normal file
7
packages/core/src/Utils/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './Extensions';
|
||||
export * from './Pool';
|
||||
export * from './Emitter';
|
||||
export * from './GlobalManager';
|
||||
export * from './PerformanceMonitor';
|
||||
export { Time } from './Time';
|
||||
export * from './Debug';
|
||||
Reference in New Issue
Block a user