185 lines
6.4 KiB
Markdown
185 lines
6.4 KiB
Markdown
# FlowScope P2 前第五轮复审意见
|
||
|
||
日期:2026-05-21
|
||
审查对象:当前工作树中 `UIPreloadService` 与 `UIPreloadServiceTests` 的未提交修改
|
||
审查范围:`ReleaseAll()` 后新旧 in-flight preload 隔离、旧任务完成清理、相关 PlayMode 测试、generated csproj 编译状态。
|
||
审查结论:第四轮指出的 P1 gate 已收口;当前没有发现新的 P2 阻塞级问题。剩余主要是 `Release<TPanel>()` 与 `ReleaseAll()` 的 in-flight 语义不完全一致,可作为 P2 前小修或 P2 内 API polish。
|
||
|
||
---
|
||
|
||
## 本轮确认已收口
|
||
|
||
### 1. `ReleaseAll()` 后旧 in-flight 与新 preload 已隔离
|
||
|
||
相关文件:
|
||
|
||
- `My project/Assets/FlowScope/Runtime/UI/UIPreloadService.cs`
|
||
- `My project/Assets/FlowScope/Tests/PlayMode/UI/UIPreloadServiceTests.cs`
|
||
|
||
当前 `PreloadAsync` 在发现已有 in-flight 但 generation 过期时,会创建新的 `InFlightPreload`:
|
||
|
||
```csharp
|
||
if (!_inFlightLoads.TryGetValue(panelType, out inFlightLoad) ||
|
||
inFlightLoad.ReleaseGeneration != _releaseGeneration)
|
||
{
|
||
var attribute = UIManager.GetPanelAttribute(panelType);
|
||
path = UIManager.ResolvePath(panelType, attribute);
|
||
inFlightLoad = new InFlightPreload();
|
||
inFlightLoad.ReleaseGeneration = _releaseGeneration;
|
||
_inFlightLoads[panelType] = inFlightLoad;
|
||
startLoad = true;
|
||
}
|
||
```
|
||
|
||
评价:上一轮指出的“ReleaseAll 后、旧 load 完成前再次 preload 同一 panel 会复用旧任务并最终不缓存”的问题已经修正。
|
||
|
||
---
|
||
|
||
### 2. 旧 in-flight 完成时不会误删新 in-flight
|
||
|
||
当前 `LoadAndStoreAsync` 不再直接 `_inFlightLoads.Remove(panelType)`,而是只移除当前任务对应的 in-flight:
|
||
|
||
```csharp
|
||
private void ForgetInFlightLoad(Type panelType, InFlightPreload inFlightLoad)
|
||
{
|
||
if (_inFlightLoads.TryGetValue(panelType, out var current) &&
|
||
ReferenceEquals(current, inFlightLoad))
|
||
{
|
||
_inFlightLoads.Remove(panelType);
|
||
}
|
||
}
|
||
```
|
||
|
||
评价:这个修正是关键的。它避免旧任务完成或失败时把 release 后新建的 in-flight 条目删掉。
|
||
|
||
---
|
||
|
||
### 3. 新增测试覆盖了上一轮缺口
|
||
|
||
新增测试:
|
||
|
||
- `PreloadAsync_AfterReleaseAllWhileOldLoadInFlight_StartsNewLoadAndCachesNewHandle`
|
||
- `OldInFlightCompletion_AfterReleaseAll_DoesNotRemoveNewInFlight`
|
||
|
||
覆盖的真实边界:
|
||
|
||
1. 旧 preload 正在加载。
|
||
2. 调用 `ReleaseAll()`。
|
||
3. 立刻再次 preload 同一 panel。
|
||
4. 新 preload 必须触发新的资源加载。
|
||
5. 旧任务完成后只能释放旧 handle,不能污染或删除新任务。
|
||
6. 新任务完成后应缓存新 handle。
|
||
|
||
评价:这两条测试正好覆盖第四轮指出的 P1 风险。
|
||
|
||
---
|
||
|
||
## 当前未发现的 P2 阻塞项
|
||
|
||
从本轮审查范围看,以下上一轮 gate 已经闭环:
|
||
|
||
- `ShutdownAsync` best-effort cleanup。
|
||
- `UIPreloadService` 首个 caller cancellation 不再污染其他 waiter。
|
||
- `ReleaseAll/Dispose` 后旧异步任务不再写回缓存。
|
||
- `ReleaseAll` 后重新 preload 同一 panel 不再复用旧 generation 任务。
|
||
- 旧任务完成不会误删新 in-flight。
|
||
|
||
因此,如果 P2 的第一项不是“正式发布资源后端/Addressables 集成质量”,当前 Game Core 主线可以进入 P2。
|
||
|
||
---
|
||
|
||
## 中风险改进
|
||
|
||
### `Release<TPanel>()` 对 in-flight preload 仍不生效
|
||
|
||
风险等级:中
|
||
建议:P2 前可小修;若 P2 首项不扩 UI preload,可放入 P2 内 polish。
|
||
|
||
证据文件:
|
||
|
||
- `My project/Assets/FlowScope/Runtime/UI/UIPreloadService.cs`
|
||
|
||
当前 `Release<TPanel>()` 只处理已经缓存到 `_handles` 的资源:
|
||
|
||
```csharp
|
||
if (!_handles.TryGetValue(panelType, out handle))
|
||
{
|
||
return;
|
||
}
|
||
|
||
_handles.Remove(panelType);
|
||
```
|
||
|
||
它不会处理同 panel 的 in-flight preload。结果是:
|
||
|
||
1. `PreloadAsync<TPanel>()` 已启动但尚未完成。
|
||
2. 调用 `Release<TPanel>()`。
|
||
3. 因为 `_handles` 里还没有缓存,`Release<TPanel>()` 直接 return。
|
||
4. 旧 preload 完成后仍会缓存 handle。
|
||
|
||
这与当前 `ReleaseAll()` 的语义不完全一致:`ReleaseAll()` 已经通过 `_releaseGeneration++` 明确让所有旧 in-flight 结果失效,而 `Release<TPanel>()` 仍只释放已缓存结果。
|
||
|
||
建议选一种语义并写清楚:
|
||
|
||
1. 如果 `Release<TPanel>()` 表示“释放该 panel 的预加载结果”,则应让该 panel 当前 in-flight 失效。
|
||
2. 如果 `Release<TPanel>()` 只表示“释放已缓存 handle,不影响正在进行的 preload”,则建议在 runtime usage guide 明确说明,避免使用者误以为它会取消或丢弃 in-flight 结果。
|
||
|
||
更一致的实现方向:
|
||
|
||
- 给 `InFlightPreload` 增加 panel-level generation 或 invalidation flag。
|
||
- `Release<TPanel>()` 在释放 `_handles` 后,同时让当前 panel 的旧 in-flight 结果不能缓存。
|
||
- 补测试:`Release_WhenLoadInFlight_DoesNotCacheAndDisposesLoadedHandle`。
|
||
|
||
---
|
||
|
||
## 低风险改进
|
||
|
||
### Addressables 真实 load/release 验证仍薄
|
||
|
||
当前 `AddressablesResourceBackendTests` 仍主要覆盖 `Name_ReturnsAddressables`。
|
||
这不是本轮 UI preload 修复引入的问题,但仍是包分发或资源后端推广前的验收缺口。
|
||
|
||
建议:
|
||
|
||
- 如果 P2 首项是 package/editor tooling,可放到 P2 内第一批验收。
|
||
- 如果 P2 首项是资源后端能力发布,则建议先补最小 Addressables PlayMode 集成或人工验收记录。
|
||
|
||
---
|
||
|
||
## 本轮验证
|
||
|
||
执行:
|
||
|
||
```powershell
|
||
dotnet build "My project\FlowScope.Tests.EditMode.csproj" --no-restore
|
||
dotnet build "My project\FlowScope.Tests.PlayMode.csproj" --no-restore
|
||
```
|
||
|
||
结果:
|
||
|
||
- EditMode generated csproj build:0 error。
|
||
- PlayMode generated csproj build:0 error。
|
||
- 仍有 Unity generated csproj 常见引用冲突 warning。
|
||
|
||
说明:
|
||
|
||
- 本轮未执行 Unity Test Runner。
|
||
- 本轮未做 MainMenuP0 人工场景验收。
|
||
- 本轮审查对象包含未提交工作树修改,不是新 commit。
|
||
|
||
---
|
||
|
||
## P2 Gate 判断
|
||
|
||
当前判断:可以准备进入 P2,但建议先做一次收尾确认。
|
||
|
||
P2 前建议最小动作:
|
||
|
||
1. 决定 `Release<TPanel>()` 是否需要与 `ReleaseAll()` 一样使同 panel in-flight 失效。
|
||
2. 如果需要,补一个很小的测试和实现。
|
||
3. 如果暂不处理,在 runtime usage guide 记录 `Release<TPanel>()` 当前只释放已缓存 handle。
|
||
4. 跑一次 Unity Test Runner 或记录无法执行原因。
|
||
|
||
如果你希望严格一点,我建议先修 `Release<TPanel>()` 的 in-flight 语义,再正式开 P2;如果按当前 P2 迭代节奏推进,它已经不是阻塞级问题。
|
||
|