消消方块阵换皮表情
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.

264 lines
9.3 KiB

1 week ago
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<number> = new Set([
UIID.Home, UIID.Shop, UIID.Role, UIID.Weapon, UIID.Replica,
]);
/** 主 Tab 邻接预测(不含自身) */
private static readonly NEIGHBORS: Readonly<Record<number, number[]>> = {
[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<typeof setTimeout> | null = null;
private static _running = false;
private static _queue: SoftTask[] = [];
private static _visits = new Map<number, number>();
private static _softReady = new Set<number>();
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<void> {
return new Promise((r) => setTimeout(r, SmartPreload.YIELD_MS));
}
private static async _drain(): Promise<void> {
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<void> {
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<void>((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);
5 days ago
gg.sdk?.pfLogKey?.('RES:PREFAB_FAIL', `preload:${conf.bundle}/${path}`);
1 week ago
}
resolve();
});
});
} catch (e) {
console.warn('[SmartPreload] preloadPanel', uiId, e);
}
}
}