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/docs/方案-加载性能优化.md

145 lines
8.6 KiB

3 weeks ago
# 方案:加载性能优化(冷启动 / 静默下载 / 点击优先)
> 日期:2026-09-11
> 关联:`docs/性能打点说明.md`、`assets/mx/module/res/*`
> **打开 / 引用 / 分包所有权** 以 [`方案-资源与UI生命周期.md`](./方案-资源与UI生命周期.md) 为准(2026-09-14)。
## 1. 问题与证据
现有启动链已具备 Phase / LoadScheduler / PreloadPredictor,但和「最快进首页 + 后台静默下载」目标不一致:
| 现状 | 后果 |
|------|------|
| Boot 并行拉 `icons`、`popCommon` | 与 `home` 抢带宽,首进/冷启动变慢 |
| `preLoadResPakg` 对弹窗做 `bundle.load` + addRef | 静默路径占内存,且曾与进战 `clearResPkg` 并发导致贴图失效(战斗 silent warm 已关闭,保留) |
| LoadScheduler silent 挂在 `AFTER_UPDATE` | 后台 IO 吃主循环帧预算 |
| `openPanel` 不暂停 silent | 玩家点页面仍可能被后台下载拖慢 |
## 2. 原则
1. **真正进内存的加载按需**:进首页只 load 首页能画出来的资源;点哪个页面就 load 哪个。
2. **静默 = 下载分包 / `bundle.preload`**:不 `addRef`、不进战斗资源包内存、可随时丢弃。
3. **静默与主循环解耦**:用 `setTimeout` 泵 silent,不走 `Director.EVENT_AFTER_UPDATE`。
4. **玩家打开 / 自动弹窗 / 进战 = 最高优先级**:`beginUserLoad` 立刻停发新的 silent;进行中的原生 `loadBundle` 无法取消,但不继续排队。
5. **分档只调激进度与表现,不 fork 业务**:下载并发/间隔用 `MemoryProfile`;战斗特效用已有 `DeviceProfile`。
6. **不恢复战斗资源包的内存预热**(`scheduleBattleWarm('silent')` / `preLoadLevelRes` 保持关闭),避免贴图生命周期回归。
## 3. 阶段
```
Boot(最小)
First + Comm + icons_boot + home + BootRes
表:Const / Item / Rank / Level(首页展示与点开始)
│
▼
first/comm/home 分包落地(BootRes 仍在本包 load)
│ 立刻静默 loadBundle(icons),不阻塞、不 acquire
▼
Hall 可交互(Home 已打开)
│ 延迟 2 帧,避免和首页首帧抢 IO
▼
Silent A:icons 优先(已在下则跳过),再战斗分包(loadBundle,不 acquire、不 load 资源)
heros / battleGame / battleMap / battleGameMonster / battleHero / popBattle
(不含已删除的 role / mechanismEffect;大厅名单只含磁盘上有预制的页面)
│
▼
Silent B:下载页面分包 + preload 弹窗预制
match 优先,再大厅页/弹窗
│
▼
玩家点击 / 自动弹窗 / 进战
beginUserLoad → 暂停 silent → 按需 loadPrefab / loadResPakg(进战最小包)
│
▼
BattleGame 已打开
│
▼
Silent C:局内弹窗 preload(暂停优先)+ 受击/死亡/武器目录 load
不 addRef;点开弹窗仍走 openPanel 最高优先
```
新玩家与老玩家冷启动同一条最小 Boot;老号缓存命中后 `loadBundle` 更快,但仍不在 Boot 里拉战斗包和全量弹窗。
进战最小包:地图、出怪口、模板/字块/营地、本关血条与开场怪、状态特效、飘字、怪物皮肤目录、英雄体/攻效/buff 目录。
进局后再补:`popBattle` 弹窗、受击/死亡、组合武器目录。BGM 由 `playBgmBattle` 按需拉。
## 4. 五维影响
| 加载 | 内存 | FPS | CPU | 发热 |
|------|------|-----|-----|------|
| 首进/冷启动少下 `icons`+`popCommon`,进首页更快 | 大厅不再把 HallWarm 整包 addRef | silent 离开主循环,首帧更稳 | 后台 IO 错峰 | 低档拉长 silent 间隔 |
补偿:段位图标走 `SpriteLoad` 按需拉 `icons`;点设置/体力等首次需等 `popCommon`(随后 silent 会补下)。进战 `loadResPakg` 只解最小包;局内弹窗/受击未预完时按需 load。
## 5. 资源所有权(不可违反)
- Silent `loadBundle` **不** `BundleLifecycle.acquire`,避免 `owner=res` 钉死战斗包,导致退战卸不掉。
- 进战 `loadResPakg` / `openPanel` 仍按原 owner 登记;释放顺序不变:teardown → onDestroy → waitFrames → clearResPkg / releaseBattlePhase。
- 进战 `abortHallSilentDownload` + `clearSilentQueue`,禁止大厅 silent 与 `loadResPakg(clear)` 并发。
- 局内 silent 不 `acquire`、不进 `BattleRes`;退战先 abort 再 teardown → `clearResPkg` / `releaseBattlePhase`。
- 战斗中允许 silent(`BattlePhysicsGate` 不再暂停);切后台 / 内存降档仍暂停。
## 6. 回归
- 清缓存冷启动:新号 / 老号都能进 Home,段位图可晚一拍出现。
- 进 Home 后立刻点开始:匹配页优先加载,不受 silent 阻塞。
- 进 Home 后立刻点神将/设置:该页优先,silent 停发。
- 战斗 ↔ 大厅 ≥3 次:无缺贴图 / Spine;退战战斗包仍可卸。
- 进战立刻点暂停/结算/Boss:未预完也能按需打开。
- 首召 / 首波:英雄体、怪物皮肤、状态特效仍在进战包内。
- 弱网:silent 失败只打日志,不 `recoverHome`。
## 7. 线上卡 loading / 失败后打不开页面
老项目就有:`assetManager.loadBundle` / `bundle.load` **无超时、同名可并发、回调可能永不回来**。
昨天把 `popCommon` / 局内弹窗 / 战斗包改成按需后,这条路径变成线上主路径,缺陷被放大。
| 缺陷 | 后果 |
|------|------|
| 静默 `loadBundle` 与玩家 `loadBundle` 同名并发 | 小游戏上第二回调可能永不触发,loading 一直转 |
| `openPanel` 用 `_panelOpenInflight` 等第一次打开 | 第一次挂死后,同页面后续点击永久等待 |
| `recoverHome` 只 `endUserLoad` 一次、再 `enterMain` | 门闩泄漏;Home 再挂则死循环卡 loading |
| `closeAllPanel` 清 inflight 却不 resolve | 已 await 的打开再也结束不了 |
修复:`BundleLifecycle.ensureLoaded` 同名合并 + 20s 超时;加载中禁止 `removeBundle`;`openPanel` 超时失败并 `abortInflightOpens`;`recoverHome` 重置用户加载门闩,连续失败改重启提示。
**2026-09-18:** 根因是 `openPanel` 与静默预载对同一预制双飞原生 load,回调丢失后失效缓存被当成永久失败。预制必须走 `ResourcesManager.loadRes` 合并。进战仍释放 HallWarm **预制**;`popCommon` zip 常驻;回大厅先 decode 再静默战斗包。
## 8. 新用户首局缺资源(2026-09-12)
首进失败、再进正常:不是贴图坏了,是冷启动优化把「齐套」拆成后台补齐,首局用到时还没齐。
| 缺陷 | 后果 |
|------|------|
| Hero/Weapon/Text/Monster1 延后加载,`getTableData` 只点火不阻塞 | 首局组装进战包漏血条/特效;AI 武器全 miss |
| `loadBundle` 超时 `finish(null)` 并清 inflight | 新用户首下未完就二次并发,回调丢失 |
| 静默超时把分包打进 `_silentMissBundles` | 后续永远跳过该包 |
| 预载 evict 后 `loadResPakg(fresh=false)` 复用已释放 `bundle.get` | 血条/皮肤 getNode 空,再进因缓存命中又好了 |
| `preLoadResPakg` 失败仍标 warm 完成 | 进战以为已预热,跳过强制重载 |
修复:开战前 `ensureBattleTables` 再组包;超时不拆原生 inflight;warm 失败才 `fresh` 重载;血条三种预制固定进最小包。
## 9. 匹配页卡死(2026-09-12)
抖音首包 `preLoadResPakg` 常超过 30s。匹配门闩 `awaitBattleWarm` 超时抛错后仍进战,进战再次 `await` 同一 Promise,超时走 `_failBattleEnter`,首页已关、匹配页不关。
修复:匹配页只点火 warm,门闩只等表 / 神将包 / BattleGame 预制;`awaitBattleWarm` 超时返回 false 不抛;进战超时改 `loadResPakg`,不失败离场。`battleWarmTimeoutMs` 与 `dirTimeoutMs` 对齐为 60s。
## 10. 首局加载过久(2026-09-12)
抖音新包点开始要 30s+,不是网单独慢,是下载被自己掐掉再重来:
| 缺陷 | 后果 |
|------|------|
| 战斗分包走 silent 队列,tier0 并行 1、间隔 120ms | heros→battleGame→地图…串行下 |
| 点开始 `abortHallSilentDownload` + `beginUserLoad` | 没开始的战斗包出队,匹配再从头下 |
| warm 超时 `abort` + `loadResPakg(fresh)` | 已下到一半的目录被 evict,再下一遍 |
修复:BootRes 完成后并行 `ensureLoaded` 战斗分包(不进 silent、点开始不停);进战 warm 仍在飞则 `loadResPakg(fresh=false)` 合并,禁止 evict 重来。
| 加载 | 内存 | FPS | CPU | 发热 |
|------|------|-----|-----|------|
| 首页展示后战斗包已在下,首局少等一轮 zip | 与原 silent 同包,不 addRef | 首页首帧后才开火,不抢 BootRes | 并行 loadBundle 峰值略升 | 低档可接受 |
回归:清缓存冷启动立刻点开始;在首页停 5s 再点;战斗↔大厅 ×3。