修正 P1 资源后端泛型契约

This commit is contained in:
JSD\13999
2026-05-20 15:28:27 +08:00
parent 6da0a7822c
commit f855c34bf9

View File

@@ -14,7 +14,7 @@
### 本轮 P1 包含
1. 资源生产化第一步:引入 `IResourceBackend`,将 Addressables 反射适配从 `FlowScope.Runtime` 拆到 `FlowScope.Addressables`
1. 资源生产化第一步:引入泛型 `IResourceBackend`,将 Addressables 适配从 `FlowScope.Runtime` 拆到 `FlowScope.Addressables`,并移除 Addressables 反射调用
2. 配置扩展点:引入 `IConfigSource``IConfigParser`,让 JSON provider 从“硬编码 JSON 文本映射”变成“source + parser”的组合。
3. 存档迁移扩展点:引入 `ISaveMigration` 与带版本包裹的 JSON serializer支持单 key 数据从旧版本迁移到当前版本。
4. UI 生产化第一步:引入轻量 `IUIScreenNavigator` 与 panel 预加载服务,仍保持 `UIManager` 的层内 LIFO 规则。
@@ -82,7 +82,6 @@
Create `My project/Assets/FlowScope/Runtime/Resources/IResourceBackend.cs`:
```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -92,12 +91,13 @@ namespace FlowScope.Resources
{
string Name { get; }
Task<object> LoadAsync(
Task<T> LoadAsync<T>(
string key,
Type assetType,
CancellationToken cancellationToken);
CancellationToken cancellationToken)
where T : class;
void Release(string key, Type assetType, object asset);
void Release<T>(string key, T asset)
where T : class;
}
}
```
@@ -269,10 +269,11 @@ git commit -m "定义 Game Core P1 生产化契约"
**Goal:** 让核心资源服务只依赖 `IResourceBackend`,将 Addressables 具体依赖放进独立 asmdef降低 Runtime 核心升级风险。
**Owned files:**
- Modify: `My project/Assets/FlowScope/Runtime/Resources/AddressablesResourceService.cs`
- Delete: `My project/Assets/FlowScope/Runtime/Resources/AddressablesResourceService.cs`
- Create: `My project/Assets/FlowScope/Runtime/Resources/ResourceService.cs`
- Create: `My project/Assets/FlowScope/Addressables/FlowScope.Addressables.asmdef`
- Create: `My project/Assets/FlowScope/Addressables/AddressablesResourceBackend.cs`
- Create: `My project/Assets/FlowScope/Addressables/AddressablesResourceService.cs`
- Create: `My project/Assets/FlowScope/Tests/EditMode/Resources/ResourceServiceBackendTests.cs`
- Create: `My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceBackendTests.cs`
- Modify: `My project/Assets/FlowScope/Tests/PlayMode/FlowScope.Tests.PlayMode.asmdef`
@@ -309,13 +310,15 @@ private sealed class FakeResourceBackend : IResourceBackend
public int ReleaseCalls { get; private set; }
public TaskCompletionSource<object> Gate { get; } = new();
public async Task<object> LoadAsync(string key, Type assetType, CancellationToken cancellationToken)
public async Task<T> LoadAsync<T>(string key, CancellationToken cancellationToken)
where T : class
{
LoadCalls++;
return await Gate.Task;
return (T)await Gate.Task;
}
public void Release(string key, Type assetType, object asset)
public void Release<T>(string key, T asset)
where T : class
{
ReleaseCalls++;
}
@@ -343,12 +346,30 @@ Keep P0 semantics:
- Last handle disposal calls backend release.
- Canceled waiter does not prevent other waiters from completing.
- If all waiters cancel before backend completes, release completed asset when backend eventually resolves.
- `ResourceService` must call `IResourceBackend.LoadAsync<T>` directly. Do not use reflection to bridge `Type` to generic calls.
- [ ] **Step 3: Convert `AddressablesResourceService` into compatibility wrapper**
- [ ] **Step 3: Move `AddressablesResourceService` compatibility wrapper to adapter assembly**
Modify `AddressablesResourceService` to preserve existing API:
Delete or empty the old Runtime implementation file:
```text
My project/Assets/FlowScope/Runtime/Resources/AddressablesResourceService.cs
```
Create a compatibility wrapper in the Addressables adapter assembly:
```text
My project/Assets/FlowScope/Addressables/AddressablesResourceService.cs
```
Use namespace `FlowScope.Resources` to preserve existing call sites:
```csharp
using System.Threading;
using System.Threading.Tasks;
namespace FlowScope.Resources
{
public sealed class AddressablesResourceService : IResourceService, ICompletedResourceService
{
private readonly ResourceService _inner;
@@ -362,10 +383,19 @@ public sealed class AddressablesResourceService : IResourceService, ICompletedRe
{
_inner = new ResourceService(backend);
}
public Task<IResourceHandle<T>> LoadAsync<T>(string key, CancellationToken cancellationToken)
where T : class => _inner.LoadAsync<T>(key, cancellationToken);
public IResourceGroup CreateGroup() => _inner.CreateGroup();
public bool TryLoadCompleted<T>(string key, out IResourceHandle<T> handle)
where T : class => _inner.TryLoadCompleted(key, out handle);
}
}
```
If `AddressablesResourceBackend` lives in a separate asmdef and cannot be referenced by Runtime, keep this wrapper in `FlowScope.Addressables` instead and update usages in sample/integration. Do not keep reflection backend in `FlowScope.Runtime`.
Do not keep an Addressables reflection backend in `FlowScope.Runtime`.
- [ ] **Step 4: Create Addressables asmdef**
@@ -393,10 +423,9 @@ Create `My project/Assets/FlowScope/Addressables/FlowScope.Addressables.asmdef`:
- [ ] **Step 5: Implement typed Addressables backend**
Create `AddressablesResourceBackend` using direct API references:
Create `AddressablesResourceBackend` using direct typed Addressables API references. This backend must not use reflection:
```csharp
using System;
using System.Threading;
using System.Threading.Tasks;
using FlowScope.Resources;
@@ -408,25 +437,19 @@ namespace FlowScope.Addressables
{
public string Name => "Addressables";
public async Task<object> LoadAsync(string key, Type assetType, CancellationToken cancellationToken)
{
var method = typeof(AddressablesResourceBackend)
.GetMethod(nameof(LoadTypedAsync), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
.MakeGenericMethod(assetType);
return await (Task<object>)method.Invoke(null, new object[] { key, cancellationToken });
}
public void Release(string key, Type assetType, object asset)
{
Addressables.Release(asset);
}
private static async Task<object> LoadTypedAsync<T>(string key, CancellationToken cancellationToken)
public async Task<T> LoadAsync<T>(string key, CancellationToken cancellationToken)
where T : class
{
var handle = Addressables.LoadAssetAsync<T>(key);
await WaitWithCancellation(handle.Task, cancellationToken);
return handle.Result;
}
public void Release<T>(string key, T asset)
where T : class
{
Addressables.Release(asset);
}
}
}
```