Better tex load cancellation handling

This commit is contained in:
Soreepeong 2024-02-22 16:32:58 +09:00
parent 517abb0c71
commit ba51ec52f5
3 changed files with 70 additions and 65 deletions

View file

@ -148,6 +148,7 @@ internal abstract class SharableTexture : IRefCountable, TextureLoadThrottler.IT
continue; continue;
} }
this.cancellationTokenSource?.Cancel();
this.cancellationTokenSource = null; this.cancellationTokenSource = null;
this.ReleaseResources(); this.ReleaseResources();
this.resourceReleased = true; this.resourceReleased = true;

View file

@ -11,7 +11,8 @@ namespace Dalamud.Interface.Internal.SharableTextures;
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class TextureLoadThrottler : IServiceType internal class TextureLoadThrottler : IServiceType
{ {
private readonly List<WorkItem> workList = new(); private readonly object workListLock = new();
private readonly List<WorkItem> pendingWorkList = new();
private readonly List<WorkItem> activeWorkList = new(); private readonly List<WorkItem> activeWorkList = new();
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
@ -61,78 +62,82 @@ internal class TextureLoadThrottler : IServiceType
ImmediateLoadFunction = immediateLoadFunction, ImmediateLoadFunction = immediateLoadFunction,
}; };
_ = Task.Run( _ = Task.Run(() => this.ContinueWork(work), default);
() =>
{
lock (this.workList)
{
this.workList.Add(work);
if (this.activeWorkList.Count >= this.MaxActiveWorkItems)
return;
}
this.ContinueWork();
},
default);
return work.TaskCompletionSource.Task; return work.TaskCompletionSource.Task;
} }
private void ContinueWork() private async Task ContinueWork(WorkItem? newItem)
{ {
WorkItem minWork; while (true)
lock (this.workList)
{ {
if (this.workList.Count == 0) WorkItem? minWork = null;
return; lock (this.workListLock)
if (this.activeWorkList.Count >= this.MaxActiveWorkItems)
return;
var minIndex = 0;
for (var i = 1; i < this.workList.Count; i++)
{ {
if (this.workList[i].CompareTo(this.workList[minIndex]) < 0) if (newItem is not null)
minIndex = i; {
this.pendingWorkList.Add(newItem);
newItem = null;
}
if (this.activeWorkList.Count >= this.MaxActiveWorkItems)
return;
var minIndex = -1;
for (var i = 0; i < this.pendingWorkList.Count; i++)
{
var work = this.pendingWorkList[i];
if (work.CancellationToken.IsCancellationRequested)
{
work.TaskCompletionSource.SetCanceled(work.CancellationToken);
this.RelocatePendingWorkItemToEndAndEraseUnsafe(i--);
continue;
}
if (minIndex == -1 || work.CompareTo(this.pendingWorkList[minIndex]) < 0)
{
minIndex = i;
minWork = work;
}
}
if (minWork is null)
return;
this.RelocatePendingWorkItemToEndAndEraseUnsafe(minIndex);
this.activeWorkList.Add(minWork);
} }
minWork = this.workList[minIndex]; try
// Avoid shifting; relocate the element to remove to the last
if (minIndex != this.workList.Count - 1)
(this.workList[^1], this.workList[minIndex]) = (this.workList[minIndex], this.workList[^1]);
this.workList.RemoveAt(this.workList.Count - 1);
this.activeWorkList.Add(minWork);
}
try
{
minWork.CancellationToken.ThrowIfCancellationRequested();
minWork.InnerTask = minWork.ImmediateLoadFunction(minWork.CancellationToken);
}
catch (Exception e)
{
minWork.InnerTask = Task.FromException<IDalamudTextureWrap>(e);
}
minWork.InnerTask.ContinueWith(
r =>
{ {
// Swallow exception, if any var r = await minWork.ImmediateLoadFunction(minWork.CancellationToken);
_ = r.Exception; minWork.TaskCompletionSource.SetResult(r);
}
catch (Exception e)
{
minWork.TaskCompletionSource.SetException(e);
}
lock (this.workList) lock (this.workListLock)
this.activeWorkList.Remove(minWork); this.activeWorkList.Remove(minWork);
if (r.IsCompletedSuccessfully) }
minWork.TaskCompletionSource.SetResult(r.Result); }
else if (r.Exception is not null)
minWork.TaskCompletionSource.SetException(r.Exception); /// <summary>
else if (r.IsCanceled) /// Remove an item in <see cref="pendingWorkList"/>, avoiding shifting.
minWork.TaskCompletionSource.SetCanceled(); /// </summary>
else /// <param name="index">Index of the item to remove.</param>
minWork.TaskCompletionSource.SetException(new Exception("??")); private void RelocatePendingWorkItemToEndAndEraseUnsafe(int index)
this.ContinueWork(); {
}); // Relocate the element to remove to the last.
if (index != this.pendingWorkList.Count - 1)
{
(this.pendingWorkList[^1], this.pendingWorkList[index]) =
(this.pendingWorkList[index], this.pendingWorkList[^1]);
}
this.pendingWorkList.RemoveAt(this.pendingWorkList.Count - 1);
} }
/// <summary> /// <summary>
@ -164,8 +169,6 @@ internal class TextureLoadThrottler : IServiceType
public required Func<CancellationToken, Task<IDalamudTextureWrap>> ImmediateLoadFunction { get; init; } public required Func<CancellationToken, Task<IDalamudTextureWrap>> ImmediateLoadFunction { get; init; }
public Task<IDalamudTextureWrap>? InnerTask { get; set; }
public int CompareTo(WorkItem other) public int CompareTo(WorkItem other)
{ {
if (this.Basis.IsOpportunistic != other.Basis.IsOpportunistic) if (this.Basis.IsOpportunistic != other.Basis.IsOpportunistic)

View file

@ -36,8 +36,9 @@ internal class TaskTracker : IDisposable, IServiceType
/// <summary> /// <summary>
/// Gets a read-only list of tracked tasks. /// Gets a read-only list of tracked tasks.
/// Intended for use only from UI thread.
/// </summary> /// </summary>
public static IReadOnlyList<TaskInfo> Tasks => TrackedTasksInternal.ToArray(); public static IReadOnlyList<TaskInfo> Tasks => TrackedTasksInternal;
/// <summary> /// <summary>
/// Clear the list of tracked tasks. /// Clear the list of tracked tasks.