修正 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 包含 ### 本轮 P1 包含
1. 资源生产化第一步:引入 `IResourceBackend`,将 Addressables 反射适配从 `FlowScope.Runtime` 拆到 `FlowScope.Addressables` 1. 资源生产化第一步:引入泛型 `IResourceBackend`,将 Addressables 适配从 `FlowScope.Runtime` 拆到 `FlowScope.Addressables`,并移除 Addressables 反射调用
2. 配置扩展点:引入 `IConfigSource``IConfigParser`,让 JSON provider 从“硬编码 JSON 文本映射”变成“source + parser”的组合。 2. 配置扩展点:引入 `IConfigSource``IConfigParser`,让 JSON provider 从“硬编码 JSON 文本映射”变成“source + parser”的组合。
3. 存档迁移扩展点:引入 `ISaveMigration` 与带版本包裹的 JSON serializer支持单 key 数据从旧版本迁移到当前版本。 3. 存档迁移扩展点:引入 `ISaveMigration` 与带版本包裹的 JSON serializer支持单 key 数据从旧版本迁移到当前版本。
4. UI 生产化第一步:引入轻量 `IUIScreenNavigator` 与 panel 预加载服务,仍保持 `UIManager` 的层内 LIFO 规则。 4. UI 生产化第一步:引入轻量 `IUIScreenNavigator` 与 panel 预加载服务,仍保持 `UIManager` 的层内 LIFO 规则。
@@ -82,7 +82,6 @@
Create `My project/Assets/FlowScope/Runtime/Resources/IResourceBackend.cs`: Create `My project/Assets/FlowScope/Runtime/Resources/IResourceBackend.cs`:
```csharp ```csharp
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -92,12 +91,13 @@ namespace FlowScope.Resources
{ {
string Name { get; } string Name { get; }
Task<object> LoadAsync( Task<T> LoadAsync<T>(
string key, 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 核心升级风险。 **Goal:** 让核心资源服务只依赖 `IResourceBackend`,将 Addressables 具体依赖放进独立 asmdef降低 Runtime 核心升级风险。
**Owned files:** **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/Runtime/Resources/ResourceService.cs`
- Create: `My project/Assets/FlowScope/Addressables/FlowScope.Addressables.asmdef` - Create: `My project/Assets/FlowScope/Addressables/FlowScope.Addressables.asmdef`
- Create: `My project/Assets/FlowScope/Addressables/AddressablesResourceBackend.cs` - 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/EditMode/Resources/ResourceServiceBackendTests.cs`
- Create: `My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceBackendTests.cs` - Create: `My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceBackendTests.cs`
- Modify: `My project/Assets/FlowScope/Tests/PlayMode/FlowScope.Tests.PlayMode.asmdef` - 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 int ReleaseCalls { get; private set; }
public TaskCompletionSource<object> Gate { get; } = new(); 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++; 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++; ReleaseCalls++;
} }
@@ -343,29 +346,56 @@ Keep P0 semantics:
- Last handle disposal calls backend release. - Last handle disposal calls backend release.
- Canceled waiter does not prevent other waiters from completing. - Canceled waiter does not prevent other waiters from completing.
- If all waiters cancel before backend completes, release completed asset when backend eventually resolves. - 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 ```csharp
public sealed class AddressablesResourceService : IResourceService, ICompletedResourceService using System.Threading;
using System.Threading.Tasks;
namespace FlowScope.Resources
{ {
private readonly ResourceService _inner; public sealed class AddressablesResourceService : IResourceService, ICompletedResourceService
public AddressablesResourceService()
: this(new AddressablesResourceBackend())
{ {
} private readonly ResourceService _inner;
public AddressablesResourceService(IResourceBackend backend) public AddressablesResourceService()
{ : this(new AddressablesResourceBackend())
_inner = new ResourceService(backend); {
}
public AddressablesResourceService(IResourceBackend backend)
{
_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** - [ ] **Step 4: Create Addressables asmdef**
@@ -393,10 +423,9 @@ Create `My project/Assets/FlowScope/Addressables/FlowScope.Addressables.asmdef`:
- [ ] **Step 5: Implement typed Addressables backend** - [ ] **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 ```csharp
using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using FlowScope.Resources; using FlowScope.Resources;
@@ -408,25 +437,19 @@ namespace FlowScope.Addressables
{ {
public string Name => "Addressables"; public string Name => "Addressables";
public async Task<object> LoadAsync(string key, Type assetType, CancellationToken cancellationToken) public async Task<T> LoadAsync<T>(string key, CancellationToken cancellationToken)
{ where T : class
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)
{ {
var handle = Addressables.LoadAssetAsync<T>(key); var handle = Addressables.LoadAssetAsync<T>(key);
await WaitWithCancellation(handle.Task, cancellationToken); await WaitWithCancellation(handle.Task, cancellationToken);
return handle.Result; return handle.Result;
} }
public void Release<T>(string key, T asset)
where T : class
{
Addressables.Release(asset);
}
} }
} }
``` ```