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.
AdLike2048/assets/mx/module/res/LoadScheduler.ts

235 lines
7.3 KiB

3 weeks ago
import { director, Director } from 'cc';
import { Singleton } from '../../tools/Singleton';
import { LoadPriority, LoadTask, ScheduledTask } from './LoadPolicy';
const PRIORITY_ORDER: LoadPriority[] = ['critical', 'interactive', 'background', 'silent'];
const BUDGET_MS: Record<Exclude<LoadPriority, 'silent'>, number> = {
critical: 16,
interactive: 8,
background: 2,
};
/**
* 分优先级任务调度器。
* - critical / interactive:单 worker + 帧预算(保交互)
* - background:单 inflight,挂主循环
* - silent:定时器泵,不占 AFTER_UPDATE;用户加载期间停发
*/
export class LoadScheduler extends Singleton {
private _nextId = 1;
private _queues: Record<LoadPriority, ScheduledTask[]> = {
critical: [],
interactive: [],
background: [],
silent: [],
};
private _running = false;
private _backgroundInflight = false;
private _silentInflight = 0;
private _silentMaxConcurrent = 1;
private _silentBudgetMs = 4;
private _silentGapMs = 50;
private _silentPaused = false;
private _silentSlowdown = false;
private _silentBudgetScale = 1;
private _backgroundBudgetScale = 1;
private _userLoadCount = 0;
private _silentTimer: ReturnType<typeof setTimeout> | null = null;
private _backgroundAllowed: () => boolean = () => true;
private _boundTick: () => void;
private _started = false;
constructor() {
super();
this._boundTick = this._tick.bind(this);
}
start(): void {
if (this._started) return;
this._started = true;
director.on(Director.EVENT_AFTER_UPDATE, this._boundTick);
this._armSilentTimer();
}
stop(): void {
if (!this._started) return;
this._started = false;
director.off(Director.EVENT_AFTER_UPDATE, this._boundTick);
this._clearSilentTimer();
}
/** 玩家点开页面 / 进战:停发新的 silent */
beginUserLoad(): void {
this._userLoadCount++;
}
/** 与 beginUserLoad 配对 */
endUserLoad(): void {
if (this._userLoadCount <= 0) return;
this._userLoadCount--;
}
/** 恢复失败:清掉未配对的用户加载门闩 */
resetUserLoad(): void {
this._userLoadCount = 0;
}
/** 是否有用户可见加载 */
isUserLoadActive(): boolean {
return this._userLoadCount > 0;
}
/** 丢弃未开始的 silent(进行中的原生 loadBundle 无法取消) */
clearSilentQueue(): void {
const queue = this._queues.silent;
while (queue.length > 0) {
const item = queue.shift();
if (item) item.resolve();
}
}
setSilentPaused(paused: boolean): void {
this._silentPaused = paused;
}
setSilentSlowdown(slow: boolean): void {
this._silentSlowdown = slow;
}
setSilentMaxConcurrent(n: number): void {
if (!Number.isFinite(n)) return;
this._silentMaxConcurrent = Math.max(1, Math.min(8, Math.floor(n)));
}
setSilentBudgetMs(ms: number): void {
if (!Number.isFinite(ms)) return;
this._silentBudgetMs = Math.max(1, Math.min(32, ms));
}
/** silent 定时器间隔(毫秒),与主循环无关 */
setSilentGapMs(ms: number): void {
if (!Number.isFinite(ms)) return;
this._silentGapMs = Math.max(16, Math.min(500, ms));
}
setSilentBudgetScale(scale: number): void {
this._silentBudgetScale = this._clampScale(scale);
}
setBackgroundBudgetScale(scale: number): void {
this._backgroundBudgetScale = this._clampScale(scale);
}
private _clampScale(scale: number): number {
if (!Number.isFinite(scale)) return 1;
return Math.max(0.25, Math.min(1, scale));
}
setBackgroundAllowed(allowed: () => boolean): void {
this._backgroundAllowed = allowed;
}
enqueue(priority: LoadPriority, task: LoadTask): Promise<void> {
return new Promise<void>((resolve, reject) => {
const item: ScheduledTask = {
id: this._nextId++,
priority,
run: task,
resolve,
reject,
};
this._queues[priority].push(item);
});
}
private _tick(): void {
if (!this._running) {
for (const priority of PRIORITY_ORDER) {
if (priority === 'silent') break;
if (priority === 'background') {
if (this._backgroundInflight || !this._backgroundAllowed()) continue;
const queue = this._queues.background;
if (queue.length === 0) continue;
const item = queue.shift()!;
this._backgroundInflight = true;
void item.run()
.then(() => item.resolve())
.catch((e) => item.reject(e))
.finally(() => {
this._backgroundInflight = false;
});
return;
}
const queue = this._queues[priority];
if (queue.length === 0) continue;
void this._drainQueue(queue, BUDGET_MS[priority]);
return;
}
}
}
private _armSilentTimer(): void {
if (this._silentTimer != null || !this._started) return;
this._silentTimer = setTimeout(this._onSilentTimer, this._silentGapMs);
}
private _clearSilentTimer(): void {
if (this._silentTimer == null) return;
clearTimeout(this._silentTimer);
this._silentTimer = null;
}
private _onSilentTimer = (): void => {
this._silentTimer = null;
this._pumpSilent();
this._armSilentTimer();
};
/** silent 独立并行池:定时器驱动,用户加载时停发 */
private _pumpSilent(): void {
if (this._silentPaused || this._userLoadCount > 0) return;
if (this._silentSlowdown && this._queues.interactive.length > 0) return;
const queue = this._queues.silent;
if (queue.length === 0) return;
const budget = this._silentBudgetMs
* this._silentBudgetScale
* (this._silentSlowdown ? 0.5 : 1);
const deadline = performance.now() + budget;
let started = 0;
while (
queue.length > 0
&& this._silentInflight < this._silentMaxConcurrent
&& (started === 0 || performance.now() < deadline)
) {
const item = queue.shift()!;
this._silentInflight++;
started++;
void item.run()
.then(() => item.resolve())
.catch((e) => item.reject(e))
.finally(() => {
this._silentInflight--;
});
}
}
private async _drainQueue(queue: ScheduledTask[], budgetMs: number): Promise<void> {
this._running = true;
const deadline = performance.now() + budgetMs;
while (queue.length > 0 && performance.now() < deadline) {
const item = queue.shift()!;
try {
await item.run();
item.resolve();
} catch (e) {
item.reject(e);
}
}
this._running = false;
}
}