mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-29 20:03:41 +01:00
Cleanup TextureLoadThrottler
This commit is contained in:
parent
b34a901702
commit
e2ed5258eb
5 changed files with 116 additions and 119 deletions
|
|
@ -36,7 +36,7 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void ReviveResources() =>
|
protected override void ReviveResources() =>
|
||||||
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().CreateLoader(
|
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
|
||||||
this,
|
this,
|
||||||
this.CreateTextureAsync,
|
this.CreateTextureAsync,
|
||||||
this.LoadCancellationToken);
|
this.LoadCancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void ReviveResources() =>
|
protected override void ReviveResources() =>
|
||||||
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().CreateLoader(
|
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
|
||||||
this,
|
this,
|
||||||
this.CreateTextureAsync,
|
this.CreateTextureAsync,
|
||||||
this.LoadCancellationToken);
|
this.LoadCancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void ReviveResources() =>
|
protected override void ReviveResources() =>
|
||||||
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().CreateLoader(
|
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
|
||||||
this,
|
this,
|
||||||
this.CreateTextureAsync,
|
this.CreateTextureAsync,
|
||||||
this.LoadCancellationToken);
|
this.LoadCancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -16,7 +16,6 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
||||||
private readonly Task adderTask;
|
private readonly Task adderTask;
|
||||||
private readonly Task[] workerTasks;
|
private readonly Task[] workerTasks;
|
||||||
|
|
||||||
private readonly object workListLock = new();
|
|
||||||
private readonly Channel<WorkItem> newItemChannel = Channel.CreateUnbounded<WorkItem>();
|
private readonly Channel<WorkItem> newItemChannel = Channel.CreateUnbounded<WorkItem>();
|
||||||
private readonly Channel<object?> workTokenChannel = Channel.CreateUnbounded<object?>();
|
private readonly Channel<object?> workTokenChannel = Channel.CreateUnbounded<object?>();
|
||||||
private readonly List<WorkItem> workItemPending = new();
|
private readonly List<WorkItem> workItemPending = new();
|
||||||
|
|
@ -27,29 +26,21 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
||||||
private TextureLoadThrottler()
|
private TextureLoadThrottler()
|
||||||
{
|
{
|
||||||
this.adderTask = Task.Run(this.LoopAddWorkItemAsync);
|
this.adderTask = Task.Run(this.LoopAddWorkItemAsync);
|
||||||
this.workerTasks = new Task[Math.Min(64, Environment.ProcessorCount)];
|
this.workerTasks = new Task[Math.Max(1, Environment.ProcessorCount - 1)];
|
||||||
foreach (ref var task in this.workerTasks.AsSpan())
|
foreach (ref var task in this.workerTasks.AsSpan())
|
||||||
task = Task.Run(this.LoopProcessWorkItemAsync);
|
task = Task.Run(this.LoopProcessWorkItemAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Basis for throttling. Values may be changed anytime.</summary>
|
||||||
/// Basis for throttling.
|
|
||||||
/// </summary>
|
|
||||||
internal interface IThrottleBasisProvider
|
internal interface IThrottleBasisProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>Gets a value indicating whether the resource is requested in an opportunistic way.</summary>
|
||||||
/// Gets a value indicating whether the resource is requested in an opportunistic way.
|
|
||||||
/// </summary>
|
|
||||||
bool IsOpportunistic { get; }
|
bool IsOpportunistic { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Gets the first requested tick count from <see cref="Environment.TickCount64"/>.</summary>
|
||||||
/// Gets the first requested tick count from <see cref="Environment.TickCount64"/>.
|
|
||||||
/// </summary>
|
|
||||||
long FirstRequestedTick { get; }
|
long FirstRequestedTick { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Gets the latest requested tick count from <see cref="Environment.TickCount64"/>.</summary>
|
||||||
/// Gets the latest requested tick count from <see cref="Environment.TickCount64"/>.
|
|
||||||
/// </summary>
|
|
||||||
long LatestRequestedTick { get; }
|
long LatestRequestedTick { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,135 +63,94 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
||||||
_ = t.Exception;
|
_ = t.Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>Loads a texture according to some order.</summary>
|
||||||
/// Creates a texture loader.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="basis">The throttle basis.</param>
|
/// <param name="basis">The throttle basis.</param>
|
||||||
/// <param name="immediateLoadFunction">The immediate load function.</param>
|
/// <param name="immediateLoadFunction">The immediate load function.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The task.</returns>
|
/// <returns>The task.</returns>
|
||||||
public Task<IDalamudTextureWrap> CreateLoader(
|
public Task<IDalamudTextureWrap> LoadTextureAsync(
|
||||||
IThrottleBasisProvider basis,
|
IThrottleBasisProvider basis,
|
||||||
Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction,
|
Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var work = new WorkItem
|
var work = new WorkItem(basis, immediateLoadFunction, cancellationToken);
|
||||||
{
|
|
||||||
TaskCompletionSource = new(),
|
|
||||||
Basis = basis,
|
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
ImmediateLoadFunction = immediateLoadFunction,
|
|
||||||
};
|
|
||||||
|
|
||||||
return
|
return
|
||||||
this.newItemChannel.Writer.TryWrite(work)
|
this.newItemChannel.Writer.TryWrite(work)
|
||||||
? work.TaskCompletionSource.Task
|
? work.Task
|
||||||
: Task.FromException<IDalamudTextureWrap>(new ObjectDisposedException(nameof(TextureLoadThrottler)));
|
: Task.FromException<IDalamudTextureWrap>(new ObjectDisposedException(nameof(TextureLoadThrottler)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoopAddWorkItemAsync()
|
private async Task LoopAddWorkItemAsync()
|
||||||
{
|
{
|
||||||
var newWorkTemp = new List<WorkItem>();
|
const int batchAddSize = 64;
|
||||||
|
var newWorks = new List<WorkItem>(batchAddSize);
|
||||||
var reader = this.newItemChannel.Reader;
|
var reader = this.newItemChannel.Reader;
|
||||||
while (!reader.Completion.IsCompleted)
|
while (await reader.WaitToReadAsync())
|
||||||
{
|
{
|
||||||
await reader.WaitToReadAsync();
|
while (newWorks.Count < batchAddSize && reader.TryRead(out var newWork))
|
||||||
|
newWorks.Add(newWork);
|
||||||
|
|
||||||
newWorkTemp.EnsureCapacity(reader.Count);
|
lock (this.workItemPending)
|
||||||
while (newWorkTemp.Count < newWorkTemp.Capacity && reader.TryRead(out var newWork))
|
this.workItemPending.AddRange(newWorks);
|
||||||
newWorkTemp.Add(newWork);
|
|
||||||
lock (this.workListLock)
|
for (var i = newWorks.Count; i > 0; i--)
|
||||||
this.workItemPending.AddRange(newWorkTemp);
|
|
||||||
for (var i = newWorkTemp.Count; i > 0; i--)
|
|
||||||
this.workTokenChannel.Writer.TryWrite(null);
|
this.workTokenChannel.Writer.TryWrite(null);
|
||||||
newWorkTemp.Clear();
|
|
||||||
|
newWorks.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoopProcessWorkItemAsync()
|
private async Task LoopProcessWorkItemAsync()
|
||||||
{
|
{
|
||||||
var reader = this.workTokenChannel.Reader;
|
var reader = this.workTokenChannel.Reader;
|
||||||
while (!reader.Completion.IsCompleted)
|
while (await reader.WaitToReadAsync())
|
||||||
{
|
{
|
||||||
_ = await reader.ReadAsync();
|
if (!reader.TryRead(out _))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (this.ExtractHighestPriorityWorkItem() is not { } work)
|
if (this.ExtractHighestPriorityWorkItem() is not { } work)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try
|
await work.Process(this.disposeCancellationTokenSource.Token);
|
||||||
{
|
|
||||||
IDalamudTextureWrap wrap;
|
|
||||||
if (work.CancellationToken.CanBeCanceled)
|
|
||||||
{
|
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
|
|
||||||
this.disposeCancellationTokenSource.Token,
|
|
||||||
work.CancellationToken);
|
|
||||||
wrap = await work.ImmediateLoadFunction(cts.Token);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wrap = await work.ImmediateLoadFunction(this.disposeCancellationTokenSource.Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
work.TaskCompletionSource.SetResult(wrap);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
work.TaskCompletionSource.SetException(e);
|
|
||||||
_ = work.TaskCompletionSource.Task.Exception;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Extracts the work item with the highest priority from <see cref="workItemPending"/>,
|
||||||
|
/// and removes cancelled items, if any.</summary>
|
||||||
|
/// <remarks>The order of items of <see cref="workItemPending"/> is undefined after this function.</remarks>
|
||||||
private WorkItem? ExtractHighestPriorityWorkItem()
|
private WorkItem? ExtractHighestPriorityWorkItem()
|
||||||
{
|
{
|
||||||
lock (this.workListLock)
|
lock (this.workItemPending)
|
||||||
{
|
{
|
||||||
WorkItem? highestPriorityWork = null;
|
for (var startIndex = 0; startIndex < this.workItemPending.Count - 1;)
|
||||||
var highestPriorityIndex = -1;
|
|
||||||
for (var i = 0; i < this.workItemPending.Count; i++)
|
|
||||||
{
|
{
|
||||||
var work = this.workItemPending[i];
|
var span = CollectionsMarshal.AsSpan(this.workItemPending)[startIndex..];
|
||||||
if (work.CancellationToken.IsCancellationRequested)
|
ref var lastRef = ref span[^1];
|
||||||
|
foreach (ref var itemRef in span[..^1])
|
||||||
{
|
{
|
||||||
work.TaskCompletionSource.SetCanceled(work.CancellationToken);
|
if (itemRef.CancelAsRequested())
|
||||||
_ = work.TaskCompletionSource.Task.Exception;
|
{
|
||||||
this.RelocatePendingWorkItemToEndAndEraseUnsafe(i--);
|
itemRef = lastRef;
|
||||||
continue;
|
this.workItemPending.RemoveAt(this.workItemPending.Count - 1);
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (highestPriorityIndex == -1 ||
|
if (itemRef.CompareTo(lastRef) < 0)
|
||||||
work.CompareTo(this.workItemPending[highestPriorityIndex]) < 0)
|
(itemRef, lastRef) = (lastRef, itemRef);
|
||||||
{
|
startIndex++;
|
||||||
highestPriorityIndex = i;
|
|
||||||
highestPriorityWork = work;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (highestPriorityWork is null)
|
if (this.workItemPending.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
this.RelocatePendingWorkItemToEndAndEraseUnsafe(highestPriorityIndex);
|
var last = this.workItemPending[^1];
|
||||||
return highestPriorityWork;
|
this.workItemPending.RemoveAt(this.workItemPending.Count - 1);
|
||||||
|
return last.CancelAsRequested() ? null : last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Remove an item in <see cref="workItemPending"/>, avoiding shifting.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">Index of the item to remove.</param>
|
|
||||||
private void RelocatePendingWorkItemToEndAndEraseUnsafe(int index)
|
|
||||||
{
|
|
||||||
// Relocate the element to remove to the last.
|
|
||||||
if (index != this.workItemPending.Count - 1)
|
|
||||||
{
|
|
||||||
(this.workItemPending[^1], this.workItemPending[index]) =
|
|
||||||
(this.workItemPending[index], this.workItemPending[^1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.workItemPending.RemoveAt(this.workItemPending.Count - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A read-only implementation of <see cref="IThrottleBasisProvider"/>.
|
/// A read-only implementation of <see cref="IThrottleBasisProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -216,27 +166,74 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
|
||||||
public long LatestRequestedTick { get; init; } = Environment.TickCount64;
|
public long LatestRequestedTick { get; init; } = Environment.TickCount64;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage(
|
private class WorkItem : IComparable<WorkItem>
|
||||||
"StyleCop.CSharp.OrderingRules",
|
|
||||||
"SA1206:Declaration keywords should follow order",
|
|
||||||
Justification = "no")]
|
|
||||||
private record WorkItem : IComparable<WorkItem>
|
|
||||||
{
|
{
|
||||||
public required TaskCompletionSource<IDalamudTextureWrap> TaskCompletionSource { get; init; }
|
private readonly TaskCompletionSource<IDalamudTextureWrap> taskCompletionSource;
|
||||||
|
private readonly IThrottleBasisProvider basis;
|
||||||
|
private readonly CancellationToken cancellationToken;
|
||||||
|
private readonly Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction;
|
||||||
|
|
||||||
public required IThrottleBasisProvider Basis { get; init; }
|
public WorkItem(
|
||||||
|
IThrottleBasisProvider basis,
|
||||||
|
Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
this.taskCompletionSource = new();
|
||||||
|
this.basis = basis;
|
||||||
|
this.cancellationToken = cancellationToken;
|
||||||
|
this.immediateLoadFunction = immediateLoadFunction;
|
||||||
|
}
|
||||||
|
|
||||||
public required CancellationToken CancellationToken { get; init; }
|
public Task<IDalamudTextureWrap> Task => this.taskCompletionSource.Task;
|
||||||
|
|
||||||
public required Func<CancellationToken, Task<IDalamudTextureWrap>> ImmediateLoadFunction { get; init; }
|
|
||||||
|
|
||||||
public int CompareTo(WorkItem other)
|
public int CompareTo(WorkItem other)
|
||||||
{
|
{
|
||||||
if (this.Basis.IsOpportunistic != other.Basis.IsOpportunistic)
|
if (this.basis.IsOpportunistic != other.basis.IsOpportunistic)
|
||||||
return this.Basis.IsOpportunistic ? 1 : -1;
|
return this.basis.IsOpportunistic ? 1 : -1;
|
||||||
if (this.Basis.IsOpportunistic)
|
if (this.basis.IsOpportunistic)
|
||||||
return -this.Basis.LatestRequestedTick.CompareTo(other.Basis.LatestRequestedTick);
|
return -this.basis.LatestRequestedTick.CompareTo(other.basis.LatestRequestedTick);
|
||||||
return this.Basis.FirstRequestedTick.CompareTo(other.Basis.FirstRequestedTick);
|
return this.basis.FirstRequestedTick.CompareTo(other.basis.FirstRequestedTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CancelAsRequested()
|
||||||
|
{
|
||||||
|
if (!this.cancellationToken.IsCancellationRequested)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Cancel the load task and move on.
|
||||||
|
this.taskCompletionSource.TrySetCanceled(this.cancellationToken);
|
||||||
|
|
||||||
|
// Suppress the OperationCanceledException caused from the above.
|
||||||
|
_ = this.taskCompletionSource.Task.Exception;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask Process(CancellationToken serviceDisposeToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IDalamudTextureWrap wrap;
|
||||||
|
if (this.cancellationToken.CanBeCanceled)
|
||||||
|
{
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||||
|
serviceDisposeToken,
|
||||||
|
this.cancellationToken);
|
||||||
|
wrap = await this.immediateLoadFunction(cts.Token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wrap = await this.immediateLoadFunction(serviceDisposeToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.taskCompletionSource.TrySetResult(wrap))
|
||||||
|
wrap.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.taskCompletionSource.TrySetException(e);
|
||||||
|
_ = this.taskCompletionSource.Task.Exception;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
||||||
public Task<IDalamudTextureWrap> CreateFromImageAsync(
|
public Task<IDalamudTextureWrap> CreateFromImageAsync(
|
||||||
ReadOnlyMemory<byte> bytes,
|
ReadOnlyMemory<byte> bytes,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
this.textureLoadThrottler.CreateLoader(
|
this.textureLoadThrottler.LoadTextureAsync(
|
||||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||||
ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray()), ct),
|
ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray()), ct),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
@ -229,7 +229,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
||||||
Stream stream,
|
Stream stream,
|
||||||
bool leaveOpen = false,
|
bool leaveOpen = false,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
this.textureLoadThrottler.CreateLoader(
|
this.textureLoadThrottler.LoadTextureAsync(
|
||||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||||
async ct =>
|
async ct =>
|
||||||
{
|
{
|
||||||
|
|
@ -300,7 +300,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
||||||
RawImageSpecification specs,
|
RawImageSpecification specs,
|
||||||
ReadOnlyMemory<byte> bytes,
|
ReadOnlyMemory<byte> bytes,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
this.textureLoadThrottler.CreateLoader(
|
this.textureLoadThrottler.LoadTextureAsync(
|
||||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||||
_ => Task.FromResult(this.CreateFromRaw(specs, bytes.Span)),
|
_ => Task.FromResult(this.CreateFromRaw(specs, bytes.Span)),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
@ -311,7 +311,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
||||||
Stream stream,
|
Stream stream,
|
||||||
bool leaveOpen = false,
|
bool leaveOpen = false,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
this.textureLoadThrottler.CreateLoader(
|
this.textureLoadThrottler.LoadTextureAsync(
|
||||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||||
async ct =>
|
async ct =>
|
||||||
{
|
{
|
||||||
|
|
@ -337,7 +337,7 @@ internal sealed class TextureManager : IServiceType, IDisposable, ITextureProvid
|
||||||
public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
|
public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
|
||||||
TexFile file,
|
TexFile file,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
this.textureLoadThrottler.CreateLoader(
|
this.textureLoadThrottler.LoadTextureAsync(
|
||||||
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
|
||||||
ct => Task.Run(() => this.NoThrottleCreateFromTexFile(file), ct),
|
ct => Task.Run(() => this.NoThrottleCreateFromTexFile(file), ct),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue