修复预加载释放中的异步写回
This commit is contained in:
@@ -13,6 +13,8 @@ namespace FlowScope.UI
|
||||
private readonly IResourceService _resources;
|
||||
private readonly Dictionary<Type, IResourceHandle<GameObject>> _handles = new();
|
||||
private readonly Dictionary<Type, InFlightPreload> _inFlightLoads = new();
|
||||
private int _releaseGeneration;
|
||||
private bool _disposed;
|
||||
|
||||
public UIPreloadService(IResourceService resources)
|
||||
{
|
||||
@@ -29,6 +31,8 @@ namespace FlowScope.UI
|
||||
string path = null;
|
||||
lock (_gate)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
if (TryGetPreloadedHandle(panelType, out _))
|
||||
{
|
||||
return;
|
||||
@@ -39,6 +43,7 @@ namespace FlowScope.UI
|
||||
var attribute = UIManager.GetPanelAttribute(panelType);
|
||||
path = UIManager.ResolvePath(panelType, attribute);
|
||||
inFlightLoad = new InFlightPreload();
|
||||
inFlightLoad.ReleaseGeneration = _releaseGeneration;
|
||||
_inFlightLoads.Add(panelType, inFlightLoad);
|
||||
startLoad = true;
|
||||
}
|
||||
@@ -65,6 +70,11 @@ namespace FlowScope.UI
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetPreloadedHandle(typeof(TPanel), out _);
|
||||
}
|
||||
}
|
||||
@@ -73,6 +83,12 @@ namespace FlowScope.UI
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
handle = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryGetPreloadedHandle(typeof(TPanel), out handle);
|
||||
}
|
||||
}
|
||||
@@ -83,6 +99,11 @@ namespace FlowScope.UI
|
||||
IResourceHandle<GameObject> handle;
|
||||
lock (_gate)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_handles.TryGetValue(panelType, out handle))
|
||||
{
|
||||
return;
|
||||
@@ -99,6 +120,7 @@ namespace FlowScope.UI
|
||||
List<IResourceHandle<GameObject>> handles;
|
||||
lock (_gate)
|
||||
{
|
||||
_releaseGeneration++;
|
||||
handles = new List<IResourceHandle<GameObject>>(_handles.Values);
|
||||
_handles.Clear();
|
||||
}
|
||||
@@ -111,6 +133,16 @@ namespace FlowScope.UI
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
ReleaseAll();
|
||||
}
|
||||
|
||||
@@ -133,7 +165,9 @@ namespace FlowScope.UI
|
||||
lock (_gate)
|
||||
{
|
||||
_inFlightLoads.Remove(panelType);
|
||||
if (inFlightLoad.WaiterCount > 0)
|
||||
if (!_disposed &&
|
||||
inFlightLoad.ReleaseGeneration == _releaseGeneration &&
|
||||
inFlightLoad.WaiterCount > 0)
|
||||
{
|
||||
_handles[panelType] = handle;
|
||||
shouldCache = true;
|
||||
@@ -179,6 +213,14 @@ namespace FlowScope.UI
|
||||
handle.Asset != null;
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(UIPreloadService));
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WaitForSharedLoadAsync(
|
||||
Task loadTask,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -206,6 +248,7 @@ namespace FlowScope.UI
|
||||
{
|
||||
public Task Task { get; set; }
|
||||
public int WaiterCount { get; set; }
|
||||
public int ReleaseGeneration { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace FlowScope.Samples.MainMenuP0
|
||||
private bool _shutdown;
|
||||
|
||||
public int ReleasedResourceCount => _sampleBackend?.ReleaseCount ?? 0;
|
||||
/// <summary>
|
||||
/// True after runtime cleanup has completed; shutdown sub-step failures are still reported by ShutdownAsync.
|
||||
/// </summary>
|
||||
public bool IsShutdown => _shutdown;
|
||||
public Task StartupTask { get; private set; }
|
||||
|
||||
|
||||
@@ -155,6 +155,57 @@ namespace FlowScope.Tests.PlayMode.UI
|
||||
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.False);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator ReleaseAll_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.ReleaseAll();
|
||||
resources.Complete();
|
||||
yield return UITestAsync.Await(task);
|
||||
|
||||
Assert.That(resources.Handle.IsDisposed, Is.True);
|
||||
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.False);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator Dispose_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.Dispose();
|
||||
resources.Complete();
|
||||
yield return UITestAsync.Await(task);
|
||||
|
||||
Assert.That(resources.Handle.IsDisposed, Is.True);
|
||||
Assert.That(preload.IsPreloaded<ExplicitPathPanel>(), Is.False);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator PreloadAsync_AfterDispose_ThrowsObjectDisposedException()
|
||||
{
|
||||
var prefab = CreatePrefab<ExplicitPathPanel>("ExplicitPathPrefab");
|
||||
var resources = new UITestResources();
|
||||
resources.Register("Custom/Explicit", prefab);
|
||||
var preload = new UIPreloadService(resources);
|
||||
|
||||
preload.Dispose();
|
||||
|
||||
yield return UITestAsync.AwaitExpected<ObjectDisposedException>(
|
||||
preload.PreloadAsync<ExplicitPathPanel>(CancellationToken.None));
|
||||
Assert.That(resources.LoadedKeys, Is.Empty);
|
||||
}
|
||||
|
||||
[UnityTest]
|
||||
public IEnumerator OpenAsync_WhenPanelIsPreloaded_ReusesPreloadedHandle()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user