perf: 优化战斗系统内存与性能,增加对象池限制与内存监控面板
- 为Skill和Monster对象池添加最大容量限制(64/24),防止内存泄漏 - 实现DamageQueueComp的环形队列优化,减少数组操作开销 - 在MissionComp中添加内存监控面板,实时显示堆内存、实体数量、对象池状态 - 优化MoveSystem的渲染排序性能,缓存查询结果减少GC压力 - 调整角色控制器UI位置与样式,关闭调试日志减少性能开销 - 战斗结束时自动清理对象池,确保内存可回收
This commit is contained in:
@@ -11,6 +11,8 @@ import { UIID } from "../common/config/GameUIConfig";
|
||||
import { SkillView } from "../skill/SkillView";
|
||||
import { FightSet } from "../common/config/GameSet";
|
||||
import { mLogger } from "../common/Logger";
|
||||
import { Monster } from "../hero/Mon";
|
||||
import { Skill } from "../skill/Skill";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
|
||||
@@ -22,6 +24,8 @@ const { ccclass, property } = _decorator;
|
||||
export class MissionComp extends CCComp {
|
||||
@property({ tooltip: "是否启用调试日志" })
|
||||
private debugMode: boolean = false;
|
||||
@property({ tooltip: "是否显示战斗内存观测面板" })
|
||||
private showMemoryPanel: boolean = true;
|
||||
|
||||
// VictoryComp:any = null;
|
||||
// reward:number = 0;
|
||||
@@ -50,6 +54,19 @@ export class MissionComp extends CCComp {
|
||||
}
|
||||
|
||||
private lastTimeStr: string = "";
|
||||
private lastTimeSecond: number = -1;
|
||||
private memoryLabel: Label | null = null;
|
||||
private memoryRefreshTimer: number = 0;
|
||||
private lastMemoryText: string = "";
|
||||
private perfDtAcc: number = 0;
|
||||
private perfFrameCount: number = 0;
|
||||
private heapBaseMB: number = -1;
|
||||
private heapPeakMB: number = 0;
|
||||
private heapTrendPerMinMB: number = 0;
|
||||
private heapTrendTimer: number = 0;
|
||||
private heapTrendBaseMB: number = -1;
|
||||
private readonly heroViewMatcher = ecs.allOf(HeroViewComp);
|
||||
private readonly skillViewMatcher = ecs.allOf(SkillView);
|
||||
|
||||
// 记录已触发的特殊刷怪索引
|
||||
private spawnedSpecialIndices: Set<number> = new Set();
|
||||
@@ -60,7 +77,7 @@ export class MissionComp extends CCComp {
|
||||
// this.on(GameEvent.FightEnd,this.fight_end,this)
|
||||
this.on(GameEvent.MissionEnd,this.mission_end,this)
|
||||
this.on(GameEvent.DO_AD_BACK,this.do_ad,this)
|
||||
|
||||
this.initMemoryPanel()
|
||||
}
|
||||
protected update(dt: number): void {
|
||||
if(!smc.mission.play) return
|
||||
@@ -73,12 +90,16 @@ export class MissionComp extends CCComp {
|
||||
// 检查特殊刷怪时间
|
||||
this.checkSpecialSpawns(smc.vmdata.mission_data.fight_time);
|
||||
this.update_time();
|
||||
this.updateMemoryPanel(dt);
|
||||
}
|
||||
}
|
||||
update_time(){
|
||||
let time = Math.max(0, this.FightTime);
|
||||
let m = Math.floor(time / 60);
|
||||
let s = Math.floor(time % 60);
|
||||
const time = Math.max(0, this.FightTime);
|
||||
const remainSecond = Math.floor(time);
|
||||
if (remainSecond === this.lastTimeSecond) return;
|
||||
this.lastTimeSecond = remainSecond;
|
||||
let m = Math.floor(remainSecond / 60);
|
||||
let s = remainSecond % 60;
|
||||
let str = `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
|
||||
if(str != this.lastTimeStr){
|
||||
this.time_node.getChildByName("time").getComponent(Label).string = str;
|
||||
@@ -99,6 +120,77 @@ export class MissionComp extends CCComp {
|
||||
});
|
||||
}
|
||||
|
||||
private initMemoryPanel() {
|
||||
if (!this.showMemoryPanel || !this.time_node) return;
|
||||
let panel = this.time_node.getChildByName("mem_panel");
|
||||
if (!panel) {
|
||||
panel = new Node("mem_panel");
|
||||
panel.parent = this.time_node;
|
||||
panel.setPosition(0, -32, 0);
|
||||
}
|
||||
let label = panel.getComponent(Label);
|
||||
if (!label) {
|
||||
label = panel.addComponent(Label);
|
||||
}
|
||||
label.fontSize = 16;
|
||||
label.lineHeight = 20;
|
||||
this.memoryLabel = label;
|
||||
}
|
||||
|
||||
private updateMemoryPanel(dt: number) {
|
||||
if (!this.showMemoryPanel || !this.memoryLabel) return;
|
||||
this.perfDtAcc += dt;
|
||||
this.perfFrameCount += 1;
|
||||
this.memoryRefreshTimer += dt;
|
||||
if (this.memoryRefreshTimer < 0.5) return;
|
||||
this.memoryRefreshTimer = 0;
|
||||
let heroCount = 0;
|
||||
ecs.query(this.heroViewMatcher).forEach(() => {
|
||||
heroCount++;
|
||||
});
|
||||
let skillCount = 0;
|
||||
ecs.query(this.skillViewMatcher).forEach(() => {
|
||||
skillCount++;
|
||||
});
|
||||
const monPool = Monster.getPoolStats();
|
||||
const skillPool = Skill.getPoolStats();
|
||||
const perf = (globalThis as any).performance;
|
||||
const heapBytes = perf && perf.memory ? perf.memory.usedJSHeapSize : 0;
|
||||
let heapMB = heapBytes > 0 ? heapBytes / 1024 / 1024 : -1;
|
||||
if (heapMB > 0 && this.heapBaseMB < 0) {
|
||||
this.heapBaseMB = heapMB;
|
||||
this.heapPeakMB = heapMB;
|
||||
this.heapTrendBaseMB = heapMB;
|
||||
this.heapTrendTimer = 0;
|
||||
}
|
||||
if (heapMB > this.heapPeakMB) {
|
||||
this.heapPeakMB = heapMB;
|
||||
}
|
||||
this.heapTrendTimer += 0.5;
|
||||
if (heapMB > 0 && this.heapTrendBaseMB > 0 && this.heapTrendTimer >= 10) {
|
||||
const deltaMB = heapMB - this.heapTrendBaseMB;
|
||||
this.heapTrendPerMinMB = (deltaMB / this.heapTrendTimer) * 60;
|
||||
this.heapTrendBaseMB = heapMB;
|
||||
this.heapTrendTimer = 0;
|
||||
}
|
||||
const heapText = heapMB > 0 ? heapMB.toFixed(1) : "N/A";
|
||||
const heapDeltaText = this.heapBaseMB > 0 && heapMB > 0 ? (heapMB - this.heapBaseMB).toFixed(1) : "N/A";
|
||||
const heapPeakText = this.heapPeakMB > 0 ? this.heapPeakMB.toFixed(1) : "N/A";
|
||||
const avgDt = this.perfFrameCount > 0 ? this.perfDtAcc / this.perfFrameCount : 0;
|
||||
const fps = avgDt > 0 ? 1 / avgDt : 0;
|
||||
this.perfDtAcc = 0;
|
||||
this.perfFrameCount = 0;
|
||||
const text =
|
||||
`Heap:${heapText}MB Δ:${heapDeltaText} Peak:${heapPeakText}\n` +
|
||||
`Trend:${this.heapTrendPerMinMB.toFixed(2)}MB/min\n` +
|
||||
`Perf dt:${(avgDt * 1000).toFixed(1)}ms fps:${fps.toFixed(1)}\n` +
|
||||
`Ent H:${heroCount} S:${skillCount}\n` +
|
||||
`Pool M:${monPool.total}(${monPool.paths}) K:${skillPool.total}(${skillPool.paths})`;
|
||||
if (text === this.lastMemoryText) return;
|
||||
this.lastMemoryText = text;
|
||||
this.memoryLabel.string = text;
|
||||
}
|
||||
|
||||
|
||||
//奖励发放
|
||||
do_reward(){
|
||||
@@ -180,6 +272,7 @@ do_ad(){
|
||||
this.scheduleOnce(() => {
|
||||
smc.mission.play=false
|
||||
this.cleanComponents()
|
||||
this.clearBattlePools()
|
||||
}, 0.5)
|
||||
}
|
||||
|
||||
@@ -188,6 +281,7 @@ do_ad(){
|
||||
// 合并 FightEnd 逻辑:清理组件、停止游戏循环
|
||||
smc.mission.play=false
|
||||
this.cleanComponents()
|
||||
this.clearBattlePools()
|
||||
this.node.active=false
|
||||
}
|
||||
|
||||
@@ -205,6 +299,17 @@ do_ad(){
|
||||
this.rewards=[] // 改为数组,用于存储掉落物品列表
|
||||
this.revive_times = 1; // 每次任务开始重置复活次数
|
||||
this.spawnedSpecialIndices.clear(); // 重置特殊刷怪记录
|
||||
this.lastTimeStr = "";
|
||||
this.lastTimeSecond = -1;
|
||||
this.memoryRefreshTimer = 0;
|
||||
this.lastMemoryText = "";
|
||||
this.perfDtAcc = 0;
|
||||
this.perfFrameCount = 0;
|
||||
this.heapBaseMB = -1;
|
||||
this.heapPeakMB = 0;
|
||||
this.heapTrendPerMinMB = 0;
|
||||
this.heapTrendTimer = 0;
|
||||
this.heapTrendBaseMB = -1;
|
||||
|
||||
// 重置全局属性加成和主角引用 (确保新一局数据干净)
|
||||
// smc.role = null;
|
||||
@@ -217,14 +322,19 @@ do_ad(){
|
||||
private cleanComponents() {
|
||||
// 优化销毁顺序:直接销毁实体,让ECS系统自动处理组件清理
|
||||
// 这样可以避免在组件reset方法中访问已经被销毁的实体引用
|
||||
ecs.query(ecs.allOf(HeroViewComp)).forEach(entity => {
|
||||
ecs.query(this.heroViewMatcher).forEach(entity => {
|
||||
entity.destroy();
|
||||
});
|
||||
|
||||
ecs.query(ecs.allOf(SkillView)).forEach(entity => {
|
||||
ecs.query(this.skillViewMatcher).forEach(entity => {
|
||||
entity.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
private clearBattlePools() {
|
||||
Monster.clearPools();
|
||||
Skill.clearPools();
|
||||
}
|
||||
|
||||
|
||||
/** 视图层逻辑代码分离演示 */
|
||||
@@ -233,4 +343,4 @@ do_ad(){
|
||||
reset() {
|
||||
this.node.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user