收口 P2 前预加载边界并补需求文档

This commit is contained in:
JSD\13999
2026-05-21 17:00:16 +08:00
parent 2a88dad6f4
commit 7cde2cfaec
5 changed files with 723 additions and 10 deletions

View File

@@ -173,6 +173,68 @@ namespace FlowScope.Tests.PlayMode.UI
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.False);
}
[UnityTest]
public IEnumerator PreloadAsync_AfterReleaseAllWhileOldLoadInFlight_StartsNewLoadAndCachesNewHandle()
{
var oldPrefab = CreatePrefab<ExplicitPathPanel>("OldExplicitPathPrefab");
var newPrefab = CreatePrefab<ExplicitPathPanel>("NewExplicitPathPrefab");
var resources = new SequencedGatedUITestResources("Custom/Explicit", oldPrefab, newPrefab);
var preload = new UIPreloadService(resources);
var oldTask = preload.PreloadAsync<ExplicitPathPanel>(CancellationToken.None);
Assert.That(resources.LoadCalls, Is.EqualTo(1));
preload.ReleaseAll();
var newTask = preload.PreloadAsync<ExplicitPathPanel>(CancellationToken.None);
Assert.That(resources.LoadCalls, Is.EqualTo(2));
resources.Complete(0);
yield return UITestAsync.Await(oldTask);
Assert.That(resources.HandleAt(0).IsDisposed, Is.True);
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.False);
resources.Complete(1);
yield return UITestAsync.Await(newTask);
Assert.That(resources.HandleAt(1).IsDisposed, Is.False);
Assert.That(preload.TryGetPreloadedHandle<ExplicitPathPanel>(out var handle), Is.True);
Assert.That(handle.Asset, Is.SameAs(newPrefab));
}
[UnityTest]
public IEnumerator OldInFlightCompletion_AfterReleaseAll_DoesNotRemoveNewInFlight()
{
var oldPrefab = CreatePrefab<ExplicitPathPanel>("OldExplicitPathPrefab");
var newPrefab = CreatePrefab<ExplicitPathPanel>("NewExplicitPathPrefab");
var resources = new SequencedGatedUITestResources("Custom/Explicit", oldPrefab, newPrefab);
var preload = new UIPreloadService(resources);
var oldTask = preload.PreloadAsync<ExplicitPathPanel>(CancellationToken.None);
Assert.That(resources.LoadCalls, Is.EqualTo(1));
preload.ReleaseAll();
var newTask = preload.PreloadAsync<ExplicitPathPanel>(CancellationToken.None);
Assert.That(resources.LoadCalls, Is.EqualTo(2));
resources.Complete(0);
yield return UITestAsync.Await(oldTask);
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.False);
var thirdTask = preload.PreloadAsync<ExplicitPathPanel>(CancellationToken.None);
Assert.That(resources.LoadCalls, Is.EqualTo(2));
resources.Complete(1);
yield return UITestAsync.Await(newTask);
yield return UITestAsync.Await(thirdTask);
Assert.That(resources.HandleAt(1).IsDisposed, Is.False);
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.True);
}
[UnityTest]
public IEnumerator Dispose_WhenLoadInFlight_DoesNotCacheAndDisposesLoadedHandle()
{
@@ -244,6 +306,24 @@ namespace FlowScope.Tests.PlayMode.UI
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.False);
}
[UnityTest]
public IEnumerator Release_WhenLoadInFlight_DoesNotCacheAndDisposesLoadedHandle()
{
var prefab = CreatePrefab<ExplicitPathPanel>("ExplicitPathPrefab");
var resources = new GatedUITestResources("Custom/Explicit", prefab);
var preload = new UIPreloadService(resources);
var task = preload.PreloadAsync<ExplicitPathPanel>(CancellationToken.None);
Assert.That(resources.LoadCalls, Is.EqualTo(1));
preload.Release<ExplicitPathPanel>();
resources.Complete();
yield return UITestAsync.Await(task);
Assert.That(resources.Handle.IsDisposed, Is.True);
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.False);
}
private Canvas CreateCanvas(string name)
{
var gameObject = new GameObject(name);
@@ -313,5 +393,64 @@ namespace FlowScope.Tests.PlayMode.UI
return (FlowScope.Resources.IResourceHandle<T>)(object)handle;
}
}
private sealed class SequencedGatedUITestResources : FlowScope.Resources.IResourceService
{
private readonly string _key;
private readonly List<GameObject> _assets = new();
private readonly List<UITestResourceHandle<GameObject>> _handles = new();
private readonly List<TaskCompletionSource<FlowScope.Resources.IResourceHandle<GameObject>>> _gates = new();
public SequencedGatedUITestResources(string key, params GameObject[] assets)
{
_key = key;
foreach (var asset in assets)
{
_assets.Add(asset);
_handles.Add(new UITestResourceHandle<GameObject>(key, asset));
_gates.Add(new TaskCompletionSource<FlowScope.Resources.IResourceHandle<GameObject>>(
TaskCreationOptions.RunContinuationsAsynchronously));
}
}
public int LoadCalls { get; private set; }
public UITestResourceHandle<GameObject> HandleAt(int index)
{
return _handles[index];
}
public Task<FlowScope.Resources.IResourceHandle<T>> LoadAsync<T>(
string key,
CancellationToken cancellationToken)
where T : class
{
cancellationToken.ThrowIfCancellationRequested();
Assert.That(key, Is.EqualTo(_key));
var loadIndex = LoadCalls;
LoadCalls++;
Assert.That(loadIndex, Is.LessThan(_gates.Count), "Unexpected extra resource load.");
return AwaitHandle<T>(loadIndex);
}
public FlowScope.Resources.IResourceGroup CreateGroup()
{
throw new System.NotSupportedException();
}
public void Complete(int index)
{
_gates[index].TrySetResult(_handles[index]);
}
private async Task<FlowScope.Resources.IResourceHandle<T>> AwaitHandle<T>(int index)
where T : class
{
var handle = await _gates[index].Task;
Assert.That(handle.Asset, Is.SameAs(_assets[index]));
return (FlowScope.Resources.IResourceHandle<T>)(object)handle;
}
}
}
}