perf: 优化战斗系统内存与性能,增加对象池限制与内存监控面板
- 为Skill和Monster对象池添加最大容量限制(64/24),防止内存泄漏 - 实现DamageQueueComp的环形队列优化,减少数组操作开销 - 在MissionComp中添加内存监控面板,实时显示堆内存、实体数量、对象池状态 - 优化MoveSystem的渲染排序性能,缓存查询结果减少GC压力 - 调整角色控制器UI位置与样式,关闭调试日志减少性能开销 - 战斗结束时自动清理对象池,确保内存可回收
This commit is contained in:
@@ -38,6 +38,7 @@ export interface DamageEvent {
|
||||
export class DamageQueueComp extends ecs.Comp {
|
||||
/** 伤害事件队列 */
|
||||
damageEvents: DamageEvent[] = [];
|
||||
private readIndex: number = 0;
|
||||
|
||||
/** 队列创建时间 */
|
||||
createTime: number = 0;
|
||||
@@ -49,7 +50,8 @@ export class DamageQueueComp extends ecs.Comp {
|
||||
processedCount: number = 0;
|
||||
|
||||
reset() {
|
||||
this.damageEvents = [];
|
||||
this.damageEvents.length = 0;
|
||||
this.readIndex = 0;
|
||||
this.createTime = 0;
|
||||
this.isProcessing = false;
|
||||
this.processedCount = 0;
|
||||
@@ -60,14 +62,13 @@ export class DamageQueueComp extends ecs.Comp {
|
||||
*/
|
||||
addDamageEvent(attrs: any, casterEid: number, s_uuid: number,ext_dmg:number=0,dmg_ratio:number=1): void {
|
||||
const timestamp = Date.now();
|
||||
const eventId = `${casterEid}_${s_uuid}_${timestamp}_${Math.random()}`;
|
||||
|
||||
const damageEvent: DamageEvent = {
|
||||
Attrs: attrs ? { ...attrs } : null, // 深拷贝属性数据
|
||||
Attrs: attrs || null,
|
||||
casterEid: casterEid,
|
||||
s_uuid: s_uuid,
|
||||
timestamp: timestamp,
|
||||
eventId: eventId,
|
||||
eventId: "",
|
||||
ext_dmg:ext_dmg,
|
||||
dmg_ratio:dmg_ratio,
|
||||
};
|
||||
@@ -75,7 +76,7 @@ export class DamageQueueComp extends ecs.Comp {
|
||||
this.damageEvents.push(damageEvent);
|
||||
|
||||
// 如果是第一个伤害事件,记录创建时间
|
||||
if (this.damageEvents.length === 1) {
|
||||
if (this.getRemainingCount() === 1) {
|
||||
this.createTime = timestamp;
|
||||
}
|
||||
}
|
||||
@@ -84,29 +85,36 @@ export class DamageQueueComp extends ecs.Comp {
|
||||
* 获取下一个待处理的伤害事件
|
||||
*/
|
||||
getNextDamageEvent(): DamageEvent | null {
|
||||
if (this.damageEvents.length === 0) return null;
|
||||
return this.damageEvents.shift() || null;
|
||||
if (this.readIndex >= this.damageEvents.length) return null;
|
||||
const event = this.damageEvents[this.readIndex];
|
||||
this.readIndex += 1;
|
||||
if (this.readIndex >= 32 && this.readIndex * 2 >= this.damageEvents.length) {
|
||||
this.damageEvents = this.damageEvents.slice(this.readIndex);
|
||||
this.readIndex = 0;
|
||||
}
|
||||
return event || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列中剩余的伤害事件数量
|
||||
*/
|
||||
getRemainingCount(): number {
|
||||
return this.damageEvents.length;
|
||||
return this.damageEvents.length - this.readIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查队列是否为空
|
||||
*/
|
||||
isEmpty(): boolean {
|
||||
return this.damageEvents.length === 0;
|
||||
return this.readIndex >= this.damageEvents.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空队列
|
||||
*/
|
||||
clear(): void {
|
||||
this.damageEvents = [];
|
||||
this.damageEvents.length = 0;
|
||||
this.readIndex = 0;
|
||||
this.processedCount = 0;
|
||||
this.isProcessing = false;
|
||||
}
|
||||
@@ -115,6 +123,10 @@ export class DamageQueueComp extends ecs.Comp {
|
||||
* 按时间戳排序伤害事件(可选功能)
|
||||
*/
|
||||
sortByTimestamp(): void {
|
||||
if (this.readIndex > 0) {
|
||||
this.damageEvents = this.damageEvents.slice(this.readIndex);
|
||||
this.readIndex = 0;
|
||||
}
|
||||
this.damageEvents.sort((a, b) => a.timestamp - b.timestamp);
|
||||
}
|
||||
|
||||
@@ -123,6 +135,10 @@ export class DamageQueueComp extends ecs.Comp {
|
||||
*/
|
||||
removeDuplicates(): void {
|
||||
const seen = new Set<string>();
|
||||
if (this.readIndex > 0) {
|
||||
this.damageEvents = this.damageEvents.slice(this.readIndex);
|
||||
this.readIndex = 0;
|
||||
}
|
||||
this.damageEvents = this.damageEvents.filter(event => {
|
||||
if (seen.has(event.eventId)) {
|
||||
return false;
|
||||
@@ -145,7 +161,7 @@ export class DamageQueueComp extends ecs.Comp {
|
||||
return {
|
||||
totalEvents: this.processedCount + this.damageEvents.length,
|
||||
processedEvents: this.processedCount,
|
||||
remainingEvents: this.damageEvents.length,
|
||||
remainingEvents: this.getRemainingCount(),
|
||||
isProcessing: this.isProcessing,
|
||||
createTime: this.createTime
|
||||
};
|
||||
@@ -187,4 +203,4 @@ export class DamageQueueHelper {
|
||||
const damageQueue = entity.get(DamageQueueComp);
|
||||
return damageQueue ? damageQueue.getQueueStats() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ interface IntervalBuffState {
|
||||
}
|
||||
@ecs.register('HeroAttrs')
|
||||
export class HeroAttrsComp extends ecs.Comp {
|
||||
public debugMode: boolean = true;
|
||||
public debugMode: boolean = false;
|
||||
|
||||
Ebus:any=null!
|
||||
// ==================== 角色基础信息 ====================
|
||||
@@ -159,7 +159,9 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
this.hp += addValue;
|
||||
this.hp = Math.max(0, Math.min(this.hp, this.hp_max));
|
||||
this.dirty_hp = true; // ✅ 仅标记需要更新
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` HP变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldHp.toFixed(1)} -> ${this.hp.toFixed(1)}`);
|
||||
if (this.debugMode) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` HP变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldHp.toFixed(1)} -> ${this.hp.toFixed(1)}`);
|
||||
}
|
||||
}
|
||||
|
||||
add_shield(value:number,isValue:boolean){
|
||||
@@ -173,7 +175,9 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
if (this.shield < 0) this.shield = 0;
|
||||
if (this.shield_max < 0) this.shield_max = 0;
|
||||
this.dirty_shield = true; // 标记护盾需要更新
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldShield.toFixed(1)} -> ${this.shield.toFixed(1)}`);
|
||||
if (this.debugMode) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', ` 护盾变更: ${this.hero_name}, 变化=${addValue.toFixed(1)}, ${oldShield.toFixed(1)} -> ${this.shield.toFixed(1)}`);
|
||||
}
|
||||
}
|
||||
// ==================== BUFF 管理 ====================
|
||||
/**
|
||||
@@ -225,7 +229,9 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
|
||||
this.applyAttrChange(buffConf.buff, normalized.value, normalized.BType);
|
||||
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', `添加Buff: ${buffConf.name}, 属性:${buffConf.buff}, 值:${normalized.value}, 时间:${duration}`);
|
||||
if (this.debugMode) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', `添加Buff: ${buffConf.name}, 属性:${buffConf.buff}, 值:${normalized.value}, 时间:${duration}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,7 +416,9 @@ export class HeroAttrsComp extends ecs.Comp {
|
||||
this.applyAttrChange(buff.attr, buff.value, buff.BType, true);
|
||||
|
||||
buffs.splice(i, 1);
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', `Buff过期: 属性:${buff.attr}, 恢复值:${buff.value}`);
|
||||
if (this.debugMode) {
|
||||
mLogger.log(this.debugMode, 'HeroAttrs', `Buff过期: 属性:${buff.attr}, 恢复值:${buff.value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,22 +20,51 @@ export class Monster extends ecs.Entity {
|
||||
|
||||
// 多键对象池:Map<prefabPath, NodePool>
|
||||
static pools: Map<string, NodePool> = new Map();
|
||||
static readonly MAX_POOL_SIZE: number = 24;
|
||||
|
||||
static getFromPool(path: string): Node | null {
|
||||
if (this.pools.has(path)) {
|
||||
const pool = this.pools.get(path)!;
|
||||
if (pool.size() > 0) {
|
||||
return pool.get();
|
||||
while (pool.size() > 0) {
|
||||
const node = pool.get();
|
||||
if (node && node.isValid) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static putToPool(path: string, node: Node) {
|
||||
if (!node || !node.isValid) return;
|
||||
if (!this.pools.has(path)) {
|
||||
this.pools.set(path, new NodePool());
|
||||
}
|
||||
this.pools.get(path)!.put(node);
|
||||
const pool = this.pools.get(path)!;
|
||||
if (pool.size() >= this.MAX_POOL_SIZE) {
|
||||
node.destroy();
|
||||
return;
|
||||
}
|
||||
pool.put(node);
|
||||
}
|
||||
|
||||
static clearPools() {
|
||||
this.pools.forEach((pool) => {
|
||||
pool.clear();
|
||||
});
|
||||
this.pools.clear();
|
||||
}
|
||||
|
||||
static getPoolStats() {
|
||||
let total = 0;
|
||||
this.pools.forEach((pool) => {
|
||||
total += pool.size();
|
||||
});
|
||||
return {
|
||||
paths: this.pools.size,
|
||||
total,
|
||||
maxPerPath: this.MAX_POOL_SIZE
|
||||
};
|
||||
}
|
||||
|
||||
protected init() {
|
||||
|
||||
@@ -41,6 +41,10 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
private readonly minSpacingY = 30;
|
||||
private readonly renderSortInterval = 0.05;
|
||||
private renderSortElapsed = 0;
|
||||
private heroMoveMatcher: ecs.IMatcher | null = null;
|
||||
private heroViewMatcher: ecs.IMatcher | null = null;
|
||||
private readonly renderEntries: { node: Node; frontScore: number; spawnOrder: number; eid: number }[] = [];
|
||||
private renderEntryCount = 0;
|
||||
|
||||
private readonly facConfigs: Record<number, MoveFacConfig> = {
|
||||
[FacSet.HERO]: {
|
||||
@@ -57,6 +61,20 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
}
|
||||
};
|
||||
|
||||
private getHeroMoveMatcher(): ecs.IMatcher {
|
||||
if (!this.heroMoveMatcher) {
|
||||
this.heroMoveMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp, MoveComp);
|
||||
}
|
||||
return this.heroMoveMatcher;
|
||||
}
|
||||
|
||||
private getHeroViewMatcher(): ecs.IMatcher {
|
||||
if (!this.heroViewMatcher) {
|
||||
this.heroViewMatcher = ecs.allOf(HeroAttrsComp, HeroViewComp);
|
||||
}
|
||||
return this.heroViewMatcher;
|
||||
}
|
||||
|
||||
filter(): ecs.IMatcher {
|
||||
return ecs.allOf(MoveComp, HeroViewComp, HeroAttrsComp);
|
||||
}
|
||||
@@ -239,7 +257,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
const selfPriority = selfAttrs ? this.getCombatPriority(selfAttrs) : 0;
|
||||
let nearestAheadX = Infinity;
|
||||
let nearestBehindX = -Infinity;
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp, MoveComp)).forEach(e => {
|
||||
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
|
||||
if (e === self) return;
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
const view = e.get(HeroViewComp);
|
||||
@@ -272,7 +290,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
private hasAnyActorTooClose(self: ecs.Entity, x: number, y: number): boolean {
|
||||
const myAttrs = self.get(HeroAttrsComp);
|
||||
if (!myAttrs) return false;
|
||||
return ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).some(e => {
|
||||
return ecs.query(this.getHeroViewMatcher()).some(e => {
|
||||
if (e === self) return false;
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
if (!attrs || attrs.is_dead) return false;
|
||||
@@ -303,7 +321,7 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
let minDis = Infinity;
|
||||
|
||||
// 优化查询:一次遍历
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp)).forEach(e => {
|
||||
ecs.query(this.getHeroViewMatcher()).forEach(e => {
|
||||
const m = e.get(HeroAttrsComp);
|
||||
if (m.fac !== myFac && !m.is_dead) {
|
||||
const v = e.get(HeroViewComp);
|
||||
@@ -332,8 +350,8 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
if (this.renderSortElapsed < this.renderSortInterval) return;
|
||||
this.renderSortElapsed = 0;
|
||||
|
||||
const renderList: { node: Node; frontScore: number; spawnOrder: number; eid: number }[] = [];
|
||||
ecs.query(ecs.allOf(HeroAttrsComp, HeroViewComp, MoveComp)).forEach(e => {
|
||||
this.renderEntryCount = 0;
|
||||
ecs.query(this.getHeroMoveMatcher()).forEach(e => {
|
||||
const attrs = e.get(HeroAttrsComp);
|
||||
const actorView = e.get(HeroViewComp);
|
||||
const actorMove = e.get(MoveComp);
|
||||
@@ -343,21 +361,27 @@ export class MoveSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate
|
||||
actorView.node.parent = actorRoot;
|
||||
}
|
||||
const frontScore = attrs.fac === FacSet.HERO ? actorView.node.position.x : -actorView.node.position.x;
|
||||
renderList.push({
|
||||
node: actorView.node,
|
||||
frontScore,
|
||||
spawnOrder: actorMove.spawnOrder,
|
||||
eid: e.eid
|
||||
});
|
||||
const entryIndex = this.renderEntryCount;
|
||||
let entry = this.renderEntries[entryIndex];
|
||||
if (!entry) {
|
||||
entry = { node: actorView.node, frontScore: 0, spawnOrder: 0, eid: 0 };
|
||||
this.renderEntries.push(entry);
|
||||
}
|
||||
entry.node = actorView.node;
|
||||
entry.frontScore = frontScore;
|
||||
entry.spawnOrder = actorMove.spawnOrder;
|
||||
entry.eid = e.eid;
|
||||
this.renderEntryCount += 1;
|
||||
});
|
||||
this.renderEntries.length = this.renderEntryCount;
|
||||
|
||||
renderList.sort((a, b) => {
|
||||
this.renderEntries.sort((a, b) => {
|
||||
if (a.frontScore !== b.frontScore) return a.frontScore - b.frontScore;
|
||||
if (a.spawnOrder !== b.spawnOrder) return a.spawnOrder - b.spawnOrder;
|
||||
return a.eid - b.eid;
|
||||
});
|
||||
|
||||
renderList.forEach((item, index) => {
|
||||
this.renderEntries.forEach((item, index) => {
|
||||
item.node.setSiblingIndex(index);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user