收束 P1 资源后端适配层
This commit is contained in:
@@ -2,7 +2,7 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FlowScope.Resources;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityAddressables = UnityEngine.AddressableAssets.Addressables;
|
||||
|
||||
namespace FlowScope.Addressables
|
||||
{
|
||||
@@ -13,7 +13,7 @@ namespace FlowScope.Addressables
|
||||
public async Task<T> LoadAsync<T>(string key, CancellationToken cancellationToken)
|
||||
where T : class
|
||||
{
|
||||
var handle = Addressables.LoadAssetAsync<T>(key);
|
||||
var handle = UnityAddressables.LoadAssetAsync<T>(key);
|
||||
await WaitWithCancellation(handle.Task, cancellationToken);
|
||||
return handle.Result;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace FlowScope.Addressables
|
||||
public void Release<T>(string key, T asset)
|
||||
where T : class
|
||||
{
|
||||
Addressables.Release(asset);
|
||||
UnityAddressables.Release(asset);
|
||||
}
|
||||
|
||||
private static async Task WaitWithCancellation(Task task, CancellationToken cancellationToken)
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"rootNamespace": "FlowScope.Addressables",
|
||||
"references": [
|
||||
"FlowScope.Runtime",
|
||||
"Unity.Addressables"
|
||||
"Unity.Addressables",
|
||||
"Unity.ResourceManager"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FlowScope.Resources;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace FlowScope.Tests.EditMode.Resources
|
||||
{
|
||||
public sealed class ResourceServiceBackendTests
|
||||
{
|
||||
[Test]
|
||||
public async Task LoadAsync_WhenSameKeyLoadsConcurrently_UsesOneBackendLoad()
|
||||
[UnityTest]
|
||||
public IEnumerator LoadAsync_WhenSameKeyLoadsConcurrently_UsesOneBackendLoad()
|
||||
{
|
||||
var backend = new FakeResourceBackend();
|
||||
var service = new ResourceService(backend);
|
||||
@@ -18,15 +21,17 @@ namespace FlowScope.Tests.EditMode.Resources
|
||||
var second = service.LoadAsync<TestAsset>("shared", CancellationToken.None);
|
||||
backend.Gate.SetResult(new TestAsset());
|
||||
|
||||
using var firstHandle = await first;
|
||||
using var secondHandle = await second;
|
||||
yield return Await(first);
|
||||
yield return Await(second);
|
||||
using var firstHandle = Result(first);
|
||||
using var secondHandle = Result(second);
|
||||
|
||||
Assert.AreEqual(1, backend.LoadCalls);
|
||||
Assert.AreSame(firstHandle.Asset, secondHandle.Asset);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoadAsync_WhenCanceled_DoesNotCreateHandle()
|
||||
[UnityTest]
|
||||
public IEnumerator LoadAsync_WhenCanceled_DoesNotCreateHandle()
|
||||
{
|
||||
var backend = new FakeResourceBackend();
|
||||
var service = new ResourceService(backend);
|
||||
@@ -35,13 +40,20 @@ namespace FlowScope.Tests.EditMode.Resources
|
||||
var task = service.LoadAsync<TestAsset>("canceled", cts.Token);
|
||||
cts.Cancel();
|
||||
|
||||
await ExpectThrowsAsync<OperationCanceledException>(task);
|
||||
Assert.IsFalse(service.TryLoadCompleted<TestAsset>("canceled", out var handle));
|
||||
Assert.IsNull(handle);
|
||||
try
|
||||
{
|
||||
yield return AwaitExpected<OperationCanceledException>(task);
|
||||
Assert.IsFalse(service.TryLoadCompleted<TestAsset>("canceled", out var handle));
|
||||
Assert.IsNull(handle);
|
||||
}
|
||||
finally
|
||||
{
|
||||
backend.Gate.TrySetResult(new TestAsset());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DisposeLastHandle_ReleasesBackendAsset()
|
||||
[UnityTest]
|
||||
public IEnumerator DisposeLastHandle_ReleasesBackendAsset()
|
||||
{
|
||||
var backend = new FakeResourceBackend();
|
||||
var service = new ResourceService(backend);
|
||||
@@ -49,8 +61,10 @@ namespace FlowScope.Tests.EditMode.Resources
|
||||
var first = service.LoadAsync<TestAsset>("shared", CancellationToken.None);
|
||||
var second = service.LoadAsync<TestAsset>("shared", CancellationToken.None);
|
||||
backend.Gate.SetResult(new TestAsset());
|
||||
var firstHandle = await first;
|
||||
var secondHandle = await second;
|
||||
yield return Await(first);
|
||||
yield return Await(second);
|
||||
var firstHandle = Result(first);
|
||||
var secondHandle = Result(second);
|
||||
|
||||
firstHandle.Dispose();
|
||||
Assert.AreEqual(0, backend.ReleaseCalls);
|
||||
@@ -59,15 +73,16 @@ namespace FlowScope.Tests.EditMode.Resources
|
||||
Assert.AreEqual(1, backend.ReleaseCalls);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DisposeLastHandle_RemovesCompletedCacheEntry()
|
||||
[UnityTest]
|
||||
public IEnumerator DisposeLastHandle_RemovesCompletedCacheEntry()
|
||||
{
|
||||
var backend = new FakeResourceBackend();
|
||||
var service = new ResourceService(backend);
|
||||
|
||||
var load = service.LoadAsync<TestAsset>("cached", CancellationToken.None);
|
||||
backend.Gate.SetResult(new TestAsset());
|
||||
var handle = await load;
|
||||
yield return Await(load);
|
||||
var handle = Result(load);
|
||||
|
||||
Assert.IsTrue(service.TryLoadCompleted<TestAsset>("cached", out var cached));
|
||||
cached.Dispose();
|
||||
@@ -76,8 +91,8 @@ namespace FlowScope.Tests.EditMode.Resources
|
||||
Assert.IsFalse(service.TryLoadCompleted<TestAsset>("cached", out _));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateGroup_DisposesAllHandles()
|
||||
[UnityTest]
|
||||
public IEnumerator CreateGroup_DisposesAllHandles()
|
||||
{
|
||||
var backend = new FakeResourceBackend();
|
||||
var service = new ResourceService(backend);
|
||||
@@ -85,13 +100,15 @@ namespace FlowScope.Tests.EditMode.Resources
|
||||
|
||||
var first = service.LoadAsync<TestAsset>("first", CancellationToken.None);
|
||||
backend.Gate.SetResult(new TestAsset());
|
||||
var firstHandle = await first;
|
||||
yield return Await(first);
|
||||
var firstHandle = Result(first);
|
||||
group.Add(firstHandle);
|
||||
|
||||
backend.Gate = new TaskCompletionSource<object>();
|
||||
backend.ResetGate();
|
||||
var second = service.LoadAsync<TestAsset>("second", CancellationToken.None);
|
||||
backend.Gate.SetResult(new TestAsset());
|
||||
var secondHandle = await second;
|
||||
yield return Await(second);
|
||||
var secondHandle = Result(second);
|
||||
group.Add(secondHandle);
|
||||
|
||||
group.Dispose();
|
||||
@@ -106,7 +123,13 @@ namespace FlowScope.Tests.EditMode.Resources
|
||||
public string Name => "Fake";
|
||||
public int LoadCalls { get; private set; }
|
||||
public int ReleaseCalls { get; private set; }
|
||||
public TaskCompletionSource<object> Gate { get; set; } = new();
|
||||
public TaskCompletionSource<object> Gate { get; private set; } =
|
||||
new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
public void ResetGate()
|
||||
{
|
||||
Gate = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
|
||||
public async Task<T> LoadAsync<T>(string key, CancellationToken cancellationToken)
|
||||
where T : class
|
||||
@@ -126,19 +149,72 @@ namespace FlowScope.Tests.EditMode.Resources
|
||||
{
|
||||
}
|
||||
|
||||
private static async Task ExpectThrowsAsync<TException>(Task task)
|
||||
private static IEnumerator Await(Task task)
|
||||
{
|
||||
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(2);
|
||||
while (!task.IsCompleted)
|
||||
{
|
||||
if (DateTime.UtcNow > deadline)
|
||||
{
|
||||
Assert.Fail("Async operation did not complete within 2 seconds.");
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
ThrowIfFailed(task);
|
||||
}
|
||||
|
||||
private static IEnumerator AwaitExpected<TException>(Task task)
|
||||
where TException : Exception
|
||||
{
|
||||
try
|
||||
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(2);
|
||||
while (!task.IsCompleted)
|
||||
{
|
||||
await task;
|
||||
if (DateTime.UtcNow > deadline)
|
||||
{
|
||||
Assert.Fail("Async operation did not complete within 2 seconds.");
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
catch (TException)
|
||||
|
||||
if (task.IsCanceled && typeof(TException) == typeof(OperationCanceledException))
|
||||
{
|
||||
return;
|
||||
yield break;
|
||||
}
|
||||
|
||||
var exception = task.Exception?.InnerException;
|
||||
if (exception is TException)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (exception != null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(exception).Throw();
|
||||
}
|
||||
|
||||
Assert.Fail($"Expected {typeof(TException).Name}.");
|
||||
}
|
||||
|
||||
private static T Result<T>(Task<T> task)
|
||||
{
|
||||
ThrowIfFailed(task);
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
private static void ThrowIfFailed(Task task)
|
||||
{
|
||||
if (task.IsCanceled)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
if (task.Exception?.InnerException != null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(task.Exception.InnerException).Throw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,20 @@
|
||||
},
|
||||
"hash": "329ca7915713417f2e0837b0e0a80b4da074db4a"
|
||||
},
|
||||
"com.unity.addressables": {
|
||||
"version": "1.21.21",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.scriptablebuildpipeline": "1.21.23",
|
||||
"com.unity.modules.assetbundle": "1.0.0",
|
||||
"com.unity.modules.imageconversion": "1.0.0",
|
||||
"com.unity.modules.jsonserialize": "1.0.0",
|
||||
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||
"com.unity.modules.unitywebrequestassetbundle": "1.0.0"
|
||||
},
|
||||
"url": "https://packages.unity.cn"
|
||||
},
|
||||
"com.unity.collab-proxy": {
|
||||
"version": "2.12.4",
|
||||
"depth": 0,
|
||||
@@ -76,6 +90,13 @@
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.cn"
|
||||
},
|
||||
"com.unity.scriptablebuildpipeline": {
|
||||
"version": "1.21.25",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.cn"
|
||||
},
|
||||
"com.unity.settings-manager": {
|
||||
"version": "2.1.0",
|
||||
"depth": 2,
|
||||
|
||||
Reference in New Issue
Block a user