mirror of
https://gitee.com/dgflash/oops-plugin-framework.git
synced 2026-05-07 19:07:30 +08:00
ECS 缓存管理优化
This commit is contained in:
@@ -5,6 +5,7 @@ import { ECSMatcher } from './ECSMatcher';
|
||||
import type { CompCtor, CompType, EntityCtor } from './ECSModel';
|
||||
import { ECSModel } from './ECSModel';
|
||||
import { ECSComblockSystem, ECSRootSystem, ECSSystem } from './ECSSystem';
|
||||
import { globalPoolCoordinator } from './ECSPoolManager';
|
||||
|
||||
/**
|
||||
* ECSEntity对象在destroy后,会回收到ECSModel.entityPool实体对象池中
|
||||
@@ -168,7 +169,7 @@ export namespace ecs {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的实体对象或从缓存中获取一个实体对象
|
||||
* 创建一个新的实体对象或从缓存中获取一个实体对象(使用动态池)
|
||||
* @param ctor 实体类
|
||||
*/
|
||||
export function getEntity<T extends Entity>(ctor: EntityCtor<T>): T {
|
||||
@@ -179,16 +180,19 @@ export namespace ecs {
|
||||
throw new Error(`${ctor.name} 实体没有注册`);
|
||||
}
|
||||
|
||||
// 获取实体对象池
|
||||
const entitys = ECSModel.entityPool.get(entityName) || [];
|
||||
let entity: T | undefined = entitys.pop() as T | undefined;
|
||||
// 使用动态池管理器
|
||||
const pool = globalPoolCoordinator.getPool(
|
||||
entityName,
|
||||
() => {
|
||||
const entity = new ctor();
|
||||
entity.eid = ECSModel.eid++; // 实体唯一编号
|
||||
entity.name = entityName;
|
||||
return entity;
|
||||
},
|
||||
ctor
|
||||
);
|
||||
|
||||
// 缓存中没有同类实体,则创建一个新的
|
||||
if (!entity) {
|
||||
entity = new ctor();
|
||||
entity.eid = ECSModel.eid++; // 实体唯一编号
|
||||
entity.name = entityName;
|
||||
}
|
||||
const entity = pool.get();
|
||||
|
||||
// 触发实体初始化逻辑
|
||||
const entityWithInit = entity as ECSEntity & { init?: () => void };
|
||||
@@ -227,6 +231,9 @@ export namespace ecs {
|
||||
});
|
||||
ECSModel.eid2Entity.clear();
|
||||
ECSModel.groups.clear();
|
||||
|
||||
// 保存历史数据用于下次学习
|
||||
globalPoolCoordinator.saveHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,19 +241,22 @@ export namespace ecs {
|
||||
* 注意:此操作会清空所有实体池、组件池和 Mask 池,请在确保不再需要这些缓存时调用
|
||||
*/
|
||||
export function clearPools(): void {
|
||||
// 清理实体池
|
||||
// 清理旧的实体池
|
||||
ECSModel.entityPool.forEach((pool) => {
|
||||
pool.length = 0;
|
||||
});
|
||||
ECSModel.entityPool.clear();
|
||||
|
||||
// 清理组件池
|
||||
// 清理旧的组件池
|
||||
ECSModel.compPools.forEach((pool) => {
|
||||
pool.length = 0;
|
||||
});
|
||||
|
||||
// 清理 Mask 对象池
|
||||
ECSMask.clearPool();
|
||||
|
||||
// 清理动态池管理器
|
||||
globalPoolCoordinator.getSmartManager().clearAll();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ecs } from './ECS';
|
||||
import { ECSMask } from './ECSMask';
|
||||
import type { CompCtor, CompType } from './ECSModel';
|
||||
import { ECSModel } from './ECSModel';
|
||||
import { globalPoolCoordinator } from './ECSPoolManager';
|
||||
|
||||
//#region 辅助方法
|
||||
|
||||
@@ -45,7 +46,7 @@ function deleteEntityComp(entity: ECSEntity, compName: string): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建组件对象
|
||||
* 创建组件对象(使用动态池管理)
|
||||
* @param ctor
|
||||
*/
|
||||
function createComp<T extends ecs.IComp>(ctor: CompCtor<T>): T {
|
||||
@@ -53,37 +54,36 @@ function createComp<T extends ecs.IComp>(ctor: CompCtor<T>): T {
|
||||
if (!cct) {
|
||||
throw Error(`没有找到该组件的构造函数,检查${ctor.compName}是否为不可构造的组件`);
|
||||
}
|
||||
const comps = ECSModel.compPools.get(ctor.tid);
|
||||
if (!comps) {
|
||||
throw Error(`组件${ctor.compName}的对象池不存在`);
|
||||
}
|
||||
const component = comps.pop() || new (cct as CompCtor<T>)();
|
||||
|
||||
// 使用动态池管理器
|
||||
const pool = globalPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (cct as CompCtor<T>)(),
|
||||
ctor
|
||||
);
|
||||
|
||||
const component = pool.get();
|
||||
return component as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁实体
|
||||
* 销毁实体(使用动态池管理)
|
||||
*
|
||||
* 缓存销毁的实体,下次新建实体时会优先从缓存中拿
|
||||
* @param entity
|
||||
*/
|
||||
function destroyEntity(entity: ECSEntity): void {
|
||||
if (ECSModel.eid2Entity.has(entity.eid)) {
|
||||
let entitys = ECSModel.entityPool.get(entity.name);
|
||||
if (entitys === undefined) {
|
||||
entitys = [];
|
||||
ECSModel.entityPool.set(entity.name, entitys);
|
||||
}
|
||||
// 限制对象池大小,防止内存无限增长
|
||||
if (entitys.length < ECSModel.MAX_ENTITY_POOL_SIZE) {
|
||||
// 池未满:保留 Uint32Array,仅清空位图,复用时零开销
|
||||
entity.getMask().clear();
|
||||
entitys.push(entity);
|
||||
}
|
||||
else {
|
||||
// 池已满:实体真正丢弃,将 Uint32Array 回收到 MaskPool 供后续复用
|
||||
entity.getMask().destroy();
|
||||
}
|
||||
// 使用动态池管理器
|
||||
const pool = globalPoolCoordinator.getPool(
|
||||
entity.name,
|
||||
() => new ECSEntity()
|
||||
);
|
||||
|
||||
// 清空mask并回收
|
||||
entity.getMask().clear();
|
||||
pool.recycle(entity);
|
||||
|
||||
ECSModel.eid2Entity.delete(entity.eid);
|
||||
}
|
||||
else {
|
||||
@@ -133,7 +133,7 @@ export class ECSEntity {
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加子实体
|
||||
* 添加子实体(带循环引用检测)
|
||||
* @param entity 被添加的实体对象
|
||||
* @returns 子实体的唯一编号, -1表示添加失败
|
||||
*/
|
||||
@@ -147,11 +147,29 @@ export class ECSEntity {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 检测循环引用
|
||||
if (this.hasAncestor(entity)) {
|
||||
console.error(`检测到循环引用: ${this.name} -> ${entity.name}`);
|
||||
return -1;
|
||||
}
|
||||
|
||||
entity._parent = this;
|
||||
this.childs.set(entity.eid, entity);
|
||||
return entity.eid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否存在祖先关系(防止循环引用)
|
||||
*/
|
||||
private hasAncestor(entity: ECSEntity): boolean {
|
||||
let current: ECSEntity | null = this;
|
||||
while (current) {
|
||||
if (current === entity) return true;
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除子实体
|
||||
* @param entity 被移除的实体对象
|
||||
@@ -290,11 +308,16 @@ export class ECSEntity {
|
||||
if (isRecycle) {
|
||||
comp.reset();
|
||||
|
||||
// 回收组件到指定缓存池中,限制池大小
|
||||
// 使用动态池管理器回收组件
|
||||
if (comp.canRecycle) {
|
||||
const compPoolsType = ECSModel.compPools.get(componentTypeId);
|
||||
if (compPoolsType && compPoolsType.length < ECSModel.MAX_COMP_POOL_SIZE) {
|
||||
compPoolsType.push(comp);
|
||||
const ctor = ECSModel.compCtors[componentTypeId];
|
||||
if (ctor) {
|
||||
const pool = globalPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (ctor as any)(),
|
||||
ctor
|
||||
);
|
||||
pool.recycle(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,9 +331,14 @@ export class ECSEntity {
|
||||
console.warn(`实体 ${this.name} 缓存组件数量超过限制,强制回收组件 ${compName}`);
|
||||
comp.reset();
|
||||
if (comp.canRecycle) {
|
||||
const compPoolsType = ECSModel.compPools.get(componentTypeId);
|
||||
if (compPoolsType && compPoolsType.length < ECSModel.MAX_COMP_POOL_SIZE) {
|
||||
compPoolsType.push(comp);
|
||||
const ctor = ECSModel.compCtors[componentTypeId];
|
||||
if (ctor) {
|
||||
const pool = globalPoolCoordinator.getPool(
|
||||
ctor.compName,
|
||||
() => new (ctor as any)(),
|
||||
ctor
|
||||
);
|
||||
pool.recycle(comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,25 @@ export class ECSGroup<E extends ECSEntity = ECSEntity> {
|
||||
private _cacheValid = false;
|
||||
|
||||
/**
|
||||
* 符合规则的实体
|
||||
* 符合规则的实体(带数组容量管理)
|
||||
*/
|
||||
get matchEntities(): E[] {
|
||||
if (!this._cacheValid) {
|
||||
// 复用数组,减少 GC 压力
|
||||
const cache = this._entitiesCache;
|
||||
cache.length = 0;
|
||||
const targetSize = this._matchEntities.size;
|
||||
|
||||
// 如果缓存数组过大,重新创建以释放内存
|
||||
if (cache.length > targetSize * 2 && targetSize < 100) {
|
||||
this._entitiesCache = [];
|
||||
} else {
|
||||
cache.length = 0;
|
||||
}
|
||||
|
||||
// 直接遍历 Map values 比 Array.from 更高效
|
||||
const iterator = this._matchEntities.values();
|
||||
let result = iterator.next();
|
||||
while (!result.done) {
|
||||
cache.push(result.value);
|
||||
this._entitiesCache.push(result.value);
|
||||
result = iterator.next();
|
||||
}
|
||||
|
||||
|
||||
464
assets/libs/ecs/ECSMemoryMonitor.ts
Normal file
464
assets/libs/ecs/ECSMemoryMonitor.ts
Normal file
@@ -0,0 +1,464 @@
|
||||
/*
|
||||
* ECS内存池监控工具
|
||||
* 用于实时监控和诊断对象池的使用情况
|
||||
*/
|
||||
|
||||
import { globalPoolCoordinator } from './ECSPoolManager';
|
||||
|
||||
/** 内存监控统计 */
|
||||
export interface MemoryStats {
|
||||
poolName: string;
|
||||
currentSize: number;
|
||||
maxSize: number;
|
||||
minSize: number;
|
||||
hitRate: number;
|
||||
createCount: number;
|
||||
recycleCount: number;
|
||||
createFrequency: number;
|
||||
accessFrequency: number;
|
||||
estimatedMemory: string;
|
||||
memoryBytes: number;
|
||||
hasIssue: boolean;
|
||||
issueType?: 'low-hit-rate' | 'memory-leak' | 'over-capacity' | 'unused';
|
||||
}
|
||||
|
||||
/** 监控配置 */
|
||||
export interface MonitorConfig {
|
||||
/** 详细程度:compact(简洁) | normal(普通) | detailed(详细) */
|
||||
level?: 'compact' | 'normal' | 'detailed';
|
||||
/** 是否显示异常池 */
|
||||
showIssues?: boolean;
|
||||
/** 是否显示内存信息 */
|
||||
showMemory?: boolean;
|
||||
/** 是否按某个字段排序 */
|
||||
sortBy?: 'frequency' | 'hitRate' | 'memory' | 'name';
|
||||
}
|
||||
|
||||
/** 异常检测阈值 */
|
||||
interface IssueThresholds {
|
||||
lowHitRate: number; // 低命中率阈值(默认50%)
|
||||
highCapacity: number; // 高容量使用率阈值(默认90%)
|
||||
unusedTime: number; // 未使用时间阈值(默认60秒)
|
||||
memoryLeakGrowth: number; // 内存泄漏增长率阈值(默认200%)
|
||||
}
|
||||
|
||||
/**
|
||||
* ECS内存监控器
|
||||
*/
|
||||
export class ECSMemoryMonitor {
|
||||
private static instance: ECSMemoryMonitor;
|
||||
private updateInterval: number = 1000; // 1秒更新一次
|
||||
private isMonitoring = false;
|
||||
private timerId: any = null;
|
||||
private lastStats: Map<string, MemoryStats> = new Map();
|
||||
|
||||
private thresholds: IssueThresholds = {
|
||||
lowHitRate: 50,
|
||||
highCapacity: 90,
|
||||
unusedTime: 60000,
|
||||
memoryLeakGrowth: 200
|
||||
};
|
||||
|
||||
private constructor() { }
|
||||
|
||||
static getInstance(): ECSMemoryMonitor {
|
||||
if (!ECSMemoryMonitor.instance) {
|
||||
ECSMemoryMonitor.instance = new ECSMemoryMonitor();
|
||||
}
|
||||
return ECSMemoryMonitor.instance;
|
||||
}
|
||||
|
||||
/** 设置异常检测阈值 */
|
||||
setThresholds(thresholds: Partial<IssueThresholds>): void {
|
||||
this.thresholds = { ...this.thresholds, ...thresholds };
|
||||
}
|
||||
|
||||
/** 获取所有池的统计信息 */
|
||||
getStats(): MemoryStats[] {
|
||||
const smartManager = globalPoolCoordinator.getSmartManager();
|
||||
const allMetrics = smartManager.getAllMetrics();
|
||||
const stats: MemoryStats[] = [];
|
||||
const now = Date.now();
|
||||
|
||||
allMetrics.forEach((metrics, poolName) => {
|
||||
const totalAccess = metrics.hitCount + metrics.missCount;
|
||||
const hitRate = totalAccess > 0 ? (metrics.hitCount / totalAccess) * 100 : 0;
|
||||
|
||||
// 计算创建频率
|
||||
let createFrequency = 0;
|
||||
if (metrics.createTimes.length >= 2) {
|
||||
const timeSpan = metrics.createTimes[metrics.createTimes.length - 1] -
|
||||
metrics.createTimes[0];
|
||||
if (timeSpan > 0) {
|
||||
createFrequency = (metrics.createTimes.length / timeSpan) * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算访问频率
|
||||
let accessFrequency = 0;
|
||||
if (metrics.accessTimes && metrics.accessTimes.length >= 2) {
|
||||
const timeSpan = metrics.accessTimes[metrics.accessTimes.length - 1] -
|
||||
metrics.accessTimes[0];
|
||||
if (timeSpan > 0) {
|
||||
accessFrequency = (metrics.accessTimes.length / timeSpan) * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
// 检测异常
|
||||
const issue = this.detectIssue(poolName, metrics, hitRate, now);
|
||||
|
||||
const stat: MemoryStats = {
|
||||
poolName,
|
||||
currentSize: metrics.currentSize,
|
||||
maxSize: metrics.maxSize,
|
||||
minSize: metrics.minSize,
|
||||
hitRate,
|
||||
createCount: metrics.createCount,
|
||||
recycleCount: metrics.recycleCount,
|
||||
createFrequency,
|
||||
accessFrequency,
|
||||
estimatedMemory: this.formatBytes(metrics.totalMemory),
|
||||
memoryBytes: metrics.totalMemory,
|
||||
hasIssue: issue !== null,
|
||||
issueType: issue || undefined
|
||||
};
|
||||
|
||||
stats.push(stat);
|
||||
this.lastStats.set(poolName, stat);
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/** 检测池的异常情况 */
|
||||
private detectIssue(poolName: string, metrics: any, hitRate: number, now: number):
|
||||
'low-hit-rate' | 'memory-leak' | 'over-capacity' | 'unused' | null {
|
||||
|
||||
// 检测低命中率
|
||||
if (metrics.hitCount + metrics.missCount > 20 && hitRate < this.thresholds.lowHitRate) {
|
||||
return 'low-hit-rate';
|
||||
}
|
||||
|
||||
// 检测容量过高
|
||||
const capacityUsage = metrics.maxSize > 0 ? (metrics.currentSize / metrics.maxSize) * 100 : 0;
|
||||
if (capacityUsage > this.thresholds.highCapacity) {
|
||||
return 'over-capacity';
|
||||
}
|
||||
|
||||
// 检测长期未使用
|
||||
if (now - metrics.lastAccessTime > this.thresholds.unusedTime) {
|
||||
return 'unused';
|
||||
}
|
||||
|
||||
// 检测内存泄漏(与上次对比)
|
||||
const lastStat = this.lastStats.get(poolName);
|
||||
if (lastStat && lastStat.memoryBytes > 0) {
|
||||
const growthRate = (metrics.totalMemory / lastStat.memoryBytes) * 100;
|
||||
if (growthRate > this.thresholds.memoryLeakGrowth && metrics.totalMemory > 1024 * 1024) {
|
||||
return 'memory-leak';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 格式化字节数 */
|
||||
private formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一的日志输出方法
|
||||
* @param config 监控配置
|
||||
*/
|
||||
log(config: MonitorConfig = {}): void {
|
||||
const {
|
||||
level = 'normal',
|
||||
showIssues = true,
|
||||
showMemory = true,
|
||||
sortBy = 'frequency'
|
||||
} = config;
|
||||
|
||||
const stats = this.getStats();
|
||||
|
||||
if (stats.length === 0) {
|
||||
console.log('[ECS Pool] 暂无对象池数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 排序
|
||||
this.sortStats(stats, sortBy);
|
||||
|
||||
// 根据详细程度输出
|
||||
switch (level) {
|
||||
case 'compact':
|
||||
this.logCompact(stats, showMemory);
|
||||
break;
|
||||
case 'detailed':
|
||||
this.logDetailed(stats, showIssues, showMemory);
|
||||
break;
|
||||
default:
|
||||
this.logNormal(stats, showIssues, showMemory);
|
||||
}
|
||||
}
|
||||
|
||||
/** 简洁输出 */
|
||||
private logCompact(stats: MemoryStats[], showMemory: boolean): void {
|
||||
const totalCurrent = stats.reduce((sum, s) => sum + s.currentSize, 0);
|
||||
const avgHitRate = stats.reduce((sum, s) => sum + s.hitRate, 0) / stats.length;
|
||||
const totalMemory = stats.reduce((sum, s) => sum + s.memoryBytes, 0);
|
||||
const issueCount = stats.filter(s => s.hasIssue).length;
|
||||
|
||||
let output = `[ECS Pool] 池数:${stats.length} | 缓存:${totalCurrent} | 命中率:${avgHitRate.toFixed(1)}%`;
|
||||
if (showMemory) {
|
||||
output += ` | 内存:${this.formatBytes(totalMemory)}`;
|
||||
}
|
||||
if (issueCount > 0) {
|
||||
output += ` | ⚠️异常:${issueCount}`;
|
||||
}
|
||||
console.log(output);
|
||||
}
|
||||
|
||||
/** 普通输出 */
|
||||
private logNormal(stats: MemoryStats[], showIssues: boolean, showMemory: boolean): void {
|
||||
console.log('\n========== ECS 对象池统计 ==========');
|
||||
console.log(`总池数量: ${stats.length}`);
|
||||
|
||||
if (showMemory) {
|
||||
const totalMemory = stats.reduce((sum, s) => sum + s.memoryBytes, 0);
|
||||
const memoryUsage = globalPoolCoordinator.getMemoryUsage();
|
||||
console.log(`总内存: ${this.formatBytes(totalMemory)} / ${this.formatBytes(memoryUsage.budget)} (${memoryUsage.percent.toFixed(1)}%)`);
|
||||
}
|
||||
|
||||
if (showIssues) {
|
||||
const issueStats = stats.filter(s => s.hasIssue);
|
||||
if (issueStats.length > 0) {
|
||||
console.log(`⚠️ 异常池: ${issueStats.length}`);
|
||||
}
|
||||
}
|
||||
console.log('');
|
||||
|
||||
stats.forEach(stat => {
|
||||
const icon = this.getStatusIcon(stat);
|
||||
console.log(`${icon} 【${stat.poolName}】`);
|
||||
console.log(` 容量: ${stat.currentSize}/${stat.maxSize} (最小:${stat.minSize})`);
|
||||
console.log(` 命中率: ${stat.hitRate.toFixed(1)}% ${this.getHitRateDesc(stat.hitRate)}`);
|
||||
console.log(` 创建/回收: ${stat.createCount}/${stat.recycleCount}`);
|
||||
console.log(` 频率: 创建${stat.createFrequency.toFixed(2)}/s, 访问${stat.accessFrequency.toFixed(2)}/s`);
|
||||
|
||||
if (showMemory) {
|
||||
console.log(` 内存: ${stat.estimatedMemory}`);
|
||||
}
|
||||
|
||||
if (showIssues && stat.hasIssue) {
|
||||
console.log(` ⚠️ 异常: ${this.getIssueDesc(stat.issueType!)}`);
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
|
||||
console.log('====================================\n');
|
||||
}
|
||||
|
||||
/** 详细输出 */
|
||||
private logDetailed(stats: MemoryStats[], showIssues: boolean, showMemory: boolean): void {
|
||||
console.log('ECS 对象池详细统计报告');
|
||||
|
||||
// 总览
|
||||
const totalCreate = stats.reduce((sum, s) => sum + s.createCount, 0);
|
||||
const totalRecycle = stats.reduce((sum, s) => sum + s.recycleCount, 0);
|
||||
const totalCurrent = stats.reduce((sum, s) => sum + s.currentSize, 0);
|
||||
const avgHitRate = stats.reduce((sum, s) => sum + s.hitRate, 0) / stats.length;
|
||||
const totalMemory = stats.reduce((sum, s) => sum + s.memoryBytes, 0);
|
||||
|
||||
console.log('📊 总体概况');
|
||||
console.log('─────────────────────────────────────────────────────────');
|
||||
console.log(` 对象池数量: ${stats.length}`);
|
||||
console.log(` 总创建次数: ${totalCreate}`);
|
||||
console.log(` 总回收次数: ${totalRecycle}`);
|
||||
console.log(` 当前缓存数: ${totalCurrent}`);
|
||||
console.log(` 平均命中率: ${avgHitRate.toFixed(1)}%`);
|
||||
|
||||
if (showMemory) {
|
||||
const memoryUsage = globalPoolCoordinator.getMemoryUsage();
|
||||
console.log(` 总内存占用: ${this.formatBytes(totalMemory)}`);
|
||||
console.log(` 内存预算: ${this.formatBytes(memoryUsage.budget)}`);
|
||||
console.log(` 内存使用率: ${memoryUsage.percent.toFixed(1)}%`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// 异常池
|
||||
if (showIssues) {
|
||||
const issueStats = stats.filter(s => s.hasIssue);
|
||||
if (issueStats.length > 0) {
|
||||
console.log('⚠️ 异常池列表');
|
||||
console.log('─────────────────────────────────────────────────────────');
|
||||
issueStats.forEach(stat => {
|
||||
console.log(` ${stat.poolName}: ${this.getIssueDesc(stat.issueType!)}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('📈 各池详细数据');
|
||||
console.log('─────────────────────────────────────────────────────────\n');
|
||||
|
||||
stats.forEach((stat, index) => {
|
||||
const usage = stat.maxSize > 0 ? (stat.currentSize / stat.maxSize * 100).toFixed(1) : '0';
|
||||
const icon = this.getStatusIcon(stat);
|
||||
|
||||
console.log(`${index + 1}. ${stat.poolName} ${icon}`);
|
||||
console.log(` ├─ 容量: ${stat.currentSize}/${stat.maxSize} (${usage}% 使用率)`);
|
||||
console.log(` ├─ 命中率: ${stat.hitRate.toFixed(1)}% ${this.getHitRateDesc(stat.hitRate)}`);
|
||||
console.log(` ├─ 创建/回收: ${stat.createCount} / ${stat.recycleCount}`);
|
||||
console.log(` ├─ 创建频率: ${stat.createFrequency.toFixed(2)} 个/秒`);
|
||||
console.log(` ├─ 访问频率: ${stat.accessFrequency.toFixed(2)} 个/秒`);
|
||||
|
||||
if (showMemory) {
|
||||
console.log(` ├─ 估算内存: ${stat.estimatedMemory}`);
|
||||
}
|
||||
|
||||
if (showIssues && stat.hasIssue) {
|
||||
console.log(` └─ ⚠️ 异常: ${this.getIssueDesc(stat.issueType!)}`);
|
||||
} else {
|
||||
console.log(` └─ 状态: 正常`);
|
||||
}
|
||||
console.log('');
|
||||
});
|
||||
|
||||
console.log('═════════════════════════════════════════════════════════\n');
|
||||
}
|
||||
|
||||
/** 排序统计数据 */
|
||||
private sortStats(stats: MemoryStats[], sortBy: string): void {
|
||||
switch (sortBy) {
|
||||
case 'frequency':
|
||||
stats.sort((a, b) => b.createFrequency - a.createFrequency);
|
||||
break;
|
||||
case 'hitRate':
|
||||
stats.sort((a, b) => b.hitRate - a.hitRate);
|
||||
break;
|
||||
case 'memory':
|
||||
stats.sort((a, b) => b.memoryBytes - a.memoryBytes);
|
||||
break;
|
||||
case 'name':
|
||||
stats.sort((a, b) => a.poolName.localeCompare(b.poolName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取状态图标 */
|
||||
private getStatusIcon(stat: MemoryStats): string {
|
||||
if (stat.hasIssue) {
|
||||
switch (stat.issueType) {
|
||||
case 'low-hit-rate': return '🔴';
|
||||
case 'memory-leak': return '💥';
|
||||
case 'over-capacity': return '⚠️';
|
||||
case 'unused': return '💤';
|
||||
}
|
||||
}
|
||||
return stat.hitRate >= 80 ? '🟢' : stat.hitRate >= 50 ? '🟡' : '🔴';
|
||||
}
|
||||
|
||||
/** 获取异常描述 */
|
||||
private getIssueDesc(issueType: string): string {
|
||||
switch (issueType) {
|
||||
case 'low-hit-rate': return '命中率过低';
|
||||
case 'memory-leak': return '疑似内存泄漏';
|
||||
case 'over-capacity': return '容量使用率过高';
|
||||
case 'unused': return '长期未使用';
|
||||
default: return '未知异常';
|
||||
}
|
||||
}
|
||||
|
||||
/** 打印指定池的统计 */
|
||||
logPool(poolName: string): void {
|
||||
const stats = this.getStats();
|
||||
const stat = stats.find(s => s.poolName === poolName);
|
||||
|
||||
if (!stat) {
|
||||
console.log(`[ECS Pool] 未找到池: ${poolName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const usage = stat.maxSize > 0 ? (stat.currentSize / stat.maxSize * 100).toFixed(1) : '0';
|
||||
const icon = this.getStatusIcon(stat);
|
||||
|
||||
console.log(`\n【${stat.poolName}】${icon}`);
|
||||
console.log(` 容量使用: ${stat.currentSize}/${stat.maxSize} (${usage}%)`);
|
||||
console.log(` 命中率: ${stat.hitRate.toFixed(1)}% ${this.getHitRateDesc(stat.hitRate)}`);
|
||||
console.log(` 创建次数: ${stat.createCount}`);
|
||||
console.log(` 回收次数: ${stat.recycleCount}`);
|
||||
console.log(` 创建频率: ${stat.createFrequency.toFixed(2)} 个/秒`);
|
||||
console.log(` 访问频率: ${stat.accessFrequency.toFixed(2)} 个/秒`);
|
||||
console.log(` 估算内存: ${stat.estimatedMemory}`);
|
||||
|
||||
if (stat.hasIssue) {
|
||||
console.log(` ⚠️ 异常: ${this.getIssueDesc(stat.issueType!)}`);
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/** 获取命中率描述 */
|
||||
private getHitRateDesc(hitRate: number): string {
|
||||
if (hitRate >= 90) return '(优秀)';
|
||||
if (hitRate >= 70) return '(良好)';
|
||||
if (hitRate >= 50) return '(一般)';
|
||||
return '(较差)';
|
||||
}
|
||||
|
||||
/** 开始监控(定时打印) */
|
||||
startMonitoring(config: MonitorConfig & { interval?: number } = {}): void {
|
||||
if (this.isMonitoring) {
|
||||
console.warn('[Monitor] 监控已在运行中');
|
||||
return;
|
||||
}
|
||||
|
||||
const { interval = 5000, ...monitorConfig } = config;
|
||||
this.updateInterval = interval;
|
||||
this.isMonitoring = true;
|
||||
|
||||
console.log(`[Monitor] 开始监控,更新间隔: ${interval}ms`);
|
||||
|
||||
this.timerId = setInterval(() => {
|
||||
this.log(monitorConfig);
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/** 停止监控 */
|
||||
stopMonitoring(): void {
|
||||
if (!this.isMonitoring) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.timerId) {
|
||||
clearInterval(this.timerId);
|
||||
this.timerId = null;
|
||||
}
|
||||
|
||||
this.isMonitoring = false;
|
||||
console.log('[Monitor] 监控已停止');
|
||||
}
|
||||
|
||||
/** 获取异常池列表 */
|
||||
getIssues(): MemoryStats[] {
|
||||
return this.getStats().filter(s => s.hasIssue);
|
||||
}
|
||||
|
||||
/** 获取类型分析报告 */
|
||||
getTypeProfiles() {
|
||||
return globalPoolCoordinator.getSmartManager().getTypeProfiles();
|
||||
}
|
||||
|
||||
/** 触发内存清理 */
|
||||
cleanup(maxAge: number = 60000): number {
|
||||
return globalPoolCoordinator.getSmartManager().cleanupAllStale(maxAge);
|
||||
}
|
||||
}
|
||||
|
||||
/** 导出单例 */
|
||||
export const ecsMonitor = ECSMemoryMonitor.getInstance();
|
||||
|
||||
11
assets/libs/ecs/ECSMemoryMonitor.ts.meta
Normal file
11
assets/libs/ecs/ECSMemoryMonitor.ts.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "c1cce79d-eaea-4205-b53c-982ee27bda62",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"simulateGlobals": []
|
||||
}
|
||||
}
|
||||
808
assets/libs/ecs/ECSPoolManager.ts
Normal file
808
assets/libs/ecs/ECSPoolManager.ts
Normal file
@@ -0,0 +1,808 @@
|
||||
/*
|
||||
* 动态对象池管理系统
|
||||
* 三阶段实现:基础动态池 + 智能特性 + 可选配置
|
||||
*/
|
||||
|
||||
//#region 类型定义
|
||||
|
||||
/** 池统计指标 */
|
||||
interface PoolMetrics {
|
||||
// 使用统计
|
||||
createCount: number; // 创建次数
|
||||
recycleCount: number; // 回收次数
|
||||
hitCount: number; // 命中次数(从池中获取)
|
||||
missCount: number; // 未命中次数(需要新建)
|
||||
|
||||
// 容量管理
|
||||
currentSize: number; // 当前池大小
|
||||
maxSize: number; // 最大容量
|
||||
minSize: number; // 最小容量
|
||||
|
||||
// 时间统计
|
||||
lastAccessTime: number; // 最后访问时间
|
||||
lastAdjustTime: number; // 最后调整时间
|
||||
createTimes: number[]; // 创建时间戳列表(用于计算频率)
|
||||
|
||||
// 内存估算
|
||||
estimatedItemSize: number; // 单个对象估算大小(字节)
|
||||
totalMemory: number; // 总内存占用
|
||||
|
||||
// LRU支持
|
||||
accessTimes: number[]; // 访问时间戳(用于LRU)
|
||||
}
|
||||
|
||||
/** 对象类型特征 */
|
||||
interface TypeProfile {
|
||||
typeName: string;
|
||||
category: 'long-lived' | 'high-frequency' | 'normal' | 'rare';
|
||||
peakRate: number; // 峰值创建速率(个/秒)
|
||||
reuseRate: number; // 复用率
|
||||
avgMemorySize: number; // 平均内存大小
|
||||
shouldPool: boolean; // 是否应该池化
|
||||
}
|
||||
|
||||
/** 池配置 */
|
||||
export interface PoolConfig {
|
||||
enabled?: boolean; // 是否启用池化
|
||||
minSize?: number; // 最小容量
|
||||
maxSize?: number; // 最大容量
|
||||
strategy?: 'dynamic' | 'fixed' | 'lru'; // 策略
|
||||
preWarmSize?: number; // 预热数量
|
||||
}
|
||||
|
||||
/** 池提示(性能优化) */
|
||||
export interface PoolHint {
|
||||
expectedPeak?: number; // 预期峰值
|
||||
sceneAware?: string; // 场景感知标识
|
||||
highFrequency?: boolean; // 是否高频对象
|
||||
}
|
||||
|
||||
/** 历史数据 */
|
||||
interface HistoryData {
|
||||
typeName: string;
|
||||
peakCount: number;
|
||||
avgCount: number;
|
||||
lastPlayTime: number;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 第一阶段:基础动态池
|
||||
|
||||
/**
|
||||
* 动态对象池
|
||||
* 自动统计、自动调整容量、支持LRU策略
|
||||
*/
|
||||
class DynamicPool<T> {
|
||||
private pool: T[] = [];
|
||||
private metrics: PoolMetrics;
|
||||
private config: PoolConfig;
|
||||
private lruTimestamps: Map<T, number> = new Map(); // LRU时间戳
|
||||
|
||||
constructor(
|
||||
private typeName: string,
|
||||
private factory: () => T,
|
||||
config: Partial<PoolConfig> = {}
|
||||
) {
|
||||
this.config = {
|
||||
enabled: true,
|
||||
minSize: 10,
|
||||
maxSize: 1000,
|
||||
strategy: 'dynamic',
|
||||
preWarmSize: 0,
|
||||
...config
|
||||
};
|
||||
|
||||
this.metrics = {
|
||||
createCount: 0,
|
||||
recycleCount: 0,
|
||||
hitCount: 0,
|
||||
missCount: 0,
|
||||
currentSize: 0,
|
||||
maxSize: this.config.maxSize!,
|
||||
minSize: this.config.minSize!,
|
||||
lastAccessTime: Date.now(),
|
||||
lastAdjustTime: Date.now(),
|
||||
createTimes: [],
|
||||
estimatedItemSize: 0,
|
||||
totalMemory: 0,
|
||||
accessTimes: []
|
||||
};
|
||||
|
||||
// 预热
|
||||
if (this.config.preWarmSize! > 0) {
|
||||
this.preWarm(this.config.preWarmSize!);
|
||||
}
|
||||
|
||||
// 估算对象大小
|
||||
this.estimateItemSize();
|
||||
}
|
||||
|
||||
/** 估算单个对象的内存大小 */
|
||||
private estimateItemSize(): void {
|
||||
if (this.metrics.estimatedItemSize > 0) return;
|
||||
|
||||
try {
|
||||
const sample = this.factory();
|
||||
this.metrics.estimatedItemSize = this.calculateObjectSize(sample);
|
||||
this.updateTotalMemory();
|
||||
} catch (e) {
|
||||
console.warn(`[Pool] ${this.typeName} 无法估算对象大小`, e);
|
||||
this.metrics.estimatedItemSize = 1024; // 默认1KB
|
||||
}
|
||||
}
|
||||
|
||||
/** 计算对象大小(粗略估算) */
|
||||
private calculateObjectSize(obj: any): number {
|
||||
let size = 0;
|
||||
const seen = new WeakSet();
|
||||
|
||||
const calculate = (o: any): void => {
|
||||
if (o === null || o === undefined) return;
|
||||
if (typeof o !== 'object') {
|
||||
size += 8; // 基本类型约8字节
|
||||
return;
|
||||
}
|
||||
if (seen.has(o)) return;
|
||||
seen.add(o);
|
||||
|
||||
// 对象本身的开销
|
||||
size += 32;
|
||||
|
||||
// 遍历属性
|
||||
for (const key in o) {
|
||||
if (o.hasOwnProperty(key)) {
|
||||
size += key.length * 2; // 键名
|
||||
const value = o[key];
|
||||
if (typeof value === 'string') {
|
||||
size += value.length * 2;
|
||||
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
size += 8;
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
calculate(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
calculate(obj);
|
||||
return size;
|
||||
}
|
||||
|
||||
/** 更新总内存占用 */
|
||||
private updateTotalMemory(): void {
|
||||
this.metrics.totalMemory = this.metrics.currentSize * this.metrics.estimatedItemSize;
|
||||
}
|
||||
|
||||
/** 获取对象 */
|
||||
get(): T {
|
||||
const now = Date.now();
|
||||
this.metrics.lastAccessTime = now;
|
||||
this.metrics.accessTimes.push(now);
|
||||
|
||||
// 只保留最近100次访问时间
|
||||
if (this.metrics.accessTimes.length > 100) {
|
||||
this.metrics.accessTimes.shift();
|
||||
}
|
||||
|
||||
if (this.pool.length > 0) {
|
||||
this.metrics.hitCount++;
|
||||
const item = this.pool.pop()!;
|
||||
|
||||
// LRU: 记录使用时间
|
||||
if (this.config.strategy === 'lru') {
|
||||
this.lruTimestamps.set(item, now);
|
||||
}
|
||||
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
this.updateTotalMemory();
|
||||
return item;
|
||||
}
|
||||
|
||||
// 池中没有,创建新对象
|
||||
this.metrics.missCount++;
|
||||
this.metrics.createCount++;
|
||||
this.metrics.createTimes.push(now);
|
||||
|
||||
// 只保留最近100次创建时间
|
||||
if (this.metrics.createTimes.length > 100) {
|
||||
this.metrics.createTimes.shift();
|
||||
}
|
||||
|
||||
const item = this.factory();
|
||||
|
||||
// LRU: 记录创建时间
|
||||
if (this.config.strategy === 'lru') {
|
||||
this.lruTimestamps.set(item, now);
|
||||
}
|
||||
|
||||
// 触发自动调整
|
||||
this.autoAdjust();
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/** 回收对象 */
|
||||
recycle(item: T): void {
|
||||
if (!this.config.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.metrics.recycleCount++;
|
||||
|
||||
// 检查是否超过最大容量
|
||||
if (this.pool.length < this.metrics.maxSize) {
|
||||
this.pool.push(item);
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
this.updateTotalMemory();
|
||||
} else if (this.config.strategy === 'lru') {
|
||||
// LRU策略:移除最久未使用的对象
|
||||
this.evictLRU();
|
||||
this.pool.push(item);
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
this.updateTotalMemory();
|
||||
}
|
||||
|
||||
// 触发自动调整
|
||||
this.autoAdjust();
|
||||
}
|
||||
|
||||
/** LRU淘汰策略 */
|
||||
private evictLRU(): void {
|
||||
if (this.pool.length === 0) return;
|
||||
|
||||
let oldestItem: T | null = null;
|
||||
let oldestTime = Date.now();
|
||||
|
||||
// 找到最久未使用的对象
|
||||
for (const item of this.pool) {
|
||||
const time = this.lruTimestamps.get(item) || 0;
|
||||
if (time < oldestTime) {
|
||||
oldestTime = time;
|
||||
oldestItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
// 移除最久未使用的对象
|
||||
if (oldestItem) {
|
||||
const index = this.pool.indexOf(oldestItem);
|
||||
if (index > -1) {
|
||||
this.pool.splice(index, 1);
|
||||
this.lruTimestamps.delete(oldestItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 自动调整池容量 */
|
||||
private autoAdjust(): void {
|
||||
const now = Date.now();
|
||||
const timeSinceLastAdjust = now - this.metrics.lastAdjustTime;
|
||||
|
||||
// 每5秒调整一次
|
||||
if (timeSinceLastAdjust < 5000) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.metrics.lastAdjustTime = now;
|
||||
|
||||
if (this.config.strategy === 'fixed') {
|
||||
return; // 固定策略不调整
|
||||
}
|
||||
|
||||
// 计算命中率
|
||||
const totalAccess = this.metrics.hitCount + this.metrics.missCount;
|
||||
if (totalAccess < 10) {
|
||||
return; // 样本太少,不调整
|
||||
}
|
||||
|
||||
const hitRate = this.metrics.hitCount / totalAccess;
|
||||
|
||||
// 根据命中率调整最大容量
|
||||
if (hitRate < 0.5) {
|
||||
// 命中率低,需要扩容
|
||||
const newMax = Math.min(
|
||||
Math.floor(this.metrics.maxSize * 1.5),
|
||||
this.config.maxSize!
|
||||
);
|
||||
if (newMax > this.metrics.maxSize) {
|
||||
console.log(`[Pool] ${this.typeName} 扩容: ${this.metrics.maxSize} -> ${newMax} (命中率: ${(hitRate * 100).toFixed(1)}%)`);
|
||||
this.metrics.maxSize = newMax;
|
||||
}
|
||||
} else if (hitRate > 0.9 && this.pool.length > this.metrics.minSize * 2) {
|
||||
// 命中率高且池很大,可以缩容
|
||||
const newMax = Math.max(
|
||||
Math.floor(this.metrics.maxSize * 0.8),
|
||||
this.metrics.minSize
|
||||
);
|
||||
if (newMax < this.metrics.maxSize) {
|
||||
console.log(`[Pool] ${this.typeName} 缩容: ${this.metrics.maxSize} -> ${newMax} (命中率: ${(hitRate * 100).toFixed(1)}%)`);
|
||||
this.metrics.maxSize = newMax;
|
||||
|
||||
// 清理多余对象
|
||||
while (this.pool.length > newMax) {
|
||||
this.pool.pop();
|
||||
}
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 预热池 */
|
||||
preWarm(count: number): void {
|
||||
const targetCount = Math.min(count, this.metrics.maxSize);
|
||||
while (this.pool.length < targetCount) {
|
||||
this.pool.push(this.factory());
|
||||
this.metrics.createCount++;
|
||||
}
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
console.log(`[Pool] ${this.typeName} 预热完成: ${this.pool.length} 个对象`);
|
||||
}
|
||||
|
||||
/** 获取统计信息 */
|
||||
getMetrics(): Readonly<PoolMetrics> {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
|
||||
/** 清空池 */
|
||||
clear(): void {
|
||||
this.pool.length = 0;
|
||||
this.lruTimestamps.clear();
|
||||
this.metrics.currentSize = 0;
|
||||
this.updateTotalMemory();
|
||||
}
|
||||
|
||||
/** 清理长期未使用的对象(用于内存压力下的主动清理) */
|
||||
cleanupStale(maxAge: number = 60000): number {
|
||||
if (this.config.strategy !== 'lru') return 0;
|
||||
|
||||
const now = Date.now();
|
||||
let cleaned = 0;
|
||||
|
||||
// 保留最小容量
|
||||
const targetSize = Math.max(this.metrics.minSize, Math.floor(this.pool.length * 0.5));
|
||||
|
||||
for (let i = this.pool.length - 1; i >= 0 && this.pool.length > targetSize; i--) {
|
||||
const item = this.pool[i];
|
||||
const lastUse = this.lruTimestamps.get(item) || 0;
|
||||
|
||||
if (now - lastUse > maxAge) {
|
||||
this.pool.splice(i, 1);
|
||||
this.lruTimestamps.delete(item);
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleaned > 0) {
|
||||
this.metrics.currentSize = this.pool.length;
|
||||
this.updateTotalMemory();
|
||||
console.log(`[Pool] ${this.typeName} 清理了 ${cleaned} 个过期对象`);
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/** 计算创建频率(个/秒) */
|
||||
getCreateFrequency(): number {
|
||||
if (this.metrics.createTimes.length < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const timeSpan = this.metrics.createTimes[this.metrics.createTimes.length - 1] -
|
||||
this.metrics.createTimes[0];
|
||||
if (timeSpan === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (this.metrics.createTimes.length / timeSpan) * 1000;
|
||||
}
|
||||
|
||||
/** 计算访问频率(个/秒) */
|
||||
getAccessFrequency(): number {
|
||||
if (this.metrics.accessTimes.length < 2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const timeSpan = this.metrics.accessTimes[this.metrics.accessTimes.length - 1] -
|
||||
this.metrics.accessTimes[0];
|
||||
if (timeSpan === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (this.metrics.accessTimes.length / timeSpan) * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 第二阶段:智能特性
|
||||
|
||||
/**
|
||||
* 智能池管理器
|
||||
* 添加学习、预热、类型分析功能
|
||||
*/
|
||||
class SmartPoolManager {
|
||||
private pools: Map<string, DynamicPool<any>> = new Map();
|
||||
private typeProfiles: Map<string, TypeProfile> = new Map();
|
||||
private historyData: Map<string, HistoryData> = new Map();
|
||||
private learningEnabled = true;
|
||||
private readonly HISTORY_KEY = 'ecs_pool_history';
|
||||
|
||||
constructor() {
|
||||
this.loadHistory();
|
||||
}
|
||||
|
||||
/** 获取或创建池 */
|
||||
getPool<T>(typeName: string, factory: () => T, config?: Partial<PoolConfig>): DynamicPool<T> {
|
||||
if (!this.pools.has(typeName)) {
|
||||
// 从历史数据学习
|
||||
const history = this.historyData.get(typeName);
|
||||
const learnedConfig = this.learnFromHistory(typeName, history);
|
||||
|
||||
// 合并配置
|
||||
const finalConfig = { ...learnedConfig, ...config };
|
||||
|
||||
const pool = new DynamicPool(typeName, factory, finalConfig);
|
||||
this.pools.set(typeName, pool);
|
||||
|
||||
console.log(`[SmartPool] 创建池: ${typeName}`, finalConfig);
|
||||
}
|
||||
|
||||
return this.pools.get(typeName)!;
|
||||
}
|
||||
|
||||
/** 从历史数据学习 */
|
||||
private learnFromHistory(typeName: string, history?: HistoryData): Partial<PoolConfig> {
|
||||
if (!history || !this.learningEnabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const config: Partial<PoolConfig> = {};
|
||||
|
||||
// 根据历史峰值设置预热和最大容量
|
||||
if (history.peakCount > 0) {
|
||||
config.preWarmSize = Math.floor(history.avgCount * 0.5);
|
||||
config.maxSize = Math.floor(history.peakCount * 1.2);
|
||||
config.minSize = Math.floor(history.avgCount * 0.2);
|
||||
|
||||
console.log(`[SmartPool] ${typeName} 从历史学习: 峰值=${history.peakCount}, 平均=${history.avgCount}`);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/** 分析类型特征 */
|
||||
analyzeType(typeName: string): TypeProfile {
|
||||
const pool = this.pools.get(typeName);
|
||||
if (!pool) {
|
||||
return this.createDefaultProfile(typeName);
|
||||
}
|
||||
|
||||
const metrics = pool.getMetrics();
|
||||
const frequency = pool.getCreateFrequency();
|
||||
|
||||
// 计算复用率
|
||||
const reuseRate = metrics.recycleCount > 0
|
||||
? metrics.hitCount / metrics.recycleCount
|
||||
: 0;
|
||||
|
||||
// 分类
|
||||
let category: TypeProfile['category'] = 'normal';
|
||||
if (frequency > 50) {
|
||||
category = 'high-frequency';
|
||||
} else if (reuseRate < 0.2) {
|
||||
category = 'rare';
|
||||
} else if (metrics.createCount < 10) {
|
||||
category = 'long-lived';
|
||||
}
|
||||
|
||||
const profile: TypeProfile = {
|
||||
typeName,
|
||||
category,
|
||||
peakRate: frequency,
|
||||
reuseRate,
|
||||
avgMemorySize: metrics.estimatedItemSize,
|
||||
shouldPool: this.shouldPool(category, reuseRate, frequency)
|
||||
};
|
||||
|
||||
this.typeProfiles.set(typeName, profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
/** 判断是否应该池化 */
|
||||
private shouldPool(category: string, reuseRate: number, frequency: number): boolean {
|
||||
// 高频对象必须池化
|
||||
if (category === 'high-frequency') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 复用率太低不池化
|
||||
if (reuseRate < 0.1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 长生命周期且数量少不池化
|
||||
if (category === 'long-lived' && frequency < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 创建默认特征 */
|
||||
private createDefaultProfile(typeName: string): TypeProfile {
|
||||
return {
|
||||
typeName,
|
||||
category: 'normal',
|
||||
peakRate: 0,
|
||||
reuseRate: 0,
|
||||
avgMemorySize: 0,
|
||||
shouldPool: true
|
||||
};
|
||||
}
|
||||
|
||||
/** 场景感知预热 */
|
||||
onSceneChange(sceneType: string, hints: Map<string, number>): void {
|
||||
console.log(`[SmartPool] 场景切换: ${sceneType}`);
|
||||
|
||||
hints.forEach((count, typeName) => {
|
||||
const pool = this.pools.get(typeName);
|
||||
if (pool) {
|
||||
pool.preWarm(count);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 保存历史数据 */
|
||||
saveHistory(): void {
|
||||
const history: Record<string, HistoryData> = {};
|
||||
|
||||
this.pools.forEach((pool, typeName) => {
|
||||
const metrics = pool.getMetrics();
|
||||
history[typeName] = {
|
||||
typeName,
|
||||
peakCount: metrics.maxSize,
|
||||
avgCount: Math.floor((metrics.hitCount + metrics.missCount) / 2),
|
||||
lastPlayTime: Date.now()
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
localStorage.setItem(this.HISTORY_KEY, JSON.stringify(history));
|
||||
console.log('[SmartPool] 历史数据已保存');
|
||||
} catch (e) {
|
||||
console.warn('[SmartPool] 保存历史数据失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 加载历史数据 */
|
||||
private loadHistory(): void {
|
||||
try {
|
||||
const data = localStorage.getItem(this.HISTORY_KEY);
|
||||
if (data) {
|
||||
const history = JSON.parse(data);
|
||||
Object.entries(history).forEach(([typeName, data]) => {
|
||||
this.historyData.set(typeName, data as HistoryData);
|
||||
});
|
||||
console.log(`[SmartPool] 加载历史数据: ${this.historyData.size} 个类型`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[SmartPool] 加载历史数据失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取所有池的统计信息 */
|
||||
getAllMetrics(): Map<string, PoolMetrics> {
|
||||
const result = new Map<string, PoolMetrics>();
|
||||
this.pools.forEach((pool, typeName) => {
|
||||
result.set(typeName, pool.getMetrics());
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 清空所有池 */
|
||||
clearAll(): void {
|
||||
this.pools.forEach(pool => pool.clear());
|
||||
console.log('[SmartPool] 所有池已清空');
|
||||
}
|
||||
|
||||
/** 清理所有池中的过期对象 */
|
||||
cleanupAllStale(maxAge: number = 60000): number {
|
||||
let totalCleaned = 0;
|
||||
this.pools.forEach(pool => {
|
||||
totalCleaned += pool.cleanupStale(maxAge);
|
||||
});
|
||||
if (totalCleaned > 0) {
|
||||
console.log(`[SmartPool] 总共清理了 ${totalCleaned} 个过期对象`);
|
||||
}
|
||||
return totalCleaned;
|
||||
}
|
||||
|
||||
/** 获取总内存占用 */
|
||||
getTotalMemory(): number {
|
||||
let total = 0;
|
||||
this.pools.forEach(pool => {
|
||||
total += pool.getMetrics().totalMemory;
|
||||
});
|
||||
return total;
|
||||
}
|
||||
|
||||
/** 获取类型分析报告 */
|
||||
getTypeProfiles(): Map<string, TypeProfile> {
|
||||
// 更新所有类型的分析
|
||||
this.pools.forEach((pool, typeName) => {
|
||||
this.analyzeType(typeName);
|
||||
});
|
||||
return new Map(this.typeProfiles);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 第三阶段:可选配置
|
||||
|
||||
/** 池配置装饰器 */
|
||||
export function poolConfig(config: PoolConfig) {
|
||||
return function <T extends { new(...args: any[]): {} }>(constructor: T) {
|
||||
// 将配置存储到构造函数上
|
||||
(constructor as any).__poolConfig = config;
|
||||
return constructor;
|
||||
};
|
||||
}
|
||||
|
||||
/** 池提示装饰器 */
|
||||
export function poolHint(hint: PoolHint) {
|
||||
return function <T extends { new(...args: any[]): {} }>(constructor: T) {
|
||||
// 将提示存储到构造函数上
|
||||
(constructor as any).__poolHint = hint;
|
||||
return constructor;
|
||||
};
|
||||
}
|
||||
|
||||
/** 获取类的池配置 */
|
||||
export function getPoolConfig(constructor: any): PoolConfig | undefined {
|
||||
return constructor.__poolConfig;
|
||||
}
|
||||
|
||||
/** 获取类的池提示 */
|
||||
export function getPoolHint(constructor: any): PoolHint | undefined {
|
||||
return constructor.__poolHint;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 全局协调器
|
||||
|
||||
/**
|
||||
* 全局池协调器
|
||||
* 管理总内存预算,协调各池之间的资源分配
|
||||
*/
|
||||
class GlobalPoolCoordinator {
|
||||
private smartManager: SmartPoolManager;
|
||||
private totalBudget = 100 * 1024 * 1024; // 100MB默认预算
|
||||
private cleanupTimer: any = null;
|
||||
private autoCleanupEnabled = false;
|
||||
|
||||
constructor() {
|
||||
this.smartManager = new SmartPoolManager();
|
||||
}
|
||||
|
||||
/** 获取或创建池 */
|
||||
getPool<T>(typeName: string, factory: () => T, ctor?: any): DynamicPool<T> {
|
||||
let config: Partial<PoolConfig> = {};
|
||||
let hint: PoolHint | undefined;
|
||||
|
||||
// 读取装饰器配置
|
||||
if (ctor) {
|
||||
const decoratorConfig = getPoolConfig(ctor);
|
||||
if (decoratorConfig) {
|
||||
config = decoratorConfig;
|
||||
}
|
||||
|
||||
hint = getPoolHint(ctor);
|
||||
if (hint) {
|
||||
// 应用提示
|
||||
if (hint.expectedPeak) {
|
||||
config.maxSize = hint.expectedPeak;
|
||||
config.preWarmSize = Math.floor(hint.expectedPeak * 0.3);
|
||||
}
|
||||
if (hint.highFrequency) {
|
||||
config.minSize = 50;
|
||||
config.maxSize = config.maxSize || 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.smartManager.getPool(typeName, factory, config);
|
||||
}
|
||||
|
||||
/** 场景切换 */
|
||||
onSceneChange(sceneType: string, hints: Map<string, number>): void {
|
||||
this.smartManager.onSceneChange(sceneType, hints);
|
||||
}
|
||||
|
||||
/** 获取智能管理器 */
|
||||
getSmartManager(): SmartPoolManager {
|
||||
return this.smartManager;
|
||||
}
|
||||
|
||||
/** 设置总内存预算 */
|
||||
setTotalBudget(bytes: number): void {
|
||||
this.totalBudget = bytes;
|
||||
console.log(`[GlobalCoordinator] 设置内存预算: ${(bytes / 1024 / 1024).toFixed(2)}MB`);
|
||||
|
||||
// 检查是否超出预算
|
||||
this.checkMemoryBudget();
|
||||
}
|
||||
|
||||
/** 检查内存预算 */
|
||||
private checkMemoryBudget(): void {
|
||||
const usedMemory = this.smartManager.getTotalMemory();
|
||||
const usagePercent = (usedMemory / this.totalBudget) * 100;
|
||||
|
||||
if (usedMemory > this.totalBudget) {
|
||||
console.warn(`[GlobalCoordinator] 内存超出预算! 使用: ${(usedMemory / 1024 / 1024).toFixed(2)}MB / ${(this.totalBudget / 1024 / 1024).toFixed(2)}MB (${usagePercent.toFixed(1)}%)`);
|
||||
|
||||
// 触发清理
|
||||
this.smartManager.cleanupAllStale(30000); // 清理30秒未使用的对象
|
||||
|
||||
// 再次检查
|
||||
const newUsed = this.smartManager.getTotalMemory();
|
||||
if (newUsed > this.totalBudget) {
|
||||
console.warn(`[GlobalCoordinator] 清理后仍超出预算,考虑增加预算或优化对象使用`);
|
||||
}
|
||||
} else if (usagePercent > 80) {
|
||||
console.warn(`[GlobalCoordinator] 内存使用率较高: ${usagePercent.toFixed(1)}%`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取当前内存使用情况 */
|
||||
getMemoryUsage(): { used: number; budget: number; percent: number } {
|
||||
const used = this.smartManager.getTotalMemory();
|
||||
return {
|
||||
used,
|
||||
budget: this.totalBudget,
|
||||
percent: (used / this.totalBudget) * 100
|
||||
};
|
||||
}
|
||||
|
||||
/** 启用自动清理(定期清理过期对象) */
|
||||
enableAutoCleanup(interval: number = 60000): void {
|
||||
if (this.autoCleanupEnabled) {
|
||||
console.warn('[GlobalCoordinator] 自动清理已启用');
|
||||
return;
|
||||
}
|
||||
|
||||
this.autoCleanupEnabled = true;
|
||||
this.cleanupTimer = setInterval(() => {
|
||||
this.smartManager.cleanupAllStale();
|
||||
this.checkMemoryBudget();
|
||||
}, interval);
|
||||
|
||||
console.log(`[GlobalCoordinator] 启用自动清理,间隔: ${interval}ms`);
|
||||
}
|
||||
|
||||
/** 禁用自动清理 */
|
||||
disableAutoCleanup(): void {
|
||||
if (this.cleanupTimer) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
this.cleanupTimer = null;
|
||||
}
|
||||
this.autoCleanupEnabled = false;
|
||||
console.log('[GlobalCoordinator] 禁用自动清理');
|
||||
}
|
||||
|
||||
/** 保存历史数据 */
|
||||
saveHistory(): void {
|
||||
this.smartManager.saveHistory();
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 导出单例
|
||||
|
||||
/** 全局池协调器实例 */
|
||||
export const globalPoolCoordinator = new GlobalPoolCoordinator();
|
||||
|
||||
//#endregion
|
||||
|
||||
11
assets/libs/ecs/ECSPoolManager.ts.meta
Normal file
11
assets/libs/ecs/ECSPoolManager.ts.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "88db857b-ee25-442e-aef3-64a3b1e474de",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {
|
||||
"simulateGlobals": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user