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, 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 = { 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 | 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 { return new Promise((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 { 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; } }