/** 资源包加载结果 */ export interface LoadResult { /** 是否全部条目加载成功 */ ok: boolean; /** 失败条目键(bundle/path/name) */ failed: string[]; } /** 加载恢复默认参数 */ export const LoadRecoveryConfig = { /** 单次 await 默认超时(毫秒) */ defaultTimeoutMs: 30000, /** loadBundle 超时;须短于 loading 看门狗,失败后才能再点页面 */ bundleTimeoutMs: 20000, /** 单预制 bundle.load 超时 */ prefabTimeoutMs: 20000, /** loadDir 超时(首包整目录下载比单预制久) */ dirTimeoutMs: 60000, /** Tab 预载等待超时 */ tabIntentTimeoutMs: 25000, /** 战斗 warm 等待超时(与 loadDir 对齐;超时后走进战加载,不得卡匹配页) */ battleWarmTimeoutMs: 60000, /** loadResPakg 失败重试次数 */ maxRetry: 2, }; /** * 为 Promise 增加超时;超时 reject 并带上 label 便于 recover 打点。 */ export function awaitWithTimeout( promise: Promise, timeoutMs: number, label: string, ): Promise { if (timeoutMs <= 0) return promise; return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(`[LoadRecovery] timeout:${label}:${timeoutMs}ms`)); }, timeoutMs); promise.then( (v) => { clearTimeout(timer); resolve(v); }, (e) => { clearTimeout(timer); reject(e); }, ); }); } /** * 带重试的加载:仅当 result.ok 为 false 或抛错时重试。 */ export async function loadWithRetry( loadFn: () => Promise, opts?: { maxRetry?: number; label?: string }, ): Promise { const maxRetry = opts?.maxRetry ?? LoadRecoveryConfig.maxRetry; const label = opts?.label ?? 'load'; let last: LoadResult = { ok: false, failed: ['unknown'] }; for (let attempt = 0; attempt <= maxRetry; attempt++) { try { last = await loadFn(); if (last.ok) return last; } catch (e) { console.error(`[LoadRecovery] ${label} attempt ${attempt}`, String(e)); last = { ok: false, failed: [String(e)] }; } } return last; } /** 拼装失败资源键,供日志与 PF */ export function formatLoadKey(bundle: string, path: string, name: string): string { return `${bundle}:${path}${name}`; }