收口 P2 前预加载边界并补需求文档
This commit is contained in:
@@ -38,13 +38,15 @@ namespace FlowScope.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_inFlightLoads.TryGetValue(panelType, out inFlightLoad))
|
||||
if (!_inFlightLoads.TryGetValue(panelType, out inFlightLoad) ||
|
||||
inFlightLoad.Invalidated ||
|
||||
inFlightLoad.ReleaseGeneration != _releaseGeneration)
|
||||
{
|
||||
var attribute = UIManager.GetPanelAttribute(panelType);
|
||||
path = UIManager.ResolvePath(panelType, attribute);
|
||||
inFlightLoad = new InFlightPreload();
|
||||
inFlightLoad.ReleaseGeneration = _releaseGeneration;
|
||||
_inFlightLoads.Add(panelType, inFlightLoad);
|
||||
_inFlightLoads[panelType] = inFlightLoad;
|
||||
startLoad = true;
|
||||
}
|
||||
|
||||
@@ -96,7 +98,7 @@ namespace FlowScope.UI
|
||||
public void Release<TPanel>()
|
||||
{
|
||||
var panelType = typeof(TPanel);
|
||||
IResourceHandle<GameObject> handle;
|
||||
IResourceHandle<GameObject> handle = null;
|
||||
lock (_gate)
|
||||
{
|
||||
if (_disposed)
|
||||
@@ -104,15 +106,14 @@ namespace FlowScope.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_handles.TryGetValue(panelType, out handle))
|
||||
InvalidateInFlightLoad(panelType);
|
||||
if (_handles.TryGetValue(panelType, out handle))
|
||||
{
|
||||
return;
|
||||
_handles.Remove(panelType);
|
||||
}
|
||||
|
||||
_handles.Remove(panelType);
|
||||
}
|
||||
|
||||
handle.Dispose();
|
||||
handle?.Dispose();
|
||||
}
|
||||
|
||||
public void ReleaseAll()
|
||||
@@ -164,8 +165,9 @@ namespace FlowScope.UI
|
||||
var shouldCache = false;
|
||||
lock (_gate)
|
||||
{
|
||||
_inFlightLoads.Remove(panelType);
|
||||
ForgetInFlightLoad(panelType, inFlightLoad);
|
||||
if (!_disposed &&
|
||||
!inFlightLoad.Invalidated &&
|
||||
inFlightLoad.ReleaseGeneration == _releaseGeneration &&
|
||||
inFlightLoad.WaiterCount > 0)
|
||||
{
|
||||
@@ -183,13 +185,30 @@ namespace FlowScope.UI
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_inFlightLoads.Remove(panelType);
|
||||
ForgetInFlightLoad(panelType, inFlightLoad);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void ForgetInFlightLoad(Type panelType, InFlightPreload inFlightLoad)
|
||||
{
|
||||
if (_inFlightLoads.TryGetValue(panelType, out var current) &&
|
||||
ReferenceEquals(current, inFlightLoad))
|
||||
{
|
||||
_inFlightLoads.Remove(panelType);
|
||||
}
|
||||
}
|
||||
|
||||
private void InvalidateInFlightLoad(Type panelType)
|
||||
{
|
||||
if (_inFlightLoads.TryGetValue(panelType, out var inFlightLoad))
|
||||
{
|
||||
inFlightLoad.Invalidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ForgetWaitingCaller(Type panelType, InFlightPreload inFlightLoad)
|
||||
{
|
||||
lock (_gate)
|
||||
@@ -249,6 +268,7 @@ namespace FlowScope.UI
|
||||
public Task Task { get; set; }
|
||||
public int WaiterCount { get; set; }
|
||||
public int ReleaseGeneration { get; set; }
|
||||
public bool Invalidated { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user