From 2350b06c84397188df4536d7be94063ce73aa4c0 Mon Sep 17 00:00:00 2001 From: dgflash Date: Sun, 8 Mar 2026 11:49:53 +0800 Subject: [PATCH] =?UTF-8?q?ECS=20=E7=BC=93=E5=AD=98=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/libs/ecs/ECS.ts | 34 +- assets/libs/ecs/ECSEntity.ts | 88 ++- assets/libs/ecs/ECSGroup.ts | 14 +- assets/libs/ecs/ECSMemoryMonitor.ts | 464 +++++++++++++ assets/libs/ecs/ECSMemoryMonitor.ts.meta | 11 + assets/libs/ecs/ECSPoolManager.ts | 808 +++++++++++++++++++++++ assets/libs/ecs/ECSPoolManager.ts.meta | 11 + 7 files changed, 1384 insertions(+), 46 deletions(-) create mode 100644 assets/libs/ecs/ECSMemoryMonitor.ts create mode 100644 assets/libs/ecs/ECSMemoryMonitor.ts.meta create mode 100644 assets/libs/ecs/ECSPoolManager.ts create mode 100644 assets/libs/ecs/ECSPoolManager.ts.meta diff --git a/assets/libs/ecs/ECS.ts b/assets/libs/ecs/ECS.ts index e528ef2..6a63b49 100644 --- a/assets/libs/ecs/ECS.ts +++ b/assets/libs/ecs/ECS.ts @@ -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(ctor: EntityCtor): 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(); } /** diff --git a/assets/libs/ecs/ECSEntity.ts b/assets/libs/ecs/ECSEntity.ts index f96c06e..d22267e 100644 --- a/assets/libs/ecs/ECSEntity.ts +++ b/assets/libs/ecs/ECSEntity.ts @@ -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(ctor: CompCtor): T { @@ -53,37 +54,36 @@ function createComp(ctor: CompCtor): 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)(); + + // 使用动态池管理器 + const pool = globalPoolCoordinator.getPool( + ctor.compName, + () => new (cct as CompCtor)(), + 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); } } } diff --git a/assets/libs/ecs/ECSGroup.ts b/assets/libs/ecs/ECSGroup.ts index cef3f43..fd68d1a 100644 --- a/assets/libs/ecs/ECSGroup.ts +++ b/assets/libs/ecs/ECSGroup.ts @@ -17,19 +17,25 @@ export class ECSGroup { 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(); } diff --git a/assets/libs/ecs/ECSMemoryMonitor.ts b/assets/libs/ecs/ECSMemoryMonitor.ts new file mode 100644 index 0000000..a9f34cf --- /dev/null +++ b/assets/libs/ecs/ECSMemoryMonitor.ts @@ -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 = 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): 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(); + diff --git a/assets/libs/ecs/ECSMemoryMonitor.ts.meta b/assets/libs/ecs/ECSMemoryMonitor.ts.meta new file mode 100644 index 0000000..c67731a --- /dev/null +++ b/assets/libs/ecs/ECSMemoryMonitor.ts.meta @@ -0,0 +1,11 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "c1cce79d-eaea-4205-b53c-982ee27bda62", + "files": [], + "subMetas": {}, + "userData": { + "simulateGlobals": [] + } +} diff --git a/assets/libs/ecs/ECSPoolManager.ts b/assets/libs/ecs/ECSPoolManager.ts new file mode 100644 index 0000000..452f13f --- /dev/null +++ b/assets/libs/ecs/ECSPoolManager.ts @@ -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 { + private pool: T[] = []; + private metrics: PoolMetrics; + private config: PoolConfig; + private lruTimestamps: Map = new Map(); // LRU时间戳 + + constructor( + private typeName: string, + private factory: () => T, + config: Partial = {} + ) { + 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 { + 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> = new Map(); + private typeProfiles: Map = new Map(); + private historyData: Map = new Map(); + private learningEnabled = true; + private readonly HISTORY_KEY = 'ecs_pool_history'; + + constructor() { + this.loadHistory(); + } + + /** 获取或创建池 */ + getPool(typeName: string, factory: () => T, config?: Partial): DynamicPool { + 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 { + if (!history || !this.learningEnabled) { + return {}; + } + + const config: Partial = {}; + + // 根据历史峰值设置预热和最大容量 + 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): 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 = {}; + + 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 { + const result = new Map(); + 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 { + // 更新所有类型的分析 + this.pools.forEach((pool, typeName) => { + this.analyzeType(typeName); + }); + return new Map(this.typeProfiles); + } +} + +//#endregion + +//#region 第三阶段:可选配置 + +/** 池配置装饰器 */ +export function poolConfig(config: PoolConfig) { + return function (constructor: T) { + // 将配置存储到构造函数上 + (constructor as any).__poolConfig = config; + return constructor; + }; +} + +/** 池提示装饰器 */ +export function poolHint(hint: PoolHint) { + return function (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(typeName: string, factory: () => T, ctor?: any): DynamicPool { + let config: Partial = {}; + 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): 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 + diff --git a/assets/libs/ecs/ECSPoolManager.ts.meta b/assets/libs/ecs/ECSPoolManager.ts.meta new file mode 100644 index 0000000..41490ae --- /dev/null +++ b/assets/libs/ecs/ECSPoolManager.ts.meta @@ -0,0 +1,11 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "88db857b-ee25-442e-aef3-64a3b1e474de", + "files": [], + "subMetas": {}, + "userData": { + "simulateGlobals": [] + } +}