You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
7.0 KiB
191 lines
7.0 KiB
import { Director, director, game } from 'cc';
|
|
import { EDITOR } from 'cc/env';
|
|
import { LoadScheduler } from '../../mx/module/res/LoadScheduler';
|
|
import { ResourcePhase } from '../../mx/module/res/ResourcePhase';
|
|
import { ThermalProfile } from './ThermalProfile';
|
|
import { PerfReport } from './PerfReport';
|
|
|
|
/**
|
|
* 发热治理:后台 IO 预算 + 分相位帧率。
|
|
* 大厅保持 60;战斗按 DeviceCapability 档固定 50/40/30(tier3/1/0)。
|
|
*/
|
|
export class ThermalGovernor {
|
|
private static _installed = false;
|
|
private static _phase: ResourcePhase = ResourcePhase.Boot;
|
|
private static _stressLevel = 0;
|
|
/** 本局战斗已改用稳定档 */
|
|
private static _usingSustain = false;
|
|
/** 本局已两次冲上限失败,不再回升 */
|
|
private static _lockedThisBattle = false;
|
|
/** 本局从上限掉到稳定档的次数 */
|
|
private static _failCount = 0;
|
|
/** 实测低于阈值的累计秒 */
|
|
private static _belowSec = 0;
|
|
/** 稳定档上累计秒(用于尝试回升) */
|
|
private static _okSec = 0;
|
|
/** 帧间隔指数滑动平均 */
|
|
private static _emaDt = 1 / 60;
|
|
/** 切帧率后跳过若干帧,避免 dt 尖峰误判 */
|
|
private static _ignoreFrames = 0;
|
|
|
|
/** 上限档滚动 FPS 低于此值则准备切稳定档 */
|
|
private static readonly ADAPT_DOWN_FPS = 50;
|
|
/** 低于阈值需持续的秒数 */
|
|
private static readonly ADAPT_DOWN_SEC = 1.5;
|
|
/** 稳定档上保持多久才尝试回到上限 */
|
|
private static readonly ADAPT_UP_SEC = 8;
|
|
/** 切帧率后忽略的帧数 */
|
|
private static readonly ADAPT_IGNORE_FRAMES = 3;
|
|
/** EMA 新样本权重 */
|
|
private static readonly EMA_ALPHA = 0.15;
|
|
|
|
static install(): void {
|
|
if (this._installed) return;
|
|
this._installed = true;
|
|
ThermalProfile.resolve();
|
|
director.on(Director.EVENT_AFTER_UPDATE, ThermalGovernor._onAfterUpdate);
|
|
}
|
|
|
|
static onResourcePhase(phase: ResourcePhase): void {
|
|
if (!this._installed) this.install();
|
|
const wasBattle = this._phase === ResourcePhase.Battle;
|
|
const nowBattle = phase === ResourcePhase.Battle;
|
|
if (wasBattle !== nowBattle) {
|
|
this._resetAdaptive();
|
|
}
|
|
this._phase = phase;
|
|
this._apply();
|
|
}
|
|
|
|
/** MemoryDowngrade D1/D2:收紧后台预载;D2 可把战斗钉在稳定档 */
|
|
static applyStressLevel(level: number): void {
|
|
if (level <= this._stressLevel) return;
|
|
this._stressLevel = level;
|
|
if (level >= 2) {
|
|
this._usingSustain = true;
|
|
}
|
|
this._apply();
|
|
}
|
|
|
|
static clearStress(): void {
|
|
if (this._stressLevel === 0) return;
|
|
this._stressLevel = 0;
|
|
this._apply();
|
|
}
|
|
|
|
/** 进出战斗时复位自适应状态,下一局重新冲击上限 */
|
|
private static _resetAdaptive(): void {
|
|
this._usingSustain = false;
|
|
this._lockedThisBattle = false;
|
|
this._failCount = 0;
|
|
this._belowSec = 0;
|
|
this._okSec = 0;
|
|
this._emaDt = 1 / 60;
|
|
this._ignoreFrames = ThermalGovernor.ADAPT_IGNORE_FRAMES;
|
|
}
|
|
|
|
/** 战斗中按滚动帧时在上限档与稳定档之间切换 */
|
|
private static _onAfterUpdate(): void {
|
|
if (EDITOR) return;
|
|
if (ThermalGovernor._ignoreFrames > 0) {
|
|
ThermalGovernor._ignoreFrames--;
|
|
return;
|
|
}
|
|
if (ThermalGovernor._phase !== ResourcePhase.Battle) return;
|
|
const cfg = ThermalProfile.current;
|
|
if (!cfg.enableAdaptiveBattleFrameRate) return;
|
|
if (cfg.battleFrameRate <= cfg.battleSustainFrameRate) return;
|
|
if (ThermalGovernor._stressLevel >= 2) return;
|
|
if (ThermalGovernor._lockedThisBattle) return;
|
|
|
|
const dt = game.deltaTime;
|
|
if (!(dt > 0) || dt > 0.1) return;
|
|
|
|
const a = ThermalGovernor.EMA_ALPHA;
|
|
ThermalGovernor._emaDt = ThermalGovernor._emaDt * (1 - a) + dt * a;
|
|
const fps = 1 / ThermalGovernor._emaDt;
|
|
|
|
if (ThermalGovernor._usingSustain) {
|
|
ThermalGovernor._okSec += dt;
|
|
if (ThermalGovernor._okSec >= ThermalGovernor.ADAPT_UP_SEC) {
|
|
ThermalGovernor._usingSustain = false;
|
|
ThermalGovernor._okSec = 0;
|
|
ThermalGovernor._belowSec = 0;
|
|
ThermalGovernor._ignoreFrames = ThermalGovernor.ADAPT_IGNORE_FRAMES;
|
|
ThermalGovernor._apply();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (fps < ThermalGovernor.ADAPT_DOWN_FPS) {
|
|
ThermalGovernor._belowSec += dt;
|
|
if (ThermalGovernor._belowSec >= ThermalGovernor.ADAPT_DOWN_SEC) {
|
|
ThermalGovernor._usingSustain = true;
|
|
ThermalGovernor._belowSec = 0;
|
|
ThermalGovernor._okSec = 0;
|
|
ThermalGovernor._failCount++;
|
|
if (ThermalGovernor._failCount >= 2) {
|
|
ThermalGovernor._lockedThisBattle = true;
|
|
}
|
|
ThermalGovernor._ignoreFrames = ThermalGovernor.ADAPT_IGNORE_FRAMES;
|
|
ThermalGovernor._apply();
|
|
}
|
|
return;
|
|
}
|
|
ThermalGovernor._belowSec = 0;
|
|
}
|
|
|
|
/** 当前相位应使用的目标帧率 */
|
|
private static _resolveTargetFps(): number {
|
|
const cfg = ThermalProfile.current;
|
|
if (!cfg.enablePhaseFrameRate) {
|
|
return cfg.battleFrameRate;
|
|
}
|
|
switch (this._phase) {
|
|
case ResourcePhase.Hall:
|
|
case ResourcePhase.HallPreload:
|
|
case ResourcePhase.BattleExit:
|
|
return cfg.hallFrameRate;
|
|
case ResourcePhase.Boot:
|
|
return cfg.bootFrameRate;
|
|
case ResourcePhase.Battle:
|
|
if (this._usingSustain || (this._stressLevel >= 2 && cfg.enableStressFrameRate)) {
|
|
return cfg.battleSustainFrameRate;
|
|
}
|
|
return cfg.battleFrameRate;
|
|
default:
|
|
return cfg.hallFrameRate;
|
|
}
|
|
}
|
|
|
|
private static _apply(): void {
|
|
ThermalProfile.resolve();
|
|
const cfg = ThermalProfile.current;
|
|
const scheduler = LoadScheduler.getInstance();
|
|
|
|
let loadScale = cfg.hallLoadBudgetScale;
|
|
if (this._stressLevel >= 1) {
|
|
loadScale = Math.min(loadScale, cfg.stressLoadBudgetScale);
|
|
}
|
|
|
|
if (this._phase === ResourcePhase.Battle) {
|
|
scheduler.setSilentBudgetScale(1);
|
|
scheduler.setBackgroundBudgetScale(1);
|
|
} else {
|
|
scheduler.setSilentBudgetScale(loadScale);
|
|
scheduler.setBackgroundBudgetScale(loadScale);
|
|
}
|
|
|
|
if (EDITOR) return;
|
|
|
|
let target = this._resolveTargetFps();
|
|
if (this._stressLevel >= 2 && cfg.enableStressFrameRate && this._phase !== ResourcePhase.Battle) {
|
|
target = Math.min(target, cfg.stressHallFrameRate);
|
|
}
|
|
|
|
if (game.frameRate !== target) {
|
|
game.frameRate = target;
|
|
PerfReport.reportThermalFps(this._phase, target, this._stressLevel);
|
|
}
|
|
}
|
|
}
|
|
|