import { assetManager, Prefab } from 'cc'; import { UIConfigs, UIID } from './ConfigRes'; import { HomeBattlePreload } from './HomeBattlePreload'; import { BattlePerformance } from '../ui/BattleGame/BattlePerformance'; type SoftTask = | { kind: 'panel'; uiId: number; priority: number } | { kind: 'battle'; chapterId: number; priority: number }; /** * 智能预测预加载:平衡首次打开体验与内存。 * * - 预测:当前 Tab 邻接 + 老玩家意图(离线奖励/签到/商城/武器) * - 调度:页面稳定后空闲执行,任务间 yield,单队列 * - 降级:内存压力 ≥2 暂停;≥3 清空队列 * - 只 loadBundle + load prefab(不 instantiate),命中 UIManager.bundle.get * - 战斗预热仍走 {@link HomeBattlePreload},由本模块触发时机 */ export class SmartPreload { private static readonly SETTLE_MS = 700; private static readonly YIELD_MS = 40; /** 邻接 Tab 最多预热数(Home 另加意图弹窗) */ private static readonly MAX_TAB_TASKS = 2; private static readonly MAX_SOFT_PANELS = 6; private static readonly MAIN_TABS: ReadonlySet = new Set([ UIID.Home, UIID.Shop, UIID.Role, UIID.Weapon, UIID.Replica, ]); /** 主 Tab 邻接预测(不含自身) */ private static readonly NEIGHBORS: Readonly> = { [UIID.Home]: [UIID.Weapon, UIID.Shop, UIID.Role, UIID.Replica], [UIID.Shop]: [UIID.Home, UIID.Role], [UIID.Role]: [UIID.Home, UIID.Weapon], [UIID.Weapon]: [UIID.Home, UIID.Role], [UIID.Replica]: [UIID.Home, UIID.Shop], }; private static _paused = false; private static _gen = 0; private static _timer: ReturnType | null = null; private static _running = false; private static _queue: SoftTask[] = []; private static _visits = new Map(); private static _softReady = new Set(); private static _pageId = 0; /** 切到主界面 Tab / 回大厅后调用 */ static onMainTab(pageId: number): void { if (!pageId) return; this._pageId = pageId; this._bumpVisit(pageId); this._paused = false; if (pageId !== UIID.Home) { HomeBattlePreload.pause(); } this._rebuildQueue(pageId); this._scheduleRun(SmartPreload.SETTLE_MS); } /** 进战斗 / 离开大厅:停掉软预载 */ static pause(): void { this._clearTimer(); this._paused = true; this._gen++; this._queue.length = 0; HomeBattlePreload.pause(); } /** 平台内存告警:升档时暂停或清空 */ static onMemoryPressure(warnLevel: number): void { if (warnLevel >= 15 || BattlePerformance.memoryPressureLevel() >= 3) { this.pause(); return; } if (warnLevel >= 10 || BattlePerformance.memoryPressureLevel() >= 2) { this._clearTimer(); this._queue.length = 0; HomeBattlePreload.pause(); } } /** 是否已软预热过该面板(调试用) */ static isSoftReady(uiId: number): boolean { return this._softReady.has(uiId); } private static _bumpVisit(uiId: number): void { this._visits.set(uiId, (this._visits.get(uiId) ?? 0) + 1); } private static _rebuildQueue(pageId: number): void { this._queue.length = 0; if (this._memBlocked()) return; // Home 意图弹窗优先于邻接 Tab:老玩家上线先点离线/签到 if (pageId === UIID.Home) { this._pushIntentPops(); } const neighbors = this.NEIGHBORS[pageId] ?? []; const scored: SoftTask[] = []; for (let i = 0; i < neighbors.length; i++) { const id = neighbors[i]; if (!this._canPreloadPanel(id)) continue; const visit = this._visits.get(id) ?? 0; scored.push({ kind: 'panel', uiId: id, priority: visit * 10 + (neighbors.length - i) }); } scored.sort((a, b) => b.priority - a.priority); for (let i = 0; i < scored.length && i < SmartPreload.MAX_TAB_TASKS; i++) { this._queue.push(scored[i]); } if (pageId === UIID.Home) { const chapterId = gg?.game?.CurentSelectChapterId || gg?.data?.doc?.chapter || 0; if (chapterId > 0) { this._queue.push({ kind: 'battle', chapterId, priority: 1 }); } } this._queue.sort((a, b) => b.priority - a.priority); } /** * 老玩家高频路径(仅 Home): * - 离线奖励 wipeOut(章节>1)+ 领奖弹窗 GetReward * - 每天首次登录签到 sign7Day */ private static _pushIntentPops(): void { const chapter = gg?.data?.doc?.chapter ?? 0; if (chapter > 1) { this._enqueuePanel(UIID.wipeOut, 100); this._enqueuePanel(UIID.GetReward, 90); } if (gg?.data?.IsFirstLoginToday) { this._enqueuePanel(UIID.sign7Day, 95); } } private static _enqueuePanel(uiId: number, priority: number): void { if (!this._canPreloadPanel(uiId)) return; this._queue.push({ kind: 'panel', uiId, priority }); } private static _canPreloadPanel(uiId: number): boolean { if (this._softReady.has(uiId)) return false; if (this._softReady.size >= SmartPreload.MAX_SOFT_PANELS) return false; if (gg?.ui?.hasPanel?.(uiId)) { this._softReady.add(uiId); return false; } const conf = UIConfigs[uiId]; if (!conf?.bundle || !conf?.name) return false; if (this.MAIN_TABS.has(uiId) && uiId !== UIID.Home) { try { if (gg?.newFun && !gg.newFun.isUnLockMainPageTab(uiId)) { return false; } } catch { // 解锁表未就绪时仍允许预载 } } return true; } private static _memBlocked(): boolean { return BattlePerformance.memoryPressureLevel() >= 2; } private static _scheduleRun(delayMs: number): void { this._clearTimer(); if (this._paused || this._queue.length === 0) return; this._timer = setTimeout(() => { this._timer = null; void this._drain(); }, delayMs); } private static _clearTimer(): void { if (this._timer != null) { clearTimeout(this._timer); this._timer = null; } } private static _alive(gen: number): boolean { return !this._paused && this._gen === gen && !gg?.game?.CurentBattle; } private static _yield(): Promise { return new Promise((r) => setTimeout(r, SmartPreload.YIELD_MS)); } private static async _drain(): Promise { if (this._running) return; const gen = ++this._gen; this._running = true; try { while (this._alive(gen) && this._queue.length > 0) { if (this._memBlocked()) { this._queue.length = 0; break; } const task = this._queue.shift(); if (!task) break; if (task.kind === 'panel') { await this._preloadPanel(task.uiId); } else if (task.kind === 'battle') { // 邻接 Tab 已优先;再错峰启动战斗包预热 HomeBattlePreload.scheduleResume(task.chapterId, 800); } if (!this._alive(gen)) break; await this._yield(); } } catch (e) { console.warn('[SmartPreload] drain failed', e); } finally { if (this._gen === gen) { this._running = false; } } } /** 仅缓存 prefab,供 UIManager.openPanel 命中 bundle.get */ private static async _preloadPanel(uiId: number): Promise { if (this._softReady.has(uiId)) return; const conf = UIConfigs[uiId]; if (!conf) return; const path = conf.path + conf.name; try { let bundle = assetManager.getBundle(conf.bundle); if (!bundle) { bundle = await new Promise((resolve) => { assetManager.loadBundle(conf.bundle, (err, b) => { resolve(err ? null : b); }); }); } if (!bundle) return; if (bundle.get(path, Prefab)) { this._softReady.add(uiId); console.log(`[SmartPreload] hit ${conf.bundle}/${path}`); return; } await new Promise((resolve) => { bundle!.load(path, Prefab, (err) => { if (!err) { this._softReady.add(uiId); console.log(`[SmartPreload] ready ${conf.bundle}/${path}`); } else { console.warn(`[SmartPreload] load fail ${conf.bundle}/${path}`, err); } resolve(); }); }); } catch (e) { console.warn('[SmartPreload] preloadPanel', uiId, e); } } }