[P0][CR-F2/F3] 关卡切换、换英雄、重刷怪的异步竞态与 GPU 纹理泄漏 #529
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
问题描述
关卡切换、换英雄、调整怪物密度或精英倍率,都会发起异步的图集加载。这些加载都不能取消,也不检查结果是否已经过期,所以:
addAtlas到已经废弃的 runtime 上,这些纹理再也不会被释放。证据
F2:关卡切换泄漏 GPU 纹理
preloadRemaining用void runtime.loadPages(...)在后台加载剩余的页面,这个加载无法取消:pack-loader.ts:757-765。swapLevel删除旧 runtime 的全部 pages 和 objectPages:pack-loader.ts:899-902。addAtlas到旧 runtime 的数组里:pack-loader.ts:683、pack-loader.ts:704。这个 runtime 已经没人引用,这些纹理也永远不会被deleteAtlas。state.pagesLoaded(L761)。F3:换英雄与重刷怪的竞态
switchHero在await loadCharacterArt(...)之后,才删除"当前"英雄的图集并换上新的:toolbar-chat-ui.ts:383-395。快速切换两次(A→B→C)时,如果 B 比 C 晚完成:reloadMonsters(toolbar-chat-ui.ts:431-545)有两个问题:travelling守卫(这个标志是 pack-loader 的局部变量,pack-loader.ts:823)。加载途中切换了关卡,完成后仍会用旧关卡的怪物美术覆盖新关卡;setMonsterArtMap(loaded.map)(L541)之前没有删除旧 map 的图集,所以每调整一次密度或精英倍率,就泄漏一批纹理。heroSelect.onchange = async () => { await switchHero(...) }没有 catch:act-scene.ts:259。仓库里已有的正确写法
syncSorceressAppearance使用appearanceSeq令牌,await 之后检查seq !== appearanceSeq就丢弃结果:toolbar-chat-ui.ts:306-310。travelling防止重复切换。这两种模式都是对的,只是没有推广到其他加载路径。
根因
异步加载没有统一的"所有权"概念:谁发起的请求、结果属于哪一代 runtime、过期的结果由谁释放,都没有约定。每个调用点各自处理,大多数调用点根本没有处理。
修复指南
fetch传入signal;isCurrent(gen);renderer.deleteAtlas(handle)并返回,不写任何状态。swapLevel删除旧 pages 之前先调用next(),让在途的loadPages失效;loadPages在addAtlas之前和之后都检查一次;state。LoadGeneration;deleteAtlas。deleteAtlas旧 map 的全部 handle。console.error,不产生未处理的 rejection。SpriteRenderer加一个只读的liveTextureCount(它已经有纹理注册表);import.meta.env.DEV下显示到调试面板里,泄漏就能被直接看到。验收标准
addAtlas/deleteAtlas),每种情况结束后存活的纹理数都回到基线:loadPages的 fetch 延迟到切换之后才完成;reloadMonsters,其中一次在加载途中切换关卡。void somePromise调用点都有.catch,或者改为通过统一的包装函数调用。liveTextureCount不再单调增长。npm run typecheck0 error;npx vitest run全部通过。相关
已在提交
97abcb3(fix(scene): guard async scene transitions with generation tokens and dispose WebGL textures (#529))中完成修复,并在5de27f8中补充端到端与 136 关全量验证套件。修复与验证摘要
swapLevel/switchArea、switchHero/changeHeroClass、reloadMonsters/respawnMonsters、loadPages及延迟回调引入单调递增的LoadGeneration令牌与AbortSignal守卫,并在过期/淘汰/切幕时立即调用renderer.deleteAtlas(gl.deleteTexture)释放 WebGL 纹理。tests/p0-529-async-lifecycle.test.tsnpm run build0 错误、14 个 P0/E2E 测试套件(190/190 用例)100% 通过、全仓 Vitest 6,460/6,460 通过、136/136 关无头浏览器巡检通过。