refactor: reorganize package structure and decouple framework packages (#338)

* refactor: reorganize package structure and decouple framework packages

## Package Structure Reorganization
- Reorganized 55 packages into categorized subdirectories:
  - packages/framework/ - Generic framework (Laya/Cocos compatible)
  - packages/engine/ - ESEngine core modules
  - packages/rendering/ - Rendering modules (WASM dependent)
  - packages/physics/ - Physics modules
  - packages/streaming/ - World streaming
  - packages/network-ext/ - Network extensions
  - packages/editor/ - Editor framework and plugins
  - packages/rust/ - Rust WASM engine
  - packages/tools/ - Build tools and SDK

## Framework Package Decoupling
- Decoupled behavior-tree and blueprint packages from ESEngine dependencies
- Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent)
- ESEngine-specific code moved to esengine/ subpath exports
- Framework packages now usable with Cocos/Laya without ESEngine

## CI Configuration
- Updated CI to only type-check and lint framework packages
- Added type-check:framework and lint:framework scripts

## Breaking Changes
- Package import paths changed due to directory reorganization
- ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine')

* fix: update es-engine file path after directory reorganization

* docs: update README to focus on framework over engine

* ci: only build framework packages, remove Rust/WASM dependencies

* fix: remove esengine subpath from behavior-tree and blueprint builds

ESEngine integration code will only be available in full engine builds.
Framework packages are now purely engine-agnostic.

* fix: move network-protocols to framework, build both in CI

* fix: update workflow paths from packages/core to packages/framework/core

* fix: exclude esengine folder from type-check in behavior-tree and blueprint

* fix: update network tsconfig references to new paths

* fix: add test:ci:framework to only test framework packages in CI

* fix: only build core and math npm packages in CI

* fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
YHH
2025-12-26 14:50:35 +08:00
committed by GitHub
parent a84ff902e4
commit 155411e743
1936 changed files with 4147 additions and 11578 deletions

View File

@@ -0,0 +1,104 @@
/**
* 二进制序列化器
* 将对象转换为UTF8字节数组
*/
export class BinarySerializer {
/**
* 将字符串编码为UTF8字节数组
*/
private static stringToUtf8(str: string): Uint8Array {
const len = str.length;
let pos = 0;
const bytes: number[] = [];
for (let i = 0; i < len; i++) {
let code = str.charCodeAt(i);
if (code >= 0xD800 && code <= 0xDBFF && i + 1 < len) {
const high = code;
const low = str.charCodeAt(i + 1);
if (low >= 0xDC00 && low <= 0xDFFF) {
code = 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00);
i++;
}
}
if (code < 0x80) {
bytes[pos++] = code;
} else if (code < 0x800) {
bytes[pos++] = 0xC0 | (code >> 6);
bytes[pos++] = 0x80 | (code & 0x3F);
} else if (code < 0x10000) {
bytes[pos++] = 0xE0 | (code >> 12);
bytes[pos++] = 0x80 | ((code >> 6) & 0x3F);
bytes[pos++] = 0x80 | (code & 0x3F);
} else {
bytes[pos++] = 0xF0 | (code >> 18);
bytes[pos++] = 0x80 | ((code >> 12) & 0x3F);
bytes[pos++] = 0x80 | ((code >> 6) & 0x3F);
bytes[pos++] = 0x80 | (code & 0x3F);
}
}
return new Uint8Array(bytes);
}
/**
* 将UTF8字节数组解码为字符串
*/
private static utf8ToString(bytes: Uint8Array): string {
const len = bytes.length;
let str = '';
let i = 0;
while (i < len) {
const byte1 = bytes[i++];
if (byte1 === undefined) break;
if (byte1 < 0x80) {
str += String.fromCharCode(byte1);
} else if ((byte1 & 0xE0) === 0xC0) {
const byte2 = bytes[i++];
if (byte2 === undefined) break;
str += String.fromCharCode(((byte1 & 0x1F) << 6) | (byte2 & 0x3F));
} else if ((byte1 & 0xF0) === 0xE0) {
const byte2 = bytes[i++];
const byte3 = bytes[i++];
if (byte2 === undefined || byte3 === undefined) break;
str += String.fromCharCode(
((byte1 & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F)
);
} else if ((byte1 & 0xF8) === 0xF0) {
const byte2 = bytes[i++];
const byte3 = bytes[i++];
const byte4 = bytes[i++];
if (byte2 === undefined || byte3 === undefined || byte4 === undefined) break;
let code = ((byte1 & 0x07) << 18) | ((byte2 & 0x3F) << 12) |
((byte3 & 0x3F) << 6) | (byte4 & 0x3F);
code -= 0x10000;
str += String.fromCharCode(
0xD800 + (code >> 10),
0xDC00 + (code & 0x3FF)
);
}
}
return str;
}
/**
* 将对象编码为二进制数据
*/
public static encode(value: any): Uint8Array {
const jsonString = JSON.stringify(value);
return this.stringToUtf8(jsonString);
}
/**
* 将二进制数据解码为对象
*/
public static decode(bytes: Uint8Array): any {
const jsonString = this.utf8ToString(bytes);
return JSON.parse(jsonString);
}
}

View File

@@ -0,0 +1,606 @@
/**
* 高级性能分析数据收集器
*
* 整合 ProfilerSDK 和现有 PerformanceMonitor 的数据,
* 提供统一的高级性能分析数据接口
*/
import { ProfilerSDK } from '../Profiler/ProfilerSDK';
import {
ProfileCategory,
ProfileFrame,
ProfileReport,
MemorySnapshot
} from '../Profiler/ProfilerTypes';
import { Time } from '../Time';
/**
* 旧版 PerformanceMonitor 接口 (用于兼容)
*/
export type ILegacyPerformanceMonitor = {
getAllSystemStats?: () => Map<string, {
averageTime: number;
minTime?: number;
maxTime?: number;
executionCount?: number;
}>;
getAllSystemData?: () => Map<string, {
executionTime: number;
entityCount?: number;
}>;
}
/**
* 热点函数项(支持递归层级)
*/
export type IHotspotItem = {
name: string;
category: string;
inclusiveTime: number;
inclusiveTimePercent: number;
exclusiveTime: number;
exclusiveTimePercent: number;
callCount: number;
avgCallTime: number;
/** 层级深度 */
depth: number;
/** 子函数 */
children?: IHotspotItem[] | undefined;
}
/**
* 高级性能数据接口
*/
export type IAdvancedProfilerData = {
/** 当前帧信息 */
currentFrame: {
frameNumber: number;
frameTime: number;
fps: number;
memory: MemorySnapshot;
};
/** 帧时间历史 (用于绘制图表) */
frameTimeHistory: Array<{
frameNumber: number;
time: number;
duration: number;
}>;
/** 按类别分组的统计 */
categoryStats: Array<{
category: string;
totalTime: number;
percentOfFrame: number;
sampleCount: number;
expanded?: boolean;
items: Array<{
name: string;
inclusiveTime: number;
exclusiveTime: number;
callCount: number;
percentOfCategory: number;
percentOfFrame: number;
}>;
}>;
/** 热点函数列表(支持层级) */
hotspots: IHotspotItem[];
/** 调用关系数据 */
callGraph: {
/** 当前选中的函数 */
currentFunction: string | null;
/** 调用当前函数的函数列表 */
callers: Array<{
name: string;
callCount: number;
totalTime: number;
percentOfCurrent: number;
}>;
/** 当前函数调用的函数列表 */
callees: Array<{
name: string;
callCount: number;
totalTime: number;
percentOfCurrent: number;
}>;
};
/** 长任务列表 */
longTasks: Array<{
startTime: number;
duration: number;
attribution: string[];
}>;
/** 内存趋势 */
memoryTrend: Array<{
time: number;
usedMB: number;
totalMB: number;
gcCount: number;
}>;
/** 统计摘要 */
summary: {
totalFrames: number;
averageFrameTime: number;
minFrameTime: number;
maxFrameTime: number;
p95FrameTime: number;
p99FrameTime: number;
currentMemoryMB: number;
peakMemoryMB: number;
gcCount: number;
longTaskCount: number;
};
}
/**
* 高级性能分析数据收集器
*/
export class AdvancedProfilerCollector {
private selectedFunction: string | null = null;
private peakMemory = 0;
constructor() {
// ProfilerSDK 通过静态方法访问
}
/**
* 设置选中的函数(用于调用关系视图)
*/
public setSelectedFunction(name: string | null): void {
this.selectedFunction = name;
}
/**
* 收集高级性能数据
*/
public collectAdvancedData(performanceMonitor?: ILegacyPerformanceMonitor): IAdvancedProfilerData {
const frameHistory = ProfilerSDK.getFrameHistory();
const currentFrame = ProfilerSDK.getCurrentFrame();
const report = ProfilerSDK.getReport(300);
const currentMemory = currentFrame?.memory || this.getDefaultMemory();
if (currentMemory.usedHeapSize > this.peakMemory) {
this.peakMemory = currentMemory.usedHeapSize;
}
return {
currentFrame: this.buildCurrentFrameData(currentFrame),
frameTimeHistory: this.buildFrameTimeHistory(frameHistory),
categoryStats: this.buildCategoryStats(currentFrame, performanceMonitor),
hotspots: this.buildHotspots(report),
callGraph: this.buildCallGraph(report),
longTasks: report.longTasks,
memoryTrend: this.buildMemoryTrend(report.memoryTrend),
summary: this.buildSummary(report, currentMemory)
};
}
/**
* 从现有 PerformanceMonitor 数据构建兼容格式
*/
public collectFromLegacyMonitor(performanceMonitor: ILegacyPerformanceMonitor | null): IAdvancedProfilerData {
if (!performanceMonitor) {
return this.createEmptyData();
}
const systemStats = performanceMonitor.getAllSystemStats?.() || new Map();
const systemData = performanceMonitor.getAllSystemData?.() || new Map();
const frameTime = Time.deltaTime * 1000;
const fps = frameTime > 0 ? Math.round(1000 / frameTime) : 0;
const categoryStats = this.buildCategoryStatsFromLegacy(systemStats, systemData, frameTime);
const hotspots = this.buildHotspotsFromLegacy(systemStats, systemData, frameTime);
return {
currentFrame: {
frameNumber: 0,
frameTime,
fps,
memory: this.getCurrentMemory()
},
frameTimeHistory: [],
categoryStats,
hotspots,
callGraph: {
currentFunction: this.selectedFunction,
callers: [],
callees: []
},
longTasks: [],
memoryTrend: [],
summary: {
totalFrames: 0,
averageFrameTime: frameTime,
minFrameTime: frameTime,
maxFrameTime: frameTime,
p95FrameTime: frameTime,
p99FrameTime: frameTime,
currentMemoryMB: this.getCurrentMemory().usedHeapSize / (1024 * 1024),
peakMemoryMB: this.peakMemory / (1024 * 1024),
gcCount: 0,
longTaskCount: 0
}
};
}
private buildCurrentFrameData(frame: ProfileFrame | null): IAdvancedProfilerData['currentFrame'] {
if (!frame) {
const frameTime = Time.deltaTime * 1000;
return {
frameNumber: 0,
frameTime,
fps: frameTime > 0 ? Math.round(1000 / frameTime) : 0,
memory: this.getCurrentMemory()
};
}
return {
frameNumber: frame.frameNumber,
frameTime: frame.duration,
fps: frame.duration > 0 ? Math.round(1000 / frame.duration) : 0,
memory: frame.memory
};
}
private buildFrameTimeHistory(frames: ProfileFrame[]): IAdvancedProfilerData['frameTimeHistory'] {
return frames.map(f => ({
frameNumber: f.frameNumber,
time: f.startTime,
duration: f.duration
}));
}
private buildCategoryStats(
frame: ProfileFrame | null,
performanceMonitor?: any
): IAdvancedProfilerData['categoryStats'] {
const result: IAdvancedProfilerData['categoryStats'] = [];
if (frame && frame.categoryStats.size > 0) {
const frameDuration = frame.duration || 1;
for (const [category, stats] of frame.categoryStats) {
const categoryItems = frame.sampleStats
.filter(s => s.category === category)
.map(s => ({
name: s.name,
inclusiveTime: s.inclusiveTime,
exclusiveTime: s.exclusiveTime,
callCount: s.callCount,
percentOfCategory: stats.totalTime > 0
? (s.inclusiveTime / stats.totalTime) * 100
: 0,
percentOfFrame: (s.inclusiveTime / frameDuration) * 100
}))
.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
result.push({
category,
totalTime: stats.totalTime,
percentOfFrame: stats.percentOfFrame,
sampleCount: stats.sampleCount,
items: categoryItems
});
}
}
if (performanceMonitor && result.length === 0) {
const systemStats = performanceMonitor.getAllSystemStats?.() || new Map();
const systemData = performanceMonitor.getAllSystemData?.() || new Map();
const frameTime = Time.deltaTime * 1000 || 1;
return this.buildCategoryStatsFromLegacy(systemStats, systemData, frameTime);
}
return result.sort((a, b) => b.totalTime - a.totalTime);
}
private buildCategoryStatsFromLegacy(
systemStats: Map<string, any>,
systemData: Map<string, any>,
frameTime: number
): IAdvancedProfilerData['categoryStats'] {
const ecsItems: IAdvancedProfilerData['categoryStats'][0]['items'] = [];
let totalECSTime = 0;
for (const [name, stats] of systemStats.entries()) {
const data = systemData.get(name);
const execTime = data?.executionTime || stats?.averageTime || 0;
totalECSTime += execTime;
ecsItems.push({
name,
inclusiveTime: execTime,
exclusiveTime: execTime,
callCount: 1,
percentOfCategory: 0,
percentOfFrame: frameTime > 0 ? (execTime / frameTime) * 100 : 0
});
}
for (const item of ecsItems) {
item.percentOfCategory = totalECSTime > 0
? (item.inclusiveTime / totalECSTime) * 100
: 0;
}
ecsItems.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
if (ecsItems.length === 0) {
return [];
}
return [{
category: ProfileCategory.ECS,
totalTime: totalECSTime,
percentOfFrame: frameTime > 0 ? (totalECSTime / frameTime) * 100 : 0,
sampleCount: ecsItems.length,
items: ecsItems
}];
}
private buildHotspots(report: ProfileReport): IAdvancedProfilerData['hotspots'] {
const totalTime = report.hotspots.reduce((sum, h) => sum + h.inclusiveTime, 0) || 1;
// 使用 callGraph 构建层级结构
// 找出所有根节点(没有被任何函数调用的,或者是顶层函数)
const rootFunctions = new Set<string>();
const childFunctions = new Set<string>();
for (const [name, node] of report.callGraph) {
// 如果没有调用者,或者调用者不在 hotspots 中,则是根节点
if (node.callers.size === 0) {
rootFunctions.add(name);
} else {
// 检查是否所有调用者都在 hotspots 之外
let hasParentInHotspots = false;
for (const callerName of node.callers.keys()) {
if (report.callGraph.has(callerName)) {
hasParentInHotspots = true;
childFunctions.add(name);
break;
}
}
if (!hasParentInHotspots) {
rootFunctions.add(name);
}
}
}
// 递归构建层级热点数据
const buildHotspotItem = (
name: string,
depth: number,
visited: Set<string>
): IHotspotItem | null => {
if (visited.has(name)) return null; // 避免循环
visited.add(name);
const stats = report.hotspots.find(h => h.name === name);
const node = report.callGraph.get(name);
if (!stats && !node) return null;
const inclusiveTime = stats?.inclusiveTime || node?.totalTime || 0;
const exclusiveTime = stats?.exclusiveTime || inclusiveTime;
const callCount = stats?.callCount || node?.callCount || 1;
// 构建子节点
const children: IHotspotItem[] = [];
if (node && depth < 5) { // 限制深度避免过深
for (const [calleeName] of node.callees) {
const child = buildHotspotItem(calleeName, depth + 1, visited);
if (child) {
children.push(child);
}
}
// 按耗时排序
children.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
}
return {
name,
category: stats?.category || node?.category || ProfileCategory.Custom,
inclusiveTime,
inclusiveTimePercent: (inclusiveTime / totalTime) * 100,
exclusiveTime,
exclusiveTimePercent: (exclusiveTime / totalTime) * 100,
callCount,
avgCallTime: callCount > 0 ? inclusiveTime / callCount : 0,
depth,
children: children.length > 0 ? children : undefined
};
};
// 构建根节点列表
const result: IAdvancedProfilerData['hotspots'] = [];
const visited = new Set<string>();
for (const rootName of rootFunctions) {
const item = buildHotspotItem(rootName, 0, visited);
if (item) {
result.push(item);
}
}
// 按耗时排序
result.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
return result.slice(0, 50);
}
private buildHotspotsFromLegacy(
systemStats: Map<string, any>,
systemData: Map<string, any>,
frameTime: number
): IAdvancedProfilerData['hotspots'] {
const hotspots: IAdvancedProfilerData['hotspots'] = [];
for (const [name, stats] of systemStats.entries()) {
const data = systemData.get(name);
const execTime = data?.executionTime || stats?.averageTime || 0;
hotspots.push({
name,
category: ProfileCategory.ECS,
inclusiveTime: execTime,
inclusiveTimePercent: frameTime > 0 ? (execTime / frameTime) * 100 : 0,
exclusiveTime: execTime,
exclusiveTimePercent: frameTime > 0 ? (execTime / frameTime) * 100 : 0,
callCount: stats?.executionCount || 1,
avgCallTime: stats?.averageTime || execTime,
depth: 0
});
}
return hotspots.sort((a, b) => b.inclusiveTime - a.inclusiveTime).slice(0, 50);
}
private buildCallGraph(report: ProfileReport): IAdvancedProfilerData['callGraph'] {
if (!this.selectedFunction) {
return {
currentFunction: null,
callers: [],
callees: []
};
}
const node = report.callGraph.get(this.selectedFunction);
if (!node) {
return {
currentFunction: this.selectedFunction,
callers: [],
callees: []
};
}
// 计算所有调用者的总调用次数(用于计算调用者的百分比)
let totalCallerCount = 0;
for (const data of node.callers.values()) {
totalCallerCount += data.count;
}
// Calling Functions谁调用了我
// - totalTime: 该调用者调用当前函数时的平均耗时
// - percentOfCurrent: 该调用者的调用次数占总调用次数的百分比
const callers = Array.from(node.callers.entries())
.map(([name, data]) => ({
name,
callCount: data.count,
totalTime: data.totalTime,
percentOfCurrent: totalCallerCount > 0 ? (data.count / totalCallerCount) * 100 : 0
}))
.sort((a, b) => b.callCount - a.callCount);
// Called Functions我调用了谁
// - totalTime: 当前函数调用该被调用者时的平均耗时
// - percentOfCurrent: 该被调用者的耗时占当前函数耗时的百分比
const callees = Array.from(node.callees.entries())
.map(([name, data]) => ({
name,
callCount: data.count,
totalTime: data.totalTime,
percentOfCurrent: node.totalTime > 0 ? (data.totalTime / node.totalTime) * 100 : 0
}))
.sort((a, b) => b.totalTime - a.totalTime);
return {
currentFunction: this.selectedFunction,
callers,
callees
};
}
private buildMemoryTrend(snapshots: MemorySnapshot[]): IAdvancedProfilerData['memoryTrend'] {
return snapshots.map(s => ({
time: s.timestamp,
usedMB: s.usedHeapSize / (1024 * 1024),
totalMB: s.totalHeapSize / (1024 * 1024),
gcCount: s.gcCount
}));
}
private buildSummary(
report: ProfileReport,
currentMemory: MemorySnapshot
): IAdvancedProfilerData['summary'] {
return {
totalFrames: report.totalFrames,
averageFrameTime: report.averageFrameTime,
minFrameTime: report.minFrameTime,
maxFrameTime: report.maxFrameTime,
p95FrameTime: report.p95FrameTime,
p99FrameTime: report.p99FrameTime,
currentMemoryMB: currentMemory.usedHeapSize / (1024 * 1024),
peakMemoryMB: this.peakMemory / (1024 * 1024),
gcCount: currentMemory.gcCount,
longTaskCount: report.longTasks.length
};
}
private getCurrentMemory(): MemorySnapshot {
const perfWithMemory = performance as Performance & {
memory?: {
usedJSHeapSize?: number;
totalJSHeapSize?: number;
jsHeapSizeLimit?: number;
};
};
const usedHeapSize = perfWithMemory.memory?.usedJSHeapSize || 0;
const totalHeapSize = perfWithMemory.memory?.totalJSHeapSize || 0;
const heapSizeLimit = perfWithMemory.memory?.jsHeapSizeLimit || 0;
return {
timestamp: performance.now(),
usedHeapSize,
totalHeapSize,
heapSizeLimit,
utilizationPercent: heapSizeLimit > 0 ? (usedHeapSize / heapSizeLimit) * 100 : 0,
gcCount: 0
};
}
private getDefaultMemory(): MemorySnapshot {
return {
timestamp: performance.now(),
usedHeapSize: 0,
totalHeapSize: 0,
heapSizeLimit: 0,
utilizationPercent: 0,
gcCount: 0
};
}
private createEmptyData(): IAdvancedProfilerData {
return {
currentFrame: {
frameNumber: 0,
frameTime: 0,
fps: 0,
memory: this.getDefaultMemory()
},
frameTimeHistory: [],
categoryStats: [],
hotspots: [],
callGraph: {
currentFunction: null,
callers: [],
callees: []
},
longTasks: [],
memoryTrend: [],
summary: {
totalFrames: 0,
averageFrameTime: 0,
minFrameTime: 0,
maxFrameTime: 0,
p95FrameTime: 0,
p99FrameTime: 0,
currentMemoryMB: 0,
peakMemoryMB: 0,
gcCount: 0,
longTaskCount: 0
}
};
}
}

View File

@@ -0,0 +1,273 @@
import { IComponentDebugData } from '../../Types';
import { ComponentPoolManager } from '../../ECS/Core/ComponentPool';
import { getComponentInstanceTypeName } from '../../ECS/Decorators';
import { IScene } from '../../ECS/IScene';
/**
* 组件数据收集器
*/
export class ComponentDataCollector {
private static componentSizeCache = new Map<string, number>();
/**
* 收集组件数据(轻量版,不计算实际内存大小)
* @param scene 场景实例
*/
public collectComponentData(scene?: IScene | null): IComponentDebugData {
if (!scene) {
return {
componentTypes: 0,
componentInstances: 0,
componentStats: []
};
}
const entityList = scene.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 = getComponentInstanceTypeName(component);
const stats = componentStats.get(typeName) || { count: 0, entities: 0 };
stats.count++;
totalInstances++;
componentStats.set(typeName, stats);
});
}
});
// 获取池利用率信息
const poolUtilizations = new Map<string, number>();
const poolSizes = new Map<string, number>();
try {
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, scene);
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, scene?: IScene | null): number {
if (ComponentDataCollector.componentSizeCache.has(typeName)) {
return ComponentDataCollector.componentSizeCache.get(typeName)!;
}
if (!scene) return 64;
const entityList = scene.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) => getComponentInstanceTypeName(c) === 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 || 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);
}
/**
* 为内存快照功能提供的详细内存计算
* 只在用户主动请求内存快照时调用
* @param typeName 组件类型名称
* @param scene 场景实例
*/
public calculateDetailedComponentMemory(typeName: string, scene?: IScene | null): number {
if (!scene) return this.getEstimatedComponentSize(typeName, scene);
const entityList = scene.entities;
if (!entityList?.buffer) return this.getEstimatedComponentSize(typeName, scene);
try {
// 找到第一个包含此组件的实体,分析组件大小
for (const entity of entityList.buffer) {
if (entity.components) {
const component = entity.components.find((c: any) => getComponentInstanceTypeName(c) === typeName);
if (component) {
return this.estimateObjectSize(component);
}
}
}
} catch (error) {
// 忽略错误,使用估算值
}
return this.getEstimatedComponentSize(typeName, scene);
}
/**
* 估算对象内存大小(仅用于内存快照)
* 优化版本:减少递归深度,提高性能
*/
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) continue;
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();
}
}

View File

@@ -0,0 +1,45 @@
import { IECSDebugConfig } from '../../Types';
import { Injectable } from '../../Core/DI/Decorators';
import type { IService } from '../../Core/ServiceContainer';
/**
* 调试配置服务
*
* 管理调试系统的配置信息
*/
@Injectable()
export class DebugConfigService implements IService {
private _config: IECSDebugConfig;
constructor() {
this._config = {
enabled: false,
websocketUrl: '',
debugFrameRate: 30,
autoReconnect: true,
channels: {
entities: true,
systems: true,
performance: true,
components: true,
scenes: true
}
};
}
public setConfig(config: IECSDebugConfig): void {
this._config = config;
}
public getConfig(): IECSDebugConfig {
return this._config;
}
public isEnabled(): boolean {
return this._config.enabled;
}
dispose(): void {
// 清理资源
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,222 @@
import { IPerformanceDebugData } from '../../Types';
import { Time } from '../Time';
/**
* 性能数据收集器
*/
export class PerformanceDataCollector {
private frameTimeHistory: number[] = [];
private maxHistoryLength: number = 60;
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) {
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;
}
}
}

View File

@@ -0,0 +1,54 @@
import { ISceneDebugData } from '../../Types';
import { IScene } from '../../ECS/IScene';
/**
* 场景数据收集器
* Scene data collector
*
* 收集场景的调试信息,通过公共接口访问数据。
* Collects scene debug information through public interfaces.
*/
export class SceneDataCollector {
private sceneStartTime: number = Date.now();
/**
* 收集场景数据
* Collect scene data
*
* @param scene 场景实例 | Scene instance
*/
public collectSceneData(scene?: IScene | null): ISceneDebugData {
if (!scene) {
return {
currentSceneName: 'No Scene',
isInitialized: false,
sceneRunTime: 0,
sceneEntityCount: 0,
sceneSystemCount: 0,
sceneUptime: 0
};
}
const currentTime = Date.now();
const runTime = (currentTime - this.sceneStartTime) / 1000;
// 使用公共接口获取数据 | Use public interface to get data
const stats = scene.getStats();
return {
currentSceneName: scene.name || 'Unnamed Scene',
isInitialized: true, // 如果 scene 存在,则认为已初始化 | If scene exists, consider initialized
sceneRunTime: runTime,
sceneEntityCount: stats.entityCount,
sceneSystemCount: stats.processorCount,
sceneUptime: runTime
};
}
/**
* 设置场景开始时间
*/
public setSceneStartTime(time: number): void {
this.sceneStartTime = time;
}
}

View File

@@ -0,0 +1,66 @@
import { ISystemDebugData } from '../../Types';
import { getSystemInstanceTypeName } from '../../ECS/Decorators';
import { IScene } from '../../ECS/IScene';
/**
* 系统数据收集器
* System data collector
*
* 收集系统的调试信息,通过公共接口访问数据。
* Collects system debug information through public interfaces.
*/
export class SystemDataCollector {
/**
* 收集系统数据
* Collect system data
*
* @param performanceMonitor 性能监视器实例 | Performance monitor instance
* @param scene 场景实例 | Scene instance
*/
public collectSystemData(performanceMonitor: any, scene?: IScene | null): ISystemDebugData {
if (!scene) {
return {
totalSystems: 0,
systemsInfo: []
};
}
// 使用公共接口 | Use public interface
const systems = scene.systems || [];
// 获取性能监控数据
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 || getSystemInstanceTypeName(system);
const stats = systemStats.get(systemName);
const data = systemData.get(systemName);
return {
name: systemName,
type: getSystemInstanceTypeName(system),
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
};
})
};
}
}

View File

@@ -0,0 +1,169 @@
/**
* WebSocket连接管理器
*/
export class WebSocketManager {
private ws?: WebSocket;
private isConnected: boolean = false;
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 5;
private url: string;
private autoReconnect: boolean;
private reconnectTimer?: ReturnType<typeof setTimeout>;
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();
delete (this as any).ws;
}
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;
}
/**
* 计划重连
*/
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);
}
}
}

View File

@@ -0,0 +1,10 @@
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';
export { DebugConfigService } from './DebugConfigService';
export { AdvancedProfilerCollector } from './AdvancedProfilerCollector';
export type { IAdvancedProfilerData } from './AdvancedProfilerCollector';

View File

@@ -0,0 +1,116 @@
/**
* 用于包装事件的一个小类
*/
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) {
const messageData = this._messageTable.get(eventType);
if (messageData) {
const 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[]) {
const list = this._messageTable.get(eventType);
if (list) {
for (const observer of list) {
observer.func.call(observer.context, ...data);
}
}
}
/**
* 判断是否存在该类型的观察者
* @param eventType 事件类型
* @param handler 事件函数
*/
public hasObserver(eventType: T, handler: Function): boolean {
const list = this._messageTable.get(eventType);
return list ? list.some((observer) => observer.func === handler) : false;
}
/**
* 移除指定事件类型的所有监听器
* @param eventType 事件类型
*/
public removeAllObservers(eventType?: T): void {
if (eventType !== undefined) {
this._messageTable.delete(eventType);
} else {
this._messageTable.clear();
}
}
/**
* 释放所有资源,清理所有监听器
*/
public dispose(): void {
this._messageTable.clear();
}
/**
* 获取事件类型数量
*/
public getEventTypeCount(): number {
return this._messageTable.size;
}
/**
* 获取指定事件类型的监听器数量
* @param eventType 事件类型
*/
public getObserverCount(eventType: T): number {
const list = this._messageTable.get(eventType);
return list ? list.length : 0;
}
}

View 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);
}
}

View File

@@ -0,0 +1,14 @@
/**
* 类型工具类
* 提供类型相关的实用方法
*/
export class TypeUtils {
/**
* 获取对象的类型
* @param obj 对象
* @returns 对象的构造函数
*/
public static getType(obj: any) {
return obj.constructor;
}
}

View File

@@ -0,0 +1,3 @@
// 扩展工具类导出
export { TypeUtils } from './TypeUtils';
export { NumberExtension } from './NumberExtension';

View File

@@ -0,0 +1,90 @@
/**
* GUID 生成工具
*
* 提供跨平台的 UUID v4 生成功能,用于实体持久化标识。
* 优先使用 crypto.randomUUID(),降级使用 Math.random() 实现。
*
* GUID generation utility.
* Provides cross-platform UUID v4 generation for entity persistent identification.
* Uses crypto.randomUUID() when available, falls back to Math.random() implementation.
*/
/**
* 生成 UUID v4 格式的 GUID
*
* Generate a UUID v4 format GUID.
*
* @returns 36 字符的 UUID 字符串 (例如: "550e8400-e29b-41d4-a716-446655440000")
*
* @example
* ```typescript
* const id = generateGUID();
* console.log(id); // "550e8400-e29b-41d4-a716-446655440000"
* ```
*/
export function generateGUID(): string {
// 优先使用原生 crypto API浏览器和 Node.js 19+
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
// 降级方案:使用 crypto.getRandomValues 或 Math.random
return generateGUIDFallback();
}
/**
* 降级 GUID 生成实现
*
* Fallback GUID generation using crypto.getRandomValues or Math.random.
*/
function generateGUIDFallback(): string {
// 尝试使用 crypto.getRandomValues
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
// 设置版本号 (version 4)
bytes[6] = (bytes[6]! & 0x0f) | 0x40;
// 设置变体 (variant 1)
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
return formatUUID(bytes);
}
// 最终降级:使用 Math.random不推荐但可用
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
/**
* 格式化 16 字节数组为 UUID 字符串
*
* Format 16-byte array to UUID string.
*/
function formatUUID(bytes: Uint8Array): string {
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
/**
* 验证字符串是否为有效的 UUID 格式
*
* Validate if a string is a valid UUID format.
*
* @param value - 要验证的字符串
* @returns 如果是有效的 UUID 格式返回 true
*/
export function isValidGUID(value: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(value);
}
/**
* 空 GUID 常量
*
* Empty GUID constant (all zeros).
*/
export const EMPTY_GUID = '00000000-0000-0000-0000-000000000000';

View File

@@ -0,0 +1,52 @@
/**
* 全局管理器的基类。所有全局管理器都应该从此类继承。
*/
export class GlobalManager {
private _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() {
}
}

View File

@@ -0,0 +1,202 @@
import { Colors, LogLevel } from './Constants';
import { ILogger, LoggerColorConfig, LoggerConfig } from './Types';
/**
* 默认控制台日志实现
*/
export class ConsoleLogger implements ILogger {
private _config: LoggerConfig;
constructor(config: Partial<LoggerConfig> = {}) {
this._config = {
level: LogLevel.Info,
enableTimestamp: true,
enableColors: typeof window === 'undefined',
...config
};
}
/**
* 输出调试级别日志
* @param message 日志消息
* @param args 附加参数
*/
public debug(message: string, ...args: unknown[]): void {
this.log(LogLevel.Debug, message, ...args);
}
/**
* 输出信息级别日志
* @param message 日志消息
* @param args 附加参数
*/
public info(message: string, ...args: unknown[]): void {
this.log(LogLevel.Info, message, ...args);
}
/**
* 输出警告级别日志
* @param message 日志消息
* @param args 附加参数
*/
public warn(message: string, ...args: unknown[]): void {
this.log(LogLevel.Warn, message, ...args);
}
/**
* 输出错误级别日志
* @param message 日志消息
* @param args 附加参数
*/
public error(message: string, ...args: unknown[]): void {
this.log(LogLevel.Error, message, ...args);
}
/**
* 输出致命错误级别日志
* @param message 日志消息
* @param args 附加参数
*/
public fatal(message: string, ...args: unknown[]): void {
this.log(LogLevel.Fatal, message, ...args);
}
/**
* 设置日志级别
* @param level 日志级别
*/
public setLevel(level: LogLevel): void {
this._config.level = level;
}
/**
* 设置颜色配置
* @param colors 颜色配置
*/
public setColors(colors: LoggerColorConfig): void {
if (Object.keys(colors).length === 0) {
// 重置为默认颜色
delete this._config.colors;
} else {
this._config.colors = {
...this._config.colors,
...colors
};
}
}
/**
* 设置日志前缀
* @param prefix 前缀字符串
*/
public setPrefix(prefix: string): void {
this._config.prefix = prefix;
}
/**
* 内部日志输出方法
* @param level 日志级别
* @param message 日志消息
* @param args 附加参数
*/
private log(level: LogLevel, message: string, ...args: unknown[]): void {
if (level < this._config.level) {
return;
}
let formattedMessage = message;
// 添加时间戳
if (this._config.enableTimestamp) {
const timestamp = new Date().toISOString();
formattedMessage = `[${timestamp}] ${formattedMessage}`;
}
// 添加前缀
if (this._config.prefix) {
formattedMessage = `[${this._config.prefix}] ${formattedMessage}`;
}
// 添加日志级别
const levelName = LogLevel[level].toUpperCase();
formattedMessage = `[${levelName}] ${formattedMessage}`;
// 使用自定义输出或默认控制台输出
if (this._config.output) {
this._config.output(level, formattedMessage);
} else {
this.outputToConsole(level, formattedMessage, ...args);
}
}
/**
* 输出到控制台
* @param level 日志级别
* @param message 格式化后的消息
* @param args 附加参数
*/
private outputToConsole(level: LogLevel, message: string, ...args: unknown[]): void {
const colors = this._config.enableColors ? this.getColors() : null;
switch (level) {
case LogLevel.Debug:
if (colors) {
console.debug(`${colors.debug}${message}${colors.reset}`, ...args);
} else {
console.debug(message, ...args);
}
break;
case LogLevel.Info:
if (colors) {
console.info(`${colors.info}${message}${colors.reset}`, ...args);
} else {
console.info(message, ...args);
}
break;
case LogLevel.Warn:
if (colors) {
console.warn(`${colors.warn}${message}${colors.reset}`, ...args);
} else {
console.warn(message, ...args);
}
break;
case LogLevel.Error:
if (colors) {
console.error(`${colors.error}${message}${colors.reset}`, ...args);
} else {
console.error(message, ...args);
}
break;
case LogLevel.Fatal:
if (colors) {
console.error(`${colors.fatal}${message}${colors.reset}`, ...args);
} else {
console.error(message, ...args);
}
break;
}
}
/**
* 获取控制台颜色配置
* @returns 颜色配置对象
*/
private getColors() {
// 默认颜色配置
const defaultColors = {
debug: Colors.BRIGHT_BLACK, // 灰色
info: Colors.GREEN, // 绿色
warn: Colors.YELLOW, // 黄色
error: Colors.RED, // 红色
fatal: Colors.BRIGHT_RED, // 亮红色
reset: Colors.RESET // 重置
};
// 合并用户自定义颜色
return {
...defaultColors,
...this._config.colors
};
}
}

View File

@@ -0,0 +1,41 @@
/**
* 日志级别
*/
export enum LogLevel {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
Fatal = 4,
None = 5
}
/**
* 预定义的颜色常量
*/
export const Colors = {
// 基础颜色
BLACK: '\x1b[30m',
RED: '\x1b[31m',
GREEN: '\x1b[32m',
YELLOW: '\x1b[33m',
BLUE: '\x1b[34m',
MAGENTA: '\x1b[35m',
CYAN: '\x1b[36m',
WHITE: '\x1b[37m',
// 亮色版本
BRIGHT_BLACK: '\x1b[90m',
BRIGHT_RED: '\x1b[91m',
BRIGHT_GREEN: '\x1b[92m',
BRIGHT_YELLOW: '\x1b[93m',
BRIGHT_BLUE: '\x1b[94m',
BRIGHT_MAGENTA: '\x1b[95m',
BRIGHT_CYAN: '\x1b[96m',
BRIGHT_WHITE: '\x1b[97m',
// 特殊
RESET: '\x1b[0m',
BOLD: '\x1b[1m',
UNDERLINE: '\x1b[4m'
} as const;

View File

@@ -0,0 +1,196 @@
import { ConsoleLogger } from './ConsoleLogger';
import { LogLevel } from './Constants';
import { ILogger, LoggerColorConfig } from './Types';
/**
* 日志管理器
*/
export class LoggerManager {
private static _instance: LoggerManager;
private _loggers = new Map<string, ILogger>();
private _defaultLogger?: ILogger;
private _defaultLevel = LogLevel.Info;
private _loggerFactory?: (name?: string) => ILogger;
private constructor() {}
private get defaultLogger(): ILogger {
if (!this._defaultLogger) {
this._defaultLogger = this.createDefaultLogger();
}
return this._defaultLogger;
}
// 新增: 创建默认 logger 的逻辑
private createDefaultLogger(): ILogger {
if (this._loggerFactory) {
return this._loggerFactory();
}
return new ConsoleLogger({ level: this._defaultLevel });
}
/**
* 获取日志管理器实例
* @returns 日志管理器实例
*/
public static getInstance(): LoggerManager {
if (!LoggerManager._instance) {
LoggerManager._instance = new LoggerManager();
}
return LoggerManager._instance;
}
/**
* 获取或创建日志器
* @param name 日志器名称
* @returns 日志器实例
*/
public getLogger(name?: string): ILogger {
if (!name) {
return this.defaultLogger;
}
// 如果有自定义 factory, 每次都调用(不缓存), 由使用方自行管理
if (this._loggerFactory) {
return this._loggerFactory(name);
}
// 默认 ConsoleLogger 仍然缓存(保持向后兼容)
if (!this._loggers.has(name)) {
this._loggers.set(name, new ConsoleLogger({ prefix: name, level: this._defaultLevel }));
}
return this._loggers.get(name)!;
}
/**
* 设置日志器
* @param name 日志器名称
* @param logger 日志器实例
*/
public setLogger(name: string, logger: ILogger): void {
this._loggers.set(name, logger);
}
/**
* 设置全局日志级别
* @param level 日志级别
*/
public setGlobalLevel(level: LogLevel): void {
this._defaultLevel = level;
if (this._defaultLogger instanceof ConsoleLogger) {
this._defaultLogger.setLevel(level);
}
for (const logger of this._loggers.values()) {
if (logger instanceof ConsoleLogger) {
logger.setLevel(level);
}
}
}
/**
* 创建子日志器
* @param parentName 父日志器名称
* @param childName 子日志器名称
* @returns 子日志器实例
*/
public createChildLogger(parentName: string, childName: string): ILogger {
const fullName = `${parentName}.${childName}`;
return this.getLogger(fullName);
}
/**
* 设置全局颜色配置
* @param colors 颜色配置
*/
public setGlobalColors(colors: LoggerColorConfig): void {
if (this._defaultLogger instanceof ConsoleLogger) {
this._defaultLogger.setColors(colors);
}
for (const logger of this._loggers.values()) {
if (logger instanceof ConsoleLogger) {
logger.setColors(colors);
}
}
}
/**
* 重置为默认颜色配置
*/
public resetColors(): void {
if (this._defaultLogger instanceof ConsoleLogger) {
this._defaultLogger.setColors({});
}
for (const logger of this._loggers.values()) {
if (logger instanceof ConsoleLogger) {
logger.setColors({});
}
}
}
/**
* 设置日志器工厂方法
* @param factory 日志器工厂方法
*
* 注意: 应该在导入 ECS 模块之前调用此方法。
* 设置后, 每次调用 getLogger() 都会通过 factory 创建新的 logger 实例, 由用户侧管理
*/
public setLoggerFactory(factory: (name?: string) => ILogger): void {
this._loggerFactory = factory;
// 清空默认 logger,下次获取时使用新工厂方法
delete this._defaultLogger;
// 清空缓存
this._loggers.clear();
}
}
/**
* 默认日志器实例
*/
export const Logger = LoggerManager.getInstance().getLogger();
/**
* 创建命名日志器
* @param name 日志器名称
* @returns 日志器实例
*/
export function createLogger(name: string): ILogger {
return LoggerManager.getInstance().getLogger(name);
}
/**
* 设置全局日志颜色配置
* @param colors 颜色配置
*/
export function setLoggerColors(colors: LoggerColorConfig): void {
LoggerManager.getInstance().setGlobalColors(colors);
}
/**
* 重置日志颜色为默认配置
*/
export function resetLoggerColors(): void {
LoggerManager.getInstance().resetColors();
}
/**
* 设置全局日志级别
* @param level 日志级别
*/
export function setGlobalLogLevel(level: LogLevel): void {
LoggerManager.getInstance().setGlobalLevel(level);
}
/**
* 设置日志器工厂方法
* @param factory 日志器工厂方法
*/
export function setLoggerFactory(factory: (name?: string) => ILogger): void {
LoggerManager.getInstance().setLoggerFactory(factory);
}

View File

@@ -0,0 +1,42 @@
import type { LogLevel } from './Constants';
/**
* 日志接口
*/
export type ILogger = {
debug(...args: unknown[]): void;
info(...args: unknown[]): void;
warn(...args: unknown[]): void;
error(...args: unknown[]): void;
fatal(...args: unknown[]): void;
}
/**
* 日志颜色配置接口
*/
export type LoggerColorConfig = {
debug?: string;
info?: string;
warn?: string;
error?: string;
fatal?: string;
reset?: string;
}
/**
* 日志配置
*/
export type LoggerConfig = {
/** 日志级别 */
level: LogLevel;
/** 是否启用时间戳 */
enableTimestamp: boolean;
/** 是否启用颜色 */
enableColors: boolean;
/** 日志前缀 */
prefix?: string;
/** 自定义输出函数 */
output?: (level: LogLevel, message: string) => void;
/** 自定义颜色配置 */
colors?: LoggerColorConfig;
}

View File

@@ -0,0 +1,4 @@
export * from './ConsoleLogger';
export * from './Constants';
export * from './LoggerManager';
export * from './Types';

View File

@@ -0,0 +1,383 @@
/**
* 性能监控数据
*/
export type PerformanceData = {
/** 系统名称 */
name: string;
/** 执行时间(毫秒) */
executionTime: number;
/** 处理的实体数量 */
entityCount: number;
/** 平均每个实体的处理时间 */
averageTimePerEntity: number;
/** 最后更新时间戳 */
lastUpdateTime: number;
/** 内存使用量(字节) */
memoryUsage?: number;
/** CPU使用率百分比 */
cpuUsage?: number;
}
/**
* 性能统计信息
*/
export type 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 type PerformanceWarning = {
type: PerformanceWarningType;
systemName: string;
message: string;
severity: 'low' | 'medium' | 'high' | 'critical';
timestamp: number;
value: number;
threshold: number;
suggestion?: string;
}
/**
* 性能阈值配置
*/
export type 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;
};
}
import type { IService } from '../Core/ServiceContainer';
/**
* 高性能监控器
* 用于监控ECS系统的性能表现提供详细的分析和优化建议
*/
export class PerformanceMonitor implements IService {
private _systemData = new Map<string, PerformanceData>();
private _systemStats = new Map<string, PerformanceStats>();
private _isEnabled = false;
private _maxRecentSamples = 60; // 保留最近60帧的数据
constructor() {}
/**
* @zh 更新FPS统计可选功能子类可重写
* @en Update FPS statistics (optional, can be overridden by subclasses)
*
* @param _deltaTime - @zh 帧时间间隔(秒)@en Frame delta time in seconds
*/
public updateFPS(_deltaTime: number): void {
// Base implementation does nothing - override in subclass for FPS tracking
}
/**
* 启用性能监控
*/
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();
}
}
}
/**
* 释放资源
*/
public dispose(): void {
this._systemData.clear();
this._systemStats.clear();
this._isEnabled = false;
}
}

View File

@@ -0,0 +1,29 @@
/**
* 可池化对象接口
*/
export type IPoolable = {
/**
* 重置对象状态,准备重用
*/
reset(): void;
}
/**
* 对象池统计信息
*/
export type PoolStats = {
/** 池中对象数量 */
size: number;
/** 池的最大大小 */
maxSize: number;
/** 总共创建的对象数量 */
totalCreated: number;
/** 总共获取的次数 */
totalObtained: number;
/** 总共释放的次数 */
totalReleased: number;
/** 命中率(从池中获取的比例) */
hitRate: number;
/** 内存使用估算(字节) */
estimatedMemoryUsage: number;
}

View File

@@ -0,0 +1,282 @@
import { IPoolable, PoolStats } from './IPoolable';
/**
* 高性能通用对象池
* 支持任意类型的对象池化,包含详细的统计信息
*/
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;
}
// 池中没有可用对象,创建新对象
this._stats.totalCreated++;
this._updateHitRate();
return this._createFn();
}
/**
* 释放对象回池中
* @param obj 要释放的对象
*/
public release(obj: T): void {
if (!obj) return;
this._stats.totalReleased++;
// 如果池未满,将对象放回池中
if (this._stats.size < this._maxSize) {
// 重置对象状态
obj.reset();
this._objects.push(obj);
this._stats.size++;
this._updateMemoryUsage();
}
// 如果池已满,让对象被垃圾回收
}
/**
* 获取池统计信息
* @returns 统计信息对象
*/
public getStats(): Readonly<PoolStats> {
return { ...this._stats };
}
/**
* 清空池
*/
public clear(): void {
// 重置所有对象
for (const obj of this._objects) {
obj.reset();
}
this._objects.length = 0;
this._stats.size = 0;
this._updateMemoryUsage();
}
/**
* 压缩池(移除多余的对象)
* @param targetSize 目标大小,默认为当前大小的一半
*/
public compact(targetSize?: number): void {
const target = targetSize ?? Math.floor(this._objects.length / 2);
while (this._objects.length > target) {
const obj = this._objects.pop();
if (obj) {
obj.reset();
this._stats.size--;
}
}
this._updateMemoryUsage();
}
/**
* 预填充池
* @param count 预填充的对象数量
*/
public prewarm(count: number): void {
const actualCount = Math.min(count, this._maxSize - this._objects.length);
for (let i = 0; i < actualCount; i++) {
const obj = this._createFn();
obj.reset();
this._objects.push(obj);
this._stats.totalCreated++;
this._stats.size++;
}
this._updateMemoryUsage();
}
/**
* 设置最大池大小
* @param maxSize 新的最大大小
*/
public setMaxSize(maxSize: number): void {
this._maxSize = maxSize;
this._stats.maxSize = maxSize;
// 如果当前池大小超过新的最大值,进行压缩
if (this._objects.length > maxSize) {
this.compact(maxSize);
}
}
/**
* 获取池中可用对象数量
* @returns 可用对象数量
*/
public getAvailableCount(): number {
return this._objects.length;
}
/**
* 检查池是否为空
* @returns 如果池为空返回true
*/
public isEmpty(): boolean {
return this._objects.length === 0;
}
/**
* 检查池是否已满
* @returns 如果池已满返回true
*/
public isFull(): boolean {
return this._objects.length >= this._maxSize;
}
/**
* 获取所有已注册的池类型
* @returns 所有池类型的数组
*/
public static getAllPoolTypes(): Function[] {
return Array.from(this._pools.keys());
}
/**
* 获取所有池的统计信息
* @returns 包含所有池统计信息的对象
*/
public static getAllPoolStats(): Record<string, PoolStats> {
const stats: Record<string, PoolStats> = {};
for (const [type, pool] of this._pools) {
const typeName = type.name || type.toString();
stats[typeName] = pool.getStats();
}
return stats;
}
/**
* 压缩所有池
*/
public static compactAllPools(): void {
for (const pool of this._pools.values()) {
pool.compact();
}
}
/**
* 清空所有池
*/
public static clearAllPools(): void {
for (const pool of this._pools.values()) {
pool.clear();
}
this._pools.clear();
}
/**
* 获取全局池统计信息的格式化字符串
* @returns 格式化的统计信息字符串
*/
public static getGlobalStatsString(): string {
const stats = this.getAllPoolStats();
const lines: string[] = ['=== Object Pool Global Statistics ===', ''];
if (Object.keys(stats).length === 0) {
lines.push('No pools registered');
return lines.join('\n');
}
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');
}
/**
* 更新命中率
*/
private _updateHitRate(): void {
if (this._stats.totalObtained === 0) {
this._stats.hitRate = 0;
} else {
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;
}
}

View File

@@ -0,0 +1,236 @@
import { IPoolable, PoolStats } from './IPoolable';
import { Pool } from './Pool';
import type { IService } from '../../Core/ServiceContainer';
/**
* 池管理器
* 统一管理所有对象池
*/
export class PoolManager implements IService {
private pools = new Map<string, Pool<any>>();
private autoCompactInterval = 60000; // 60秒
private lastCompactTime = 0;
constructor() {
// 普通构造函数,不再使用单例模式
}
/**
* 注册池
* @param name 池名称
* @param pool 池实例
*/
public registerPool<T extends IPoolable>(name: string, pool: Pool<T>): void {
this.pools.set(name, pool);
}
/**
* 获取池
* @param name 池名称
* @returns 池实例
*/
public getPool<T extends IPoolable>(name: string): Pool<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;
}
}
/**
* 创建或获取标准池
* @param name 池名称
* @param createFn 创建函数
* @param maxSize 最大大小
* @param estimatedObjectSize 估算对象大小
* @returns 池实例
*/
public createPool<T extends IPoolable>(
name: string,
createFn: () => T,
maxSize: number = 100,
estimatedObjectSize: number = 1024
): Pool<T> {
let pool = this.pools.get(name) as Pool<T>;
if (!pool) {
pool = new Pool(createFn, maxSize, estimatedObjectSize);
this.pools.set(name, pool);
}
return pool;
}
/**
* 移除池
* @param name 池名称
* @returns 是否成功移除
*/
public removePool(name: string): boolean {
const pool = this.pools.get(name);
if (pool) {
pool.clear();
this.pools.delete(name);
return true;
}
return false;
}
/**
* 获取所有池名称
* @returns 池名称数组
*/
public getPoolNames(): string[] {
return Array.from(this.pools.keys());
}
/**
* 获取池数量
* @returns 池数量
*/
public getPoolCount(): number {
return this.pools.size;
}
/**
* 压缩所有池
*/
public compactAllPools(): void {
for (const pool of this.pools.values()) {
pool.compact();
}
}
/**
* 清空所有池
*/
public clearAllPools(): void {
for (const pool of this.pools.values()) {
pool.clear();
}
}
/**
* 获取所有池的统计信息
* @returns 统计信息映射
*/
public getAllStats(): Map<string, PoolStats> {
const stats = new Map<string, PoolStats>();
for (const [name, pool] of this.pools) {
stats.set(name, pool.getStats());
}
return stats;
}
/**
* 获取总体统计信息
* @returns 总体统计信息
*/
public getGlobalStats(): PoolStats {
let totalSize = 0;
let totalMaxSize = 0;
let totalCreated = 0;
let totalObtained = 0;
let totalReleased = 0;
let totalMemoryUsage = 0;
for (const pool of this.pools.values()) {
const stats = pool.getStats();
totalSize += stats.size;
totalMaxSize += stats.maxSize;
totalCreated += stats.totalCreated;
totalObtained += stats.totalObtained;
totalReleased += stats.totalReleased;
totalMemoryUsage += stats.estimatedMemoryUsage;
}
const hitRate = totalObtained === 0 ? 0 : (totalObtained - totalCreated) / totalObtained;
return {
size: totalSize,
maxSize: totalMaxSize,
totalCreated,
totalObtained,
totalReleased,
hitRate,
estimatedMemoryUsage: totalMemoryUsage
};
}
/**
* 获取格式化的统计信息字符串
* @returns 格式化字符串
*/
public getStatsString(): string {
const lines: string[] = ['=== Pool Manager Statistics ===', ''];
if (this.pools.size === 0) {
lines.push('No pools registered');
return lines.join('\n');
}
const globalStats = this.getGlobalStats();
lines.push(`Total Pools: ${this.pools.size}`);
lines.push(`Global Hit Rate: ${(globalStats.hitRate * 100).toFixed(1)}%`);
lines.push(`Global Memory Usage: ${(globalStats.estimatedMemoryUsage / 1024).toFixed(1)} KB`);
lines.push('');
for (const [name, pool] of this.pools) {
const stats = pool.getStats();
lines.push(`${name}:`);
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`);
lines.push('');
}
return lines.join('\n');
}
/**
* 设置自动压缩间隔
* @param intervalMs 间隔毫秒数
*/
public setAutoCompactInterval(intervalMs: number): void {
this.autoCompactInterval = intervalMs;
}
/**
* 预填充所有池
*/
public prewarmAllPools(): void {
for (const pool of this.pools.values()) {
const stats = pool.getStats();
const prewarmCount = Math.floor(stats.maxSize * 0.2); // 预填充20%
pool.prewarm(prewarmCount);
}
}
/**
* 重置池管理器
*/
public reset(): void {
this.clearAllPools();
this.pools.clear();
this.lastCompactTime = 0;
}
/**
* 释放资源
* 实现 IService 接口
*/
public dispose(): void {
this.reset();
}
}

View File

@@ -0,0 +1,3 @@
export * from './IPoolable';
export * from './Pool';
export * from './PoolManager';

View File

@@ -0,0 +1,649 @@
/**
* 自动性能分析器
*
* 提供自动函数包装和采样分析功能,无需手动埋点即可收集性能数据。
*
* 支持三种分析模式:
* 1. 自动包装模式 - 使用 Proxy 自动包装类的所有方法
* 2. 采样分析模式 - 定时采样调用栈(需要浏览器支持)
* 3. 装饰器模式 - 使用 @Profile() 装饰器手动标记方法
*/
import { ProfilerSDK } from './ProfilerSDK';
import { ProfileCategory } from './ProfilerTypes';
/**
* 自动分析配置
*/
export type AutoProfilerConfig = {
/** 是否启用自动包装 */
enabled: boolean;
/** 采样间隔(毫秒),用于采样分析器 */
sampleInterval: number;
/** 最小记录耗时(毫秒),低于此值的调用不记录 */
minDuration: number;
/** 是否追踪异步方法 */
trackAsync: boolean;
/** 排除的方法名模式 */
excludePatterns: RegExp[];
/** 最大采样缓冲区大小 */
maxBufferSize: number;
}
const DEFAULT_CONFIG: AutoProfilerConfig = {
enabled: true,
sampleInterval: 10,
minDuration: 0.1, // 0.1ms
trackAsync: true,
excludePatterns: [
/^_/, // 私有方法
/^get[A-Z]/, // getter 方法
/^set[A-Z]/, // setter 方法
/^is[A-Z]/, // 布尔检查方法
/^has[A-Z]/, // 存在检查方法
],
maxBufferSize: 10000
};
/**
* 采样数据
*/
interface SampleData {
timestamp: number;
stack: string[];
duration?: number;
}
/**
* 包装信息
*/
interface WrapInfo {
className: string;
methodName: string;
category: ProfileCategory;
original: Function;
}
/**
* 自动性能分析器
*/
export class AutoProfiler {
private static _instance: AutoProfiler | null = null;
private _config: AutoProfilerConfig;
private wrappedObjects: WeakMap<object, Map<string, WrapInfo>> = new WeakMap();
private samplingProfiler: SamplingProfiler | null = null;
private registeredClasses: Map<string, { constructor: Function; category: ProfileCategory }> = new Map();
private constructor(config?: Partial<AutoProfilerConfig>) {
this._config = { ...DEFAULT_CONFIG, ...config };
}
/**
* @zh 获取单例实例
* @en Get singleton instance
*/
public static getInstance(config?: Partial<AutoProfilerConfig>): AutoProfiler {
if (!AutoProfiler._instance) {
AutoProfiler._instance = new AutoProfiler(config);
}
return AutoProfiler._instance;
}
/**
* @zh 重置实例
* @en Reset instance
*/
public static resetInstance(): void {
if (AutoProfiler._instance) {
AutoProfiler._instance.dispose();
AutoProfiler._instance = null;
}
}
/**
* 启用/禁用自动分析
*/
public static setEnabled(enabled: boolean): void {
AutoProfiler.getInstance().setEnabled(enabled);
}
/**
* 注册类以进行自动分析
* 该类的所有实例方法都会被自动包装
*/
public static registerClass<T extends new (...args: any[]) => any>(
constructor: T,
category: ProfileCategory = ProfileCategory.Custom,
className?: string
): T {
return AutoProfiler.getInstance().registerClass(constructor, category, className);
}
/**
* 包装对象实例的所有方法
*/
public static wrapInstance<T extends object>(
instance: T,
className: string,
category: ProfileCategory = ProfileCategory.Custom
): T {
return AutoProfiler.getInstance().wrapInstance(instance, className, category);
}
/**
* 包装单个函数
*/
public static wrapFunction<T extends (...args: any[]) => any>(
fn: T,
name: string,
category: ProfileCategory = ProfileCategory.Custom
): T {
return AutoProfiler.getInstance().wrapFunction(fn, name, category);
}
/**
* 启动采样分析器
*/
public static startSampling(): void {
AutoProfiler.getInstance().startSampling();
}
/**
* 停止采样分析器
*/
public static stopSampling(): SampleData[] {
return AutoProfiler.getInstance().stopSampling();
}
/**
* @zh 设置启用状态
* @en Set enabled state
*/
public setEnabled(enabled: boolean): void {
this._config.enabled = enabled;
if (!enabled && this.samplingProfiler) {
this.samplingProfiler.stop();
}
}
/**
* @zh 注册类以进行自动分析
* @en Register class for automatic profiling
*/
public registerClass<T extends new (...args: any[]) => any>(
constructor: T,
category: ProfileCategory = ProfileCategory.Custom,
className?: string
): T {
const name = className || constructor.name;
this.registeredClasses.set(name, { constructor, category });
// eslint-disable-next-line @typescript-eslint/no-this-alias -- Required for Proxy construct handler
const self = this;
const ProxiedClass = new Proxy(constructor, {
construct(target, args, newTarget) {
const instance = Reflect.construct(target, args, newTarget);
if (self._config.enabled) {
self.wrapInstance(instance, name, category);
}
return instance;
}
});
return ProxiedClass;
}
/**
* @zh 包装对象实例的所有方法
* @en Wrap all methods of an object instance
*/
public wrapInstance<T extends object>(
instance: T,
className: string,
category: ProfileCategory = ProfileCategory.Custom
): T {
if (!this._config.enabled) {
return instance;
}
// 检查是否已经包装过
if (this.wrappedObjects.has(instance)) {
return instance;
}
const wrapInfoMap = new Map<string, WrapInfo>();
this.wrappedObjects.set(instance, wrapInfoMap);
const methodNames = this._getAllMethodNames(instance);
for (const methodName of methodNames) {
if (this._shouldExcludeMethod(methodName)) {
continue;
}
const descriptor = this._getPropertyDescriptor(instance, methodName);
if (!descriptor || typeof descriptor.value !== 'function') {
continue;
}
const original = descriptor.value as Function;
const wrapped = this._createWrappedMethod(original, className, methodName, category);
wrapInfoMap.set(methodName, {
className,
methodName,
category,
original
});
try {
(instance as any)[methodName] = wrapped;
} catch {
// 某些属性可能是只读的
}
}
return instance;
}
/**
* @zh 包装单个函数
* @en Wrap a single function
*/
public wrapFunction<T extends (...args: any[]) => any>(
fn: T,
name: string,
category: ProfileCategory = ProfileCategory.Custom
): T {
if (!this._config.enabled) return fn;
// eslint-disable-next-line @typescript-eslint/no-this-alias -- Required for wrapped function closure
const self = this;
const wrapped = function(this: any, ...args: any[]): any {
const handle = ProfilerSDK.beginSample(name, category);
try {
const result = fn.apply(this, args);
if (self._config.trackAsync && result instanceof Promise) {
return result.finally(() => {
ProfilerSDK.endSample(handle);
});
}
ProfilerSDK.endSample(handle);
return result;
} catch (error) {
ProfilerSDK.endSample(handle);
throw error;
}
} as T;
Object.defineProperty(wrapped, 'name', { value: fn.name || name });
Object.defineProperty(wrapped, 'length', { value: fn.length });
return wrapped;
}
/**
* @zh 启动采样分析器
* @en Start sampling profiler
*/
public startSampling(): void {
if (!this.samplingProfiler) {
this.samplingProfiler = new SamplingProfiler(this._config);
}
this.samplingProfiler.start();
}
/**
* 停止采样分析器
*/
public stopSampling(): SampleData[] {
if (!this.samplingProfiler) {
return [];
}
return this.samplingProfiler.stop();
}
/**
* 释放资源
*/
public dispose(): void {
if (this.samplingProfiler) {
this.samplingProfiler.stop();
this.samplingProfiler = null;
}
this.registeredClasses.clear();
}
/**
* @zh 创建包装后的方法
* @en Create wrapped method
*/
private _createWrappedMethod(
original: Function,
className: string,
methodName: string,
category: ProfileCategory
): Function {
// eslint-disable-next-line @typescript-eslint/no-this-alias -- Required for wrapped method closure
const self = this;
const fullName = `${className}.${methodName}`;
const minDuration = this._config.minDuration;
return function(this: any, ...args: any[]): any {
if (!self._config.enabled || !ProfilerSDK.isEnabled()) {
return original.apply(this, args);
}
const startTime = performance.now();
const handle = ProfilerSDK.beginSample(fullName, category);
try {
const result = original.apply(this, args);
if (self._config.trackAsync && result instanceof Promise) {
return result.then(
(value) => {
const duration = performance.now() - startTime;
if (duration >= minDuration) {
ProfilerSDK.endSample(handle);
}
return value;
},
(error) => {
ProfilerSDK.endSample(handle);
throw error;
}
);
}
const duration = performance.now() - startTime;
if (duration >= minDuration) {
ProfilerSDK.endSample(handle);
}
return result;
} catch (error) {
ProfilerSDK.endSample(handle);
throw error;
}
};
}
/**
* @zh 获取对象的所有方法名
* @en Get all method names of an object
*/
private _getAllMethodNames(obj: object): string[] {
const methods = new Set<string>();
let current = obj;
while (current && current !== Object.prototype) {
for (const name of Object.getOwnPropertyNames(current)) {
if (name !== 'constructor') {
methods.add(name);
}
}
current = Object.getPrototypeOf(current);
}
return Array.from(methods);
}
/**
* @zh 获取属性描述符
* @en Get property descriptor
*/
private _getPropertyDescriptor(obj: object, name: string): PropertyDescriptor | undefined {
let current = obj;
while (current && current !== Object.prototype) {
const descriptor = Object.getOwnPropertyDescriptor(current, name);
if (descriptor) return descriptor;
current = Object.getPrototypeOf(current);
}
return undefined;
}
/**
* @zh 判断是否应该排除该方法
* @en Check if method should be excluded
*/
private _shouldExcludeMethod(methodName: string): boolean {
if (methodName === 'constructor' || methodName.startsWith('__')) {
return true;
}
for (const pattern of this._config.excludePatterns) {
if (pattern.test(methodName)) {
return true;
}
}
return false;
}
}
/**
* 采样分析器
* 使用定时器定期采样调用栈信息
*/
class SamplingProfiler {
private config: AutoProfilerConfig;
private samples: SampleData[] = [];
private intervalId: number | null = null;
private isRunning = false;
constructor(config: AutoProfilerConfig) {
this.config = config;
}
/**
* 开始采样
*/
public start(): void {
if (this.isRunning) return;
this.isRunning = true;
this.samples = [];
// 使用 requestAnimationFrame 或 setInterval 进行采样
const sample = () => {
if (!this.isRunning) return;
const stack = this.captureStack();
if (stack.length > 0) {
this.samples.push({
timestamp: performance.now(),
stack
});
// 限制缓冲区大小
if (this.samples.length > this.config.maxBufferSize) {
this.samples.shift();
}
}
// 继续采样
if (this.config.sampleInterval < 16) {
// 高频采样使用 setTimeout
this.intervalId = setTimeout(sample, this.config.sampleInterval) as any;
} else {
this.intervalId = setTimeout(sample, this.config.sampleInterval) as any;
}
};
sample();
}
/**
* 停止采样并返回数据
*/
public stop(): SampleData[] {
this.isRunning = false;
if (this.intervalId !== null) {
clearTimeout(this.intervalId);
this.intervalId = null;
}
return [...this.samples];
}
/**
* 捕获当前调用栈
*/
private captureStack(): string[] {
try {
// 创建 Error 对象获取调用栈
const error = new Error();
const stack = error.stack || '';
// 解析调用栈
const lines = stack.split('\n').slice(3); // 跳过 Error 和 captureStack/sample
const frames: string[] = [];
for (const line of lines) {
const frame = this.parseStackFrame(line);
if (frame && !this.isInternalFrame(frame)) {
frames.push(frame);
}
}
return frames;
} catch {
return [];
}
}
/**
* 解析调用栈帧
*/
private parseStackFrame(line: string): string | null {
// Chrome/Edge 格式: " at functionName (file:line:col)"
// Firefox 格式: "functionName@file:line:col"
// Safari 格式: "functionName@file:line:col"
line = line.trim();
// Chrome 格式
let match = line.match(/at\s+(.+?)\s+\(/);
if (match && match[1]) {
return match[1];
}
// Chrome 匿名函数格式
match = line.match(/at\s+(.+)/);
if (match && match[1]) {
const name = match[1];
if (!name.includes('(')) {
return name;
}
}
// Firefox/Safari 格式
match = line.match(/^(.+?)@/);
if (match && match[1]) {
return match[1];
}
return null;
}
/**
* 判断是否是内部帧(应该过滤掉)
*/
private isInternalFrame(frame: string): boolean {
const internalPatterns = [
'SamplingProfiler',
'AutoProfiler',
'ProfilerSDK',
'setTimeout',
'setInterval',
'requestAnimationFrame',
'<anonymous>',
'eval'
];
return internalPatterns.some(pattern => frame.includes(pattern));
}
}
/**
* @Profile 装饰器
* 用于标记需要性能分析的方法
*
* @example
* ```typescript
* class MySystem extends System {
* @Profile()
* update() {
* // 方法执行时间会被自动记录
* }
*
* @Profile('customName', ProfileCategory.Physics)
* calculatePhysics() {
* // 使用自定义名称和分类
* }
* }
* ```
*/
export function Profile(
name?: string,
category: ProfileCategory = ProfileCategory.Custom
): MethodDecorator {
return function(
target: object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
): PropertyDescriptor {
const original = descriptor.value;
const methodName = name || `${target.constructor.name}.${String(propertyKey)}`;
descriptor.value = function(this: any, ...args: any[]): any {
if (!ProfilerSDK.isEnabled()) {
return original.apply(this, args);
}
const handle = ProfilerSDK.beginSample(methodName, category);
try {
const result = original.apply(this, args);
// 处理异步方法
if (result instanceof Promise) {
return result.finally(() => {
ProfilerSDK.endSample(handle);
});
}
// 同步方法,立即结束采样
ProfilerSDK.endSample(handle);
return result;
} catch (error) {
// 发生错误时也要结束采样
ProfilerSDK.endSample(handle);
throw error;
}
};
return descriptor;
};
}
/**
* @ProfileClass 装饰器
* 用于自动包装类的所有方法
*
* @example
* ```typescript
* @ProfileClass(ProfileCategory.Physics)
* class PhysicsSystem extends System {
* update() { ... } // 自动被包装
* calculate() { ... } // 自动被包装
* }
* ```
*/
export function ProfileClass(category: ProfileCategory = ProfileCategory.Custom): ClassDecorator {
return function<T extends Function>(constructor: T): T {
return AutoProfiler.registerClass(constructor as any, category) as any;
};
}

View File

@@ -0,0 +1,901 @@
/**
* 性能分析器 SDK
*
* 提供统一的性能分析接口,支持:
* - 手动采样标记
* - 自动作用域测量
* - 调用层级追踪
* - 计数器和仪表
*/
import {
ProfileCategory,
ProfileSample,
ProfileSampleStats,
ProfileFrame,
ProfileCounter,
MemorySnapshot,
SampleHandle,
ProfilerConfig,
CallGraphNode,
ProfileReport,
LongTaskInfo,
DEFAULT_PROFILER_CONFIG
} from './ProfilerTypes';
let idCounter = 0;
function generateId(): string {
return `sample_${++idCounter}_${Date.now()}`;
}
/**
* 性能分析器 SDK
*/
export class ProfilerSDK {
private static _instance: ProfilerSDK | null = null;
private _config: ProfilerConfig;
private currentFrame: ProfileFrame | null = null;
private frameHistory: ProfileFrame[] = [];
private frameNumber = 0;
private activeSamples: Map<string, SampleHandle> = new Map();
private sampleStack: SampleHandle[] = [];
private counters: Map<string, ProfileCounter> = new Map();
private callGraph: Map<string, CallGraphNode> = new Map();
private gcCount = 0;
private previousHeapSize = 0;
private longTasks: LongTaskInfo[] = [];
private performanceObserver: PerformanceObserver | null = null;
private constructor(config?: Partial<ProfilerConfig>) {
this._config = { ...DEFAULT_PROFILER_CONFIG, ...config };
if (this._config.detectLongTasks) {
this._setupLongTaskObserver();
}
}
/**
* @zh 获取单例实例
* @en Get singleton instance
*/
public static getInstance(config?: Partial<ProfilerConfig>): ProfilerSDK {
if (!ProfilerSDK._instance) {
ProfilerSDK._instance = new ProfilerSDK(config);
}
return ProfilerSDK._instance;
}
/**
* @zh 重置实例(测试用)
* @en Reset instance (for testing)
*/
public static resetInstance(): void {
if (ProfilerSDK._instance) {
ProfilerSDK._instance.dispose();
ProfilerSDK._instance = null;
}
}
/**
* 开始采样
*/
public static beginSample(name: string, category: ProfileCategory = ProfileCategory.Custom): SampleHandle | null {
return ProfilerSDK.getInstance().beginSample(name, category);
}
/**
* 结束采样
*/
public static endSample(handle: SampleHandle | null): void {
if (handle) {
ProfilerSDK.getInstance().endSample(handle);
}
}
/**
* 测量同步函数执行时间
*/
public static measure<T>(name: string, fn: () => T, category: ProfileCategory = ProfileCategory.Custom): T {
return ProfilerSDK.getInstance().measure(name, fn, category);
}
/**
* 测量异步函数执行时间
*/
public static async measureAsync<T>(
name: string,
fn: () => Promise<T>,
category: ProfileCategory = ProfileCategory.Custom
): Promise<T> {
return ProfilerSDK.getInstance().measureAsync(name, fn, category);
}
/**
* 开始帧
*/
public static beginFrame(): void {
ProfilerSDK.getInstance().beginFrame();
}
/**
* 结束帧
*/
public static endFrame(): void {
ProfilerSDK.getInstance().endFrame();
}
/**
* 递增计数器
*/
public static incrementCounter(
name: string,
value: number = 1,
category: ProfileCategory = ProfileCategory.Custom
): void {
ProfilerSDK.getInstance().incrementCounter(name, value, category);
}
/**
* 设置仪表值
*/
public static setGauge(
name: string,
value: number,
category: ProfileCategory = ProfileCategory.Custom
): void {
ProfilerSDK.getInstance().setGauge(name, value, category);
}
/**
* 启用/禁用分析器
*/
public static setEnabled(enabled: boolean): void {
ProfilerSDK.getInstance().setEnabled(enabled);
}
/**
* @zh 检查是否启用
* @en Check if enabled
*/
public static isEnabled(): boolean {
return ProfilerSDK.getInstance()._config.enabled;
}
/**
* 获取当前帧数据
*/
public static getCurrentFrame(): ProfileFrame | null {
return ProfilerSDK.getInstance().currentFrame;
}
/**
* 获取帧历史
*/
public static getFrameHistory(): ProfileFrame[] {
return ProfilerSDK.getInstance().frameHistory;
}
/**
* 获取分析报告
*/
public static getReport(frameCount?: number): ProfileReport {
return ProfilerSDK.getInstance().generateReport(frameCount);
}
/**
* 重置数据
*/
public static reset(): void {
ProfilerSDK.getInstance().reset();
}
/**
* @zh 开始采样
* @en Begin sample
*/
public beginSample(name: string, category: ProfileCategory = ProfileCategory.Custom): SampleHandle | null {
if (!this._config.enabled || !this._config.enabledCategories.has(category)) {
return null;
}
const parentHandle = this.sampleStack.length > 0
? this.sampleStack[this.sampleStack.length - 1]
: undefined;
if (parentHandle && this.sampleStack.length >= this._config.maxSampleDepth) {
return null;
}
const handle: SampleHandle = {
id: generateId(),
name,
category,
startTime: performance.now(),
depth: this.sampleStack.length,
parentId: parentHandle?.id
};
this.activeSamples.set(handle.id, handle);
this.sampleStack.push(handle);
return handle;
}
/**
* @zh 结束采样
* @en End sample
*/
public endSample(handle: SampleHandle): void {
if (!this._config.enabled || !this.activeSamples.has(handle.id)) {
return;
}
const endTime = performance.now();
const duration = endTime - handle.startTime;
// 获取父级 handle在删除当前 handle 之前)
const parentHandle = handle.parentId ? this.activeSamples.get(handle.parentId) : undefined;
const sample: ProfileSample = {
id: handle.id,
name: handle.name,
category: handle.category,
startTime: handle.startTime,
endTime,
duration,
selfTime: duration,
parentId: handle.parentId,
parentName: parentHandle?.name,
depth: handle.depth,
callCount: 1
};
if (this.currentFrame) {
this.currentFrame.samples.push(sample);
}
this._updateCallGraph(handle.name, handle.category, duration, parentHandle);
this.activeSamples.delete(handle.id);
const stackIndex = this.sampleStack.indexOf(handle);
if (stackIndex !== -1) {
this.sampleStack.splice(stackIndex, 1);
}
}
/**
* 测量同步函数
*/
public measure<T>(name: string, fn: () => T, category: ProfileCategory = ProfileCategory.Custom): T {
const handle = this.beginSample(name, category);
try {
return fn();
} finally {
if (handle) {
this.endSample(handle);
}
}
}
/**
* 测量异步函数
*/
public async measureAsync<T>(
name: string,
fn: () => Promise<T>,
category: ProfileCategory = ProfileCategory.Custom
): Promise<T> {
const handle = this.beginSample(name, category);
try {
return await fn();
} finally {
if (handle) {
this.endSample(handle);
}
}
}
/**
* @zh 开始帧
* @en Begin frame
*/
public beginFrame(): void {
if (!this._config.enabled) return;
this.frameNumber++;
this.currentFrame = {
frameNumber: this.frameNumber,
startTime: performance.now(),
endTime: 0,
duration: 0,
samples: [],
sampleStats: [],
counters: new Map(this.counters),
memory: this._captureMemory(),
categoryStats: new Map()
};
this._resetFrameCounters();
}
/**
* @zh 结束帧
* @en End frame
*/
public endFrame(): void {
if (!this._config.enabled || !this.currentFrame) return;
this.currentFrame.endTime = performance.now();
this.currentFrame.duration = this.currentFrame.endTime - this.currentFrame.startTime;
this._calculateSampleStats();
this._calculateCategoryStats();
this.frameHistory.push(this.currentFrame);
while (this.frameHistory.length > this._config.maxFrameHistory) {
this.frameHistory.shift();
}
this.sampleStack = [];
this.activeSamples.clear();
}
/**
* @zh 递增计数器
* @en Increment counter
*/
public incrementCounter(
name: string,
value: number = 1,
category: ProfileCategory = ProfileCategory.Custom
): void {
if (!this._config.enabled) return;
let counter = this.counters.get(name);
if (!counter) {
counter = {
name,
category,
value: 0,
type: 'counter',
history: []
};
this.counters.set(name, counter);
}
counter.value += value;
counter.history.push({ time: performance.now(), value: counter.value });
if (counter.history.length > 100) {
counter.history.shift();
}
}
/**
* @zh 设置仪表值
* @en Set gauge value
*/
public setGauge(
name: string,
value: number,
category: ProfileCategory = ProfileCategory.Custom
): void {
if (!this._config.enabled) return;
let counter = this.counters.get(name);
if (!counter) {
counter = {
name,
category,
value: 0,
type: 'gauge',
history: []
};
this.counters.set(name, counter);
}
counter.value = value;
counter.history.push({ time: performance.now(), value });
if (counter.history.length > 100) {
counter.history.shift();
}
}
/**
* @zh 设置启用状态
* @en Set enabled state
*/
public setEnabled(enabled: boolean): void {
this._config.enabled = enabled;
if (enabled && this._config.detectLongTasks && !this.performanceObserver) {
this._setupLongTaskObserver();
}
}
/**
* 重置数据
*/
public reset(): void {
this.frameHistory = [];
this.currentFrame = null;
this.frameNumber = 0;
this.activeSamples.clear();
this.sampleStack = [];
this.counters.clear();
this.callGraph.clear();
this.gcCount = 0;
this.longTasks = [];
}
/**
* 生成分析报告
*/
public generateReport(frameCount?: number): ProfileReport {
const frames = frameCount
? this.frameHistory.slice(-frameCount)
: this.frameHistory;
if (frames.length === 0) {
return this._createEmptyReport();
}
const frameTimes = frames.map((f) => f.duration);
const sortedTimes = [...frameTimes].sort((a, b) => a - b);
const aggregatedStats = this._aggregateSampleStats(frames);
const hotspots = aggregatedStats
.sort((a, b) => b.inclusiveTime - a.inclusiveTime)
.slice(0, 20);
const categoryBreakdown = this._aggregateCategoryStats(frames);
const callGraph = this._buildCallGraphFromFrames(frames);
const firstFrame = frames[0];
const lastFrame = frames[frames.length - 1];
return {
startTime: firstFrame?.startTime ?? 0,
endTime: lastFrame?.endTime ?? 0,
totalFrames: frames.length,
averageFrameTime: frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length,
minFrameTime: Math.min(...frameTimes),
maxFrameTime: Math.max(...frameTimes),
p95FrameTime: sortedTimes[Math.floor(sortedTimes.length * 0.95)] || 0,
p99FrameTime: sortedTimes[Math.floor(sortedTimes.length * 0.99)] || 0,
hotspots,
callGraph,
categoryBreakdown,
memoryTrend: frames.map((f) => f.memory),
longTasks: [...this.longTasks]
};
}
/**
* @zh 从帧历史构建调用图
* @en Build call graph from frame history
*
* @zh 注意totalTime 存储的是平均耗时(总耗时/调用次数),而不是累计总耗时
* @en Note: totalTime stores average time (total/count), not cumulative total
*/
private _buildCallGraphFromFrames(frames: ProfileFrame[]): Map<string, CallGraphNode> {
// 临时存储累计数据
const tempData = new Map<string, {
category: ProfileCategory;
callCount: number;
totalTime: number;
callers: Map<string, { count: number; totalTime: number }>;
callees: Map<string, { count: number; totalTime: number }>;
}>();
for (const frame of frames) {
for (const sample of frame.samples) {
// 获取或创建当前函数的节点
let node = tempData.get(sample.name);
if (!node) {
node = {
category: sample.category,
callCount: 0,
totalTime: 0,
callers: new Map(),
callees: new Map()
};
tempData.set(sample.name, node);
}
node.callCount++;
node.totalTime += sample.duration;
// 如果有父级,建立调用关系
if (sample.parentName) {
// 记录当前函数被谁调用
const callerData = node.callers.get(sample.parentName) || { count: 0, totalTime: 0 };
callerData.count++;
callerData.totalTime += sample.duration;
node.callers.set(sample.parentName, callerData);
// 确保父节点存在并记录它调用了谁
let parentNode = tempData.get(sample.parentName);
if (!parentNode) {
parentNode = {
category: sample.category,
callCount: 0,
totalTime: 0,
callers: new Map(),
callees: new Map()
};
tempData.set(sample.parentName, parentNode);
}
const calleeData = parentNode.callees.get(sample.name) || { count: 0, totalTime: 0 };
calleeData.count++;
calleeData.totalTime += sample.duration;
parentNode.callees.set(sample.name, calleeData);
}
}
}
// 转换为最终结果,将 totalTime 改为平均耗时
const callGraph = new Map<string, CallGraphNode>();
for (const [name, data] of tempData) {
const avgCallers = new Map<string, { count: number; totalTime: number }>();
for (const [callerName, callerData] of data.callers) {
avgCallers.set(callerName, {
count: callerData.count,
totalTime: callerData.count > 0 ? callerData.totalTime / callerData.count : 0
});
}
const avgCallees = new Map<string, { count: number; totalTime: number }>();
for (const [calleeName, calleeData] of data.callees) {
avgCallees.set(calleeName, {
count: calleeData.count,
totalTime: calleeData.count > 0 ? calleeData.totalTime / calleeData.count : 0
});
}
callGraph.set(name, {
name,
category: data.category,
callCount: data.callCount,
totalTime: data.callCount > 0 ? data.totalTime / data.callCount : 0,
callers: avgCallers,
callees: avgCallees
});
}
return callGraph;
}
/**
* 获取调用图数据
*/
public getCallGraph(): Map<string, CallGraphNode> {
return new Map(this.callGraph);
}
/**
* 获取特定函数的调用关系
*/
public getFunctionCallInfo(name: string): {
callers: Array<{ name: string; count: number; totalTime: number }>;
callees: Array<{ name: string; count: number; totalTime: number }>;
} | null {
const node = this.callGraph.get(name);
if (!node) return null;
return {
callers: Array.from(node.callers.entries()).map(([name, data]) => ({
name,
...data
})),
callees: Array.from(node.callees.entries()).map(([name, data]) => ({
name,
...data
}))
};
}
/**
* 释放资源
*/
public dispose(): void {
if (this.performanceObserver) {
this.performanceObserver.disconnect();
this.performanceObserver = null;
}
this.reset();
}
private _captureMemory(): MemorySnapshot {
const now = performance.now();
let usedHeapSize = 0;
let totalHeapSize = 0;
let heapSizeLimit = 0;
const perfWithMemory = performance as Performance & {
memory?: {
usedJSHeapSize?: number;
totalJSHeapSize?: number;
jsHeapSizeLimit?: number;
};
};
if (perfWithMemory.memory) {
usedHeapSize = perfWithMemory.memory.usedJSHeapSize || 0;
totalHeapSize = perfWithMemory.memory.totalJSHeapSize || 0;
heapSizeLimit = perfWithMemory.memory.jsHeapSizeLimit || 0;
if (this.previousHeapSize > 0 && usedHeapSize < this.previousHeapSize - 1024 * 1024) {
this.gcCount++;
}
this.previousHeapSize = usedHeapSize;
}
return {
timestamp: now,
usedHeapSize,
totalHeapSize,
heapSizeLimit,
utilizationPercent: heapSizeLimit > 0 ? (usedHeapSize / heapSizeLimit) * 100 : 0,
gcCount: this.gcCount
};
}
private _resetFrameCounters(): void {
for (const counter of this.counters.values()) {
if (counter.type === 'counter') {
counter.value = 0;
}
}
}
private _calculateSampleStats(): void {
if (!this.currentFrame) return;
const sampleMap = new Map<string, ProfileSampleStats>();
for (const sample of this.currentFrame.samples) {
let stats = sampleMap.get(sample.name);
if (!stats) {
stats = {
name: sample.name,
category: sample.category,
inclusiveTime: 0,
exclusiveTime: 0,
callCount: 0,
averageTime: 0,
minTime: Number.MAX_VALUE,
maxTime: 0,
percentOfFrame: 0,
percentOfParent: 0,
children: [],
depth: sample.depth
};
sampleMap.set(sample.name, stats);
}
stats.inclusiveTime += sample.duration;
stats.callCount += 1;
stats.minTime = Math.min(stats.minTime, sample.duration);
stats.maxTime = Math.max(stats.maxTime, sample.duration);
}
for (const sample of this.currentFrame.samples) {
if (sample.parentId) {
const parentSample = this.currentFrame.samples.find((s) => s.id === sample.parentId);
if (parentSample) {
const parentStats = sampleMap.get(parentSample.name);
if (parentStats) {
parentStats.exclusiveTime = parentStats.inclusiveTime;
for (const childSample of this.currentFrame.samples) {
if (childSample.parentId === parentSample.id) {
parentStats.exclusiveTime -= childSample.duration;
}
}
}
}
}
}
const frameDuration = this.currentFrame.duration || 1;
for (const stats of sampleMap.values()) {
stats.averageTime = stats.inclusiveTime / stats.callCount;
stats.percentOfFrame = (stats.inclusiveTime / frameDuration) * 100;
if (stats.exclusiveTime === 0) {
stats.exclusiveTime = stats.inclusiveTime;
}
}
this.currentFrame.sampleStats = Array.from(sampleMap.values())
.sort((a, b) => b.inclusiveTime - a.inclusiveTime);
}
private _calculateCategoryStats(): void {
if (!this.currentFrame) return;
const categoryMap = new Map<ProfileCategory, { totalTime: number; sampleCount: number }>();
for (const sample of this.currentFrame.samples) {
if (sample.depth === 0) {
let stats = categoryMap.get(sample.category);
if (!stats) {
stats = { totalTime: 0, sampleCount: 0 };
categoryMap.set(sample.category, stats);
}
stats.totalTime += sample.duration;
stats.sampleCount += 1;
}
}
const frameDuration = this.currentFrame.duration || 1;
for (const [category, stats] of categoryMap) {
this.currentFrame.categoryStats.set(category, {
...stats,
percentOfFrame: (stats.totalTime / frameDuration) * 100
});
}
}
private _updateCallGraph(
name: string,
category: ProfileCategory,
duration: number,
parentHandle?: SampleHandle
): void {
let node = this.callGraph.get(name);
if (!node) {
node = {
name,
category,
callCount: 0,
totalTime: 0,
callers: new Map(),
callees: new Map()
};
this.callGraph.set(name, node);
}
node.callCount++;
node.totalTime += duration;
// 如果有父级,建立调用关系
if (parentHandle) {
// 记录当前函数被谁调用
const callerData = node.callers.get(parentHandle.name) || { count: 0, totalTime: 0 };
callerData.count++;
callerData.totalTime += duration;
node.callers.set(parentHandle.name, callerData);
// 确保父节点存在
let parentNode = this.callGraph.get(parentHandle.name);
if (!parentNode) {
parentNode = {
name: parentHandle.name,
category: parentHandle.category,
callCount: 0,
totalTime: 0,
callers: new Map(),
callees: new Map()
};
this.callGraph.set(parentHandle.name, parentNode);
}
// 记录父函数调用了谁
const calleeData = parentNode.callees.get(name) || { count: 0, totalTime: 0 };
calleeData.count++;
calleeData.totalTime += duration;
parentNode.callees.set(name, calleeData);
}
}
private _aggregateSampleStats(frames: ProfileFrame[]): ProfileSampleStats[] {
const aggregated = new Map<string, ProfileSampleStats>();
for (const frame of frames) {
for (const stats of frame.sampleStats) {
let agg = aggregated.get(stats.name);
if (!agg) {
agg = {
...stats,
minTime: Number.MAX_VALUE
};
aggregated.set(stats.name, agg);
} else {
agg.inclusiveTime += stats.inclusiveTime;
agg.exclusiveTime += stats.exclusiveTime;
agg.callCount += stats.callCount;
agg.minTime = Math.min(agg.minTime, stats.minTime);
agg.maxTime = Math.max(agg.maxTime, stats.maxTime);
}
}
}
const totalTime = frames.reduce((sum, f) => sum + f.duration, 0);
for (const stats of aggregated.values()) {
stats.averageTime = stats.inclusiveTime / stats.callCount;
stats.percentOfFrame = (stats.inclusiveTime / totalTime) * 100;
}
return Array.from(aggregated.values());
}
private _aggregateCategoryStats(frames: ProfileFrame[]): Map<ProfileCategory, {
totalTime: number;
averageTime: number;
percentOfTotal: number;
}> {
const aggregated = new Map<ProfileCategory, { totalTime: number; frameCount: number }>();
for (const frame of frames) {
for (const [category, stats] of frame.categoryStats) {
let agg = aggregated.get(category);
if (!agg) {
agg = { totalTime: 0, frameCount: 0 };
aggregated.set(category, agg);
}
agg.totalTime += stats.totalTime;
agg.frameCount++;
}
}
const totalTime = frames.reduce((sum, f) => sum + f.duration, 0);
const result = new Map<ProfileCategory, { totalTime: number; averageTime: number; percentOfTotal: number }>();
for (const [category, agg] of aggregated) {
result.set(category, {
totalTime: agg.totalTime,
averageTime: agg.frameCount > 0 ? agg.totalTime / agg.frameCount : 0,
percentOfTotal: totalTime > 0 ? (agg.totalTime / totalTime) * 100 : 0
});
}
return result;
}
private _setupLongTaskObserver(): void {
if (typeof PerformanceObserver === 'undefined') return;
try {
this.performanceObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > this._config.longTaskThreshold) {
this.longTasks.push({
startTime: entry.startTime,
duration: entry.duration,
attribution: (entry as any).attribution?.map((a: any) => a.name) || []
});
if (this.longTasks.length > 100) {
this.longTasks.shift();
}
}
}
});
this.performanceObserver.observe({ entryTypes: ['longtask'] });
} catch {
// Long Task API not supported
}
}
private _createEmptyReport(): ProfileReport {
return {
startTime: 0,
endTime: 0,
totalFrames: 0,
averageFrameTime: 0,
minFrameTime: 0,
maxFrameTime: 0,
p95FrameTime: 0,
p99FrameTime: 0,
hotspots: [],
callGraph: new Map(),
categoryBreakdown: new Map(),
memoryTrend: [],
longTasks: []
};
}
}

View File

@@ -0,0 +1,229 @@
/**
* 性能分析器类型定义
*/
/**
* 性能分析类别
*/
export enum ProfileCategory {
/** ECS 系统 */
ECS = 'ECS',
/** 渲染相关 */
Rendering = 'Rendering',
/** 物理系统 */
Physics = 'Physics',
/** 音频系统 */
Audio = 'Audio',
/** 网络相关 */
Network = 'Network',
/** 用户脚本 */
Script = 'Script',
/** 内存相关 */
Memory = 'Memory',
/** 动画系统 */
Animation = 'Animation',
/** AI/行为树 */
AI = 'AI',
/** 输入处理 */
Input = 'Input',
/** 资源加载 */
Loading = 'Loading',
/** 自定义 */
Custom = 'Custom'
}
/**
* 采样句柄
*/
export type SampleHandle = {
id: string;
name: string;
category: ProfileCategory;
startTime: number;
depth: number;
parentId?: string | undefined;
}
/**
* 性能采样数据
*/
export type ProfileSample = {
id: string;
name: string;
category: ProfileCategory;
startTime: number;
endTime: number;
duration: number;
selfTime: number;
parentId?: string | undefined;
/** 父级采样的名称(用于构建调用图) */
parentName?: string | undefined;
depth: number;
callCount: number;
metadata?: Record<string, unknown>;
}
/**
* 聚合后的采样统计
*/
export type ProfileSampleStats = {
name: string;
category: ProfileCategory;
/** 包含时间(包含子调用) */
inclusiveTime: number;
/** 独占时间(不包含子调用) */
exclusiveTime: number;
/** 调用次数 */
callCount: number;
/** 平均时间 */
averageTime: number;
/** 最小时间 */
minTime: number;
/** 最大时间 */
maxTime: number;
/** 占总帧时间百分比 */
percentOfFrame: number;
/** 占父级时间百分比 */
percentOfParent: number;
/** 子采样 */
children: ProfileSampleStats[];
/** 深度 */
depth: number;
}
/**
* 内存快照
*/
export type MemorySnapshot = {
timestamp: number;
/** 已使用堆内存 (bytes) */
usedHeapSize: number;
/** 总堆内存 (bytes) */
totalHeapSize: number;
/** 堆内存限制 (bytes) */
heapSizeLimit: number;
/** 使用率 (0-100) */
utilizationPercent: number;
/** 检测到的 GC 次数 */
gcCount: number;
}
/**
* 计数器数据
*/
export type ProfileCounter = {
name: string;
category: ProfileCategory;
value: number;
type: 'counter' | 'gauge';
history: Array<{ time: number; value: number }>;
}
/**
* 单帧性能数据
*/
export type ProfileFrame = {
frameNumber: number;
startTime: number;
endTime: number;
duration: number;
samples: ProfileSample[];
sampleStats: ProfileSampleStats[];
counters: Map<string, ProfileCounter>;
memory: MemorySnapshot;
/** 按类别分组的统计 */
categoryStats: Map<ProfileCategory, {
totalTime: number;
sampleCount: number;
percentOfFrame: number;
}>;
}
/**
* 分析器配置
*/
export type ProfilerConfig = {
/** 是否启用 */
enabled: boolean;
/** 最大历史帧数 */
maxFrameHistory: number;
/** 采样深度限制 */
maxSampleDepth: number;
/** 是否收集内存数据 */
collectMemory: boolean;
/** 内存采样间隔 (ms) */
memorySampleInterval: number;
/** 是否检测长任务 */
detectLongTasks: boolean;
/** 长任务阈值 (ms) */
longTaskThreshold: number;
/** 启用的类别 */
enabledCategories: Set<ProfileCategory>;
}
/**
* 长任务信息
*/
export type LongTaskInfo = {
startTime: number;
duration: number;
attribution: string[];
}
/**
* 调用关系节点
*/
export type CallGraphNode = {
name: string;
category: ProfileCategory;
/** 被调用次数 */
callCount: number;
/** 总耗时 */
totalTime: number;
/** 调用者列表 */
callers: Map<string, { count: number; totalTime: number }>;
/** 被调用者列表 */
callees: Map<string, { count: number; totalTime: number }>;
}
/**
* 性能分析报告
*/
export type ProfileReport = {
startTime: number;
endTime: number;
totalFrames: number;
averageFrameTime: number;
minFrameTime: number;
maxFrameTime: number;
p95FrameTime: number;
p99FrameTime: number;
/** 热点函数 (按耗时排序) */
hotspots: ProfileSampleStats[];
/** 调用图 */
callGraph: Map<string, CallGraphNode>;
/** 类别统计 */
categoryBreakdown: Map<ProfileCategory, {
totalTime: number;
averageTime: number;
percentOfTotal: number;
}>;
/** 内存趋势 */
memoryTrend: MemorySnapshot[];
/** 长任务列表 */
longTasks: LongTaskInfo[];
}
/**
* 默认配置
*/
export const DEFAULT_PROFILER_CONFIG: ProfilerConfig = {
enabled: false,
maxFrameHistory: 300,
maxSampleDepth: 32,
collectMemory: true,
memorySampleInterval: 100,
detectLongTasks: true,
longTaskThreshold: 50,
enabledCategories: new Set(Object.values(ProfileCategory))
};

View File

@@ -0,0 +1,8 @@
/**
* 性能分析器模块
*/
export * from './ProfilerTypes';
export { ProfilerSDK } from './ProfilerSDK';
export { AutoProfiler, Profile, ProfileClass } from './AutoProfiler';
export type { AutoProfilerConfig } from './AutoProfiler';

View 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;
}
}

View File

@@ -0,0 +1,18 @@
export type ITimer<TContext = unknown> = {
context: TContext;
/**
* 调用stop以停止此计时器再次运行。这对非重复计时器没有影响。
*/
stop(): void;
/**
* 将计时器的运行时间重置为0
*/
reset(): void;
/**
* 返回投向T的上下文作为方便
*/
getContext<T>(): T;
}

View 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;
private _timeInSeconds: number = 0;
private _repeats: boolean = false;
private _onTime!: (timer: ITimer<TContext>) => void;
private _isDone: boolean = false;
private _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!;
}
}

View File

@@ -0,0 +1,49 @@
import { Timer } from './Timer';
import { ITimer } from './ITimer';
import type { IService } from '../../Core/ServiceContainer';
import type { IUpdatable } from '../../Types/IUpdatable';
import { Updatable } from '../../Core/DI';
/**
* 定时器管理器
*
* 允许动作的延迟和重复执行
*/
@Updatable()
export class TimerManager implements IService, IUpdatable {
private _timers: Array<Timer<unknown>> = [];
public 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> {
const timer = new Timer<TContext>();
timer.initialize(timeInSeconds, repeats, context, onTime);
this._timers.push(timer as Timer<unknown>);
return timer;
}
/**
* 释放资源
*/
public dispose(): void {
for (const timer of this._timers) {
timer.unload();
}
this._timers = [];
}
}

View File

@@ -0,0 +1,11 @@
export * from './Extensions';
export * from './Pool';
export * from './Emitter';
export * from './GlobalManager';
export * from './PerformanceMonitor';
export { Time } from './Time';
export * from './Debug';
export * from './Logger';
export * from './BinarySerializer';
export * from './Profiler';
export * from './GUID';