More cleanup

This commit is contained in:
Soreepeong 2024-03-04 22:46:38 +09:00
parent 0a658477c6
commit 2572f24e08
11 changed files with 635 additions and 253 deletions

View file

@ -2,7 +2,6 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Utility;
namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
@ -13,10 +12,8 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
/// <summary>Initializes a new instance of the <see cref="FileSystemSharedImmediateTexture"/> class.</summary>
/// <param name="path">The path.</param>
private FileSystemSharedImmediateTexture(string path) => this.path = path;
/// <inheritdoc/>
public override string SourcePathForDebug => this.path;
private FileSystemSharedImmediateTexture(string path)
: base(path) => this.path = path;
/// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary>
/// <param name="path">The path.</param>
@ -28,20 +25,7 @@ internal sealed class FileSystemSharedImmediateTexture : SharedImmediateTexture
$"{nameof(FileSystemSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})";
/// <inheritdoc/>
protected override void ReleaseResources()
{
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
this.UnderlyingWrap = null;
}
/// <inheritdoc/>
protected override void ReviveResources() =>
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
this,
this.CreateTextureAsync,
this.LoadCancellationToken);
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
protected override async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
{
var tm = await Service<TextureManager>.GetAsync();
return await tm.NoThrottleCreateFromFileAsync(this.path, cancellationToken);

View file

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using Dalamud.Data;
using Dalamud.Interface.Internal;
using Dalamud.Utility;
using Lumina.Data.Files;
@ -17,10 +16,8 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
/// <summary>Initializes a new instance of the <see cref="GamePathSharedImmediateTexture"/> class.</summary>
/// <param name="path">The path.</param>
private GamePathSharedImmediateTexture(string path) => this.path = path;
/// <inheritdoc/>
public override string SourcePathForDebug => this.path;
private GamePathSharedImmediateTexture(string path)
: base(path) => this.path = path;
/// <summary>Creates a new placeholder instance of <see cref="GamePathSharedImmediateTexture"/>.</summary>
/// <param name="path">The path.</param>
@ -32,20 +29,7 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
$"{nameof(GamePathSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.path})";
/// <inheritdoc/>
protected override void ReleaseResources()
{
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
this.UnderlyingWrap = null;
}
/// <inheritdoc/>
protected override void ReviveResources() =>
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
this,
this.CreateTextureAsync,
this.LoadCancellationToken);
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
protected override async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
{
var dm = await Service<DataManager>.GetAsync();
var tm = await Service<TextureManager>.GetAsync();

View file

@ -4,7 +4,6 @@ using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Utility;
namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
@ -19,14 +18,12 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
/// <param name="assembly">The assembly containing manifest resources.</param>
/// <param name="name">The case-sensitive name of the manifest resource being requested.</param>
private ManifestResourceSharedImmediateTexture(Assembly assembly, string name)
: base($"{assembly.GetName().FullName}:{name}")
{
this.assembly = assembly;
this.name = name;
}
/// <inheritdoc/>
public override string SourcePathForDebug => $"{this.assembly.GetName().FullName}:{this.name}";
/// <summary>Creates a new placeholder instance of <see cref="ManifestResourceSharedImmediateTexture"/>.</summary>
/// <param name="args">The arguments to pass to the constructor.</param>
/// <returns>The new instance.</returns>
@ -34,32 +31,15 @@ internal sealed class ManifestResourceSharedImmediateTexture : SharedImmediateTe
new ManifestResourceSharedImmediateTexture(args.Assembly, args.Name);
/// <inheritdoc/>
public override string ToString() =>
$"{nameof(ManifestResourceSharedImmediateTexture)}#{this.InstanceIdForDebug}({this.SourcePathForDebug})";
/// <inheritdoc/>
protected override void ReleaseResources()
{
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
this.UnderlyingWrap = null;
}
/// <inheritdoc/>
protected override void ReviveResources() =>
this.UnderlyingWrap = Service<TextureLoadThrottler>.Get().LoadTextureAsync(
this,
this.CreateTextureAsync,
this.LoadCancellationToken);
private async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
protected override async Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken)
{
await using var stream = this.assembly.GetManifestResourceStream(this.name);
if (stream is null)
throw new FileNotFoundException("The resource file could not be found.");
var tm = await Service<TextureManager>.GetAsync();
var ms = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0);
var ms = new MemoryStream(stream.CanSeek ? checked((int)stream.Length) : 0);
await stream.CopyToAsync(ms, cancellationToken);
return tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, (int)ms.Length), cancellationToken);
return tm.NoThrottleCreateFromImage(ms.GetBuffer().AsMemory(0, checked((int)ms.Length)), cancellationToken);
}
}

View file

@ -11,7 +11,7 @@ namespace Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
/// <summary>Represents a texture that may have multiple reference holders (owners).</summary>
internal abstract class SharedImmediateTexture
: ISharedImmediateTexture, IRefCountable, TextureLoadThrottler.IThrottleBasisProvider
: ISharedImmediateTexture, IRefCountable, DynamicPriorityQueueLoader.IThrottleBasisProvider
{
private const int SelfReferenceDurationTicks = 2000;
private const long SelfReferenceExpiryExpired = long.MaxValue;
@ -28,9 +28,11 @@ internal abstract class SharedImmediateTexture
private NotOwnedTextureWrap? nonOwningWrap;
/// <summary>Initializes a new instance of the <see cref="SharedImmediateTexture"/> class.</summary>
/// <param name="sourcePathForDebug">Name of the underlying resource.</param>
/// <remarks>The new instance is a placeholder instance.</remarks>
protected SharedImmediateTexture()
protected SharedImmediateTexture(string sourcePathForDebug)
{
this.SourcePathForDebug = sourcePathForDebug;
this.InstanceIdForDebug = Interlocked.Increment(ref instanceCounter);
this.refCount = 0;
this.selfReferenceExpiry = SelfReferenceExpiryExpired;
@ -53,7 +55,7 @@ internal abstract class SharedImmediateTexture
public int RefCountForDebug => this.refCount;
/// <summary>Gets the source path. Debug use only.</summary>
public abstract string SourcePathForDebug { get; }
public string SourcePathForDebug { get; }
/// <summary>Gets a value indicating whether this instance of <see cref="SharedImmediateTexture"/> supports revival.
/// </summary>
@ -76,7 +78,7 @@ internal abstract class SharedImmediateTexture
public bool ContentQueried { get; private set; }
/// <summary>Gets a cancellation token for cancelling load.
/// Intended to be called from implementors' constructors and <see cref="ReviveResources"/>.</summary>
/// Intended to be called from implementors' constructors and <see cref="LoadUnderlyingWrap"/>.</summary>
protected CancellationToken LoadCancellationToken => this.cancellationTokenSource?.Token ?? default;
/// <summary>Gets or sets a weak reference to an object that demands this objects to be alive.</summary>
@ -134,7 +136,7 @@ internal abstract class SharedImmediateTexture
this.cancellationTokenSource?.Cancel();
this.cancellationTokenSource = null;
this.nonOwningWrap = null;
this.ReleaseResources();
this.ClearUnderlyingWrap();
this.resourceReleased = true;
return newRefCount;
@ -272,11 +274,27 @@ internal abstract class SharedImmediateTexture
return this.availableOnAccessWrapForApi9;
}
/// <inheritdoc/>
public override string ToString() => $"{this.GetType().Name}#{this.InstanceIdForDebug}({this.SourcePathForDebug})";
/// <summary>Cleans up this instance of <see cref="SharedImmediateTexture"/>.</summary>
protected abstract void ReleaseResources();
protected void ClearUnderlyingWrap()
{
_ = this.UnderlyingWrap?.ToContentDisposedTask(true);
this.UnderlyingWrap = null;
}
/// <summary>Attempts to restore the reference to this texture.</summary>
protected abstract void ReviveResources();
protected void LoadUnderlyingWrap() =>
this.UnderlyingWrap = Service<TextureManager>.Get().DynamicPriorityTextureLoader.LoadAsync(
this,
this.CreateTextureAsync,
this.LoadCancellationToken);
/// <summary>Creates the texture.</summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The task resulting in a loaded texture.</returns>
protected abstract Task<IDalamudTextureWrap> CreateTextureAsync(CancellationToken cancellationToken);
private IRefCountable.RefCountResult TryAddRef(out int newRefCount)
{
@ -301,7 +319,7 @@ internal abstract class SharedImmediateTexture
this.cancellationTokenSource = new();
try
{
this.ReviveResources();
this.LoadUnderlyingWrap();
}
catch
{

View file

@ -2,7 +2,6 @@ using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Dalamud.Interface.Internal;
using Dalamud.Storage.Assets;
using Dalamud.Utility;
@ -18,13 +17,6 @@ internal sealed partial class TextureManager
{
private SimpleDrawerImpl? simpleDrawer;
[ServiceManager.CallWhenServicesReady("Need device")]
private unsafe void ContinueConstructionFromExistingTextures(InterfaceManager.InterfaceManagerWithScene withScene)
{
this.simpleDrawer = new();
this.simpleDrawer.Setup(this.Device.Get());
}
/// <summary>A class for drawing simple stuff.</summary>
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",

View file

@ -34,7 +34,7 @@ internal sealed partial class TextureManager
}
D3D11_FORMAT_SUPPORT supported;
if (this.Device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
if (this.device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
return false;
const D3D11_FORMAT_SUPPORT required =
@ -48,52 +48,48 @@ internal sealed partial class TextureManager
IDalamudTextureWrap wrap,
TextureModificationArgs args = default,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default)
{
return this.textureLoadThrottler.LoadTextureAsync(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
ImmediateLoadFunction,
CancellationToken cancellationToken = default) =>
this.DynamicPriorityTextureLoader.LoadAsync<IDalamudTextureWrap>(
null,
async _ =>
{
// leaveWrapOpen is taken care from calling LoadTextureAsync
using var wrapAux = new WrapAux(wrap, true);
using var tex = await this.NoThrottleCreateFromExistingTextureAsync(wrapAux, args);
unsafe
{
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
tex,
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
using var srv = default(ComPtr<ID3D11ShaderResourceView>);
this.device.Get()->CreateShaderResourceView(
(ID3D11Resource*)tex.Get(),
&srvDesc,
srv.GetAddressOf())
.ThrowOnError();
var desc = default(D3D11_TEXTURE2D_DESC);
tex.Get()->GetDesc(&desc);
return new UnknownTextureWrap(
(IUnknown*)srv.Get(),
(int)desc.Width,
(int)desc.Height,
true);
}
},
cancellationToken,
leaveWrapOpen ? null : wrap);
async Task<IDalamudTextureWrap> ImmediateLoadFunction(CancellationToken ct)
{
// leaveWrapOpen is taken care from calling LoadTextureAsync
using var wrapAux = new WrapAux(wrap, true);
using var tex = await this.NoThrottleCreateFromExistingTextureAsync(wrapAux, args);
unsafe
{
var srvDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC(
tex,
D3D_SRV_DIMENSION.D3D11_SRV_DIMENSION_TEXTURE2D);
using var srv = default(ComPtr<ID3D11ShaderResourceView>);
this.Device.Get()->CreateShaderResourceView(
(ID3D11Resource*)tex.Get(),
&srvDesc,
srv.GetAddressOf())
.ThrowOnError();
var desc = default(D3D11_TEXTURE2D_DESC);
tex.Get()->GetDesc(&desc);
return new UnknownTextureWrap(
(IUnknown*)srv.Get(),
(int)desc.Width,
(int)desc.Height,
true);
}
}
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
public Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
ImGuiViewportTextureArgs args,
CancellationToken cancellationToken = default)
{
// This constructor may throw; keep the function "async", to wrap the exception as a Task.
args.ThrowOnInvalidValues();
var t = new ViewportTextureWrap(args, cancellationToken);
t.QueueUpdate();
return await t.FirstUpdateTask;
return t.FirstUpdateTask;
}
/// <inheritdoc/>
@ -210,7 +206,7 @@ internal sealed partial class TextureManager
CPUAccessFlags = 0u,
MiscFlags = 0u,
};
this.Device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
this.device.Get()->CreateTexture2D(&tex2DCopyTempDesc, null, tex2DCopyTemp.GetAddressOf()).ThrowOnError();
}
await this.interfaceManager.RunBeforeImGuiRender(
@ -222,7 +218,7 @@ internal sealed partial class TextureManager
var rtvCopyTempDesc = new D3D11_RENDER_TARGET_VIEW_DESC(
tex2DCopyTemp,
D3D11_RTV_DIMENSION.D3D11_RTV_DIMENSION_TEXTURE2D);
this.Device.Get()->CreateRenderTargetView(
this.device.Get()->CreateRenderTargetView(
(ID3D11Resource*)tex2DCopyTemp.Get(),
&rtvCopyTempDesc,
rtvCopyTemp.GetAddressOf()).ThrowOnError();

View file

@ -1,15 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Configuration.Internal;
using Dalamud.Data;
using Dalamud.Game;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
@ -22,22 +24,22 @@ using TerraFX.Interop.Windows;
namespace Dalamud.Interface.Textures.Internal;
/// <summary>Service responsible for loading and disposing ImGui texture wraps.</summary>
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService]
#pragma warning disable SA1015
[ResolveVia<ITextureProvider>]
[ResolveVia<ITextureSubstitutionProvider>]
[ResolveVia<ITextureReadbackProvider>]
#pragma warning restore SA1015
internal sealed partial class TextureManager
: IServiceType, IDisposable, ITextureProvider, ITextureSubstitutionProvider, ITextureReadbackProvider
: IServiceType,
IDisposable,
ITextureProvider,
ITextureSubstitutionProvider,
ITextureReadbackProvider
{
private static readonly ModuleLog Log = new(nameof(TextureManager));
[ServiceManager.ServiceDependency]
private readonly Dalamud dalamud = Service<Dalamud>.Get();
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration dalamudConfiguration = Service<DalamudConfiguration>.Get();
[ServiceManager.ServiceDependency]
private readonly DataManager dataManager = Service<DataManager>.Get();
@ -47,54 +49,56 @@ internal sealed partial class TextureManager
[ServiceManager.ServiceDependency]
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
[ServiceManager.ServiceDependency]
private readonly TextureLoadThrottler textureLoadThrottler = Service<TextureLoadThrottler>.Get();
private DynamicPriorityQueueLoader? dynamicPriorityTextureLoader;
private SharedTextureManager? sharedTextureManager;
private WicManager? wicManager;
private bool disposing;
private ComPtr<ID3D11Device> device;
[SuppressMessage(
"StyleCop.CSharp.LayoutRules",
"SA1519:Braces should not be omitted from multi-line child statement",
Justification = "Multiple fixed blocks")]
[ServiceManager.ServiceConstructor]
private TextureManager()
private unsafe TextureManager(InterfaceManager.InterfaceManagerWithScene withScene)
{
this.sharedTextureManager = new(this);
this.wicManager = new(this);
using var failsafe = new DisposeSafety.ScopedFinalizer();
failsafe.Add(this.device = new((ID3D11Device*)withScene.Manager.Device!.NativePointer));
failsafe.Add(this.dynamicPriorityTextureLoader = new(Math.Max(1, Environment.ProcessorCount - 1)));
failsafe.Add(this.sharedTextureManager = new(this));
failsafe.Add(this.wicManager = new(this));
failsafe.Add(this.simpleDrawer = new());
this.simpleDrawer.Setup(this.device.Get());
failsafe.Cancel();
}
/// <summary>Gets the D3D11 Device used to create textures. Ownership is not transferred.</summary>
public unsafe ComPtr<ID3D11Device> Device
{
get
{
if (this.interfaceManager.Scene is not { } scene)
{
_ = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
scene = this.interfaceManager.Scene ?? throw new InvalidOperationException();
}
/// <summary>Finalizes an instance of the <see cref="TextureManager"/> class.</summary>
~TextureManager() => this.ReleaseUnmanagedResources();
var device = default(ComPtr<ID3D11Device>);
device.Attach((ID3D11Device*)scene.Device.NativePointer);
return device;
}
/// <summary>Gets the dynamic-priority queue texture loader.</summary>
public DynamicPriorityQueueLoader DynamicPriorityTextureLoader
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.dynamicPriorityTextureLoader ?? throw new ObjectDisposedException(nameof(TextureManager));
}
/// <summary>Gets a simpler drawer.</summary>
public SimpleDrawerImpl SimpleDrawer =>
this.simpleDrawer ?? throw new ObjectDisposedException(nameof(TextureManager));
public SimpleDrawerImpl SimpleDrawer
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.simpleDrawer ?? throw new ObjectDisposedException(nameof(TextureManager));
}
/// <summary>Gets the shared texture manager.</summary>
public SharedTextureManager Shared =>
this.sharedTextureManager ??
throw new ObjectDisposedException(nameof(TextureManager));
public SharedTextureManager Shared
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.sharedTextureManager ?? throw new ObjectDisposedException(nameof(TextureManager));
}
/// <summary>Gets the WIC manager.</summary>
public WicManager Wic =>
this.wicManager ??
throw new ObjectDisposedException(nameof(TextureManager));
public WicManager Wic
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.wicManager ?? throw new ObjectDisposedException(nameof(TextureManager));
}
/// <inheritdoc/>
public void Dispose()
@ -104,17 +108,28 @@ internal sealed partial class TextureManager
this.disposing = true;
Interlocked.Exchange(ref this.dynamicPriorityTextureLoader, null)?.Dispose();
Interlocked.Exchange(ref this.simpleDrawer, null)?.Dispose();
Interlocked.Exchange(ref this.sharedTextureManager, null)?.Dispose();
Interlocked.Exchange(ref this.wicManager, null)?.Dispose();
this.ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
/// <summary>Puts a plugin on blame for a texture.</summary>
/// <param name="textureWrap">The texture.</param>
/// <param name="ownerPlugin">The plugin.</param>
public void Blame(IDalamudTextureWrap textureWrap, LocalPlugin ownerPlugin)
{
// nop for now
}
/// <inheritdoc/>
public Task<IDalamudTextureWrap> CreateFromImageAsync(
ReadOnlyMemory<byte> bytes,
CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.LoadTextureAsync(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
this.DynamicPriorityTextureLoader.LoadAsync(
null,
ct => Task.Run(() => this.NoThrottleCreateFromImage(bytes.ToArray(), ct), ct),
cancellationToken);
@ -123,24 +138,16 @@ internal sealed partial class TextureManager
Stream stream,
bool leaveOpen = false,
CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.LoadTextureAsync(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
async ct =>
{
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct);
},
cancellationToken)
.ContinueWith(
r =>
{
if (!leaveOpen)
stream.Dispose();
return r;
},
default(CancellationToken))
.Unwrap();
this.DynamicPriorityTextureLoader.LoadAsync(
null,
async ct =>
{
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
return this.NoThrottleCreateFromImage(ms.GetBuffer(), ct);
},
cancellationToken,
leaveOpen ? null : stream);
/// <inheritdoc/>
// It probably doesn't make sense to throttle this, as it copies the passed bytes to GPU without any transformation.
@ -153,8 +160,8 @@ internal sealed partial class TextureManager
RawImageSpecification specs,
ReadOnlyMemory<byte> bytes,
CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.LoadTextureAsync(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
this.DynamicPriorityTextureLoader.LoadAsync(
null,
_ => Task.FromResult(this.NoThrottleCreateFromRaw(specs, bytes.Span)),
cancellationToken);
@ -164,24 +171,16 @@ internal sealed partial class TextureManager
Stream stream,
bool leaveOpen = false,
CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.LoadTextureAsync(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
async ct =>
{
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
return this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length));
},
cancellationToken)
.ContinueWith(
r =>
{
if (!leaveOpen)
stream.Dispose();
return r;
},
default(CancellationToken))
.Unwrap();
this.DynamicPriorityTextureLoader.LoadAsync(
null,
async ct =>
{
await using var ms = stream.CanSeek ? new MemoryStream((int)stream.Length) : new();
await stream.CopyToAsync(ms, ct).ConfigureAwait(false);
return this.NoThrottleCreateFromRaw(specs, ms.GetBuffer().AsSpan(0, (int)ms.Length));
},
cancellationToken,
leaveOpen ? null : stream);
/// <inheritdoc/>
public IDalamudTextureWrap CreateFromTexFile(TexFile file) => this.CreateFromTexFileAsync(file).Result;
@ -190,9 +189,9 @@ internal sealed partial class TextureManager
public Task<IDalamudTextureWrap> CreateFromTexFileAsync(
TexFile file,
CancellationToken cancellationToken = default) =>
this.textureLoadThrottler.LoadTextureAsync(
new TextureLoadThrottler.ReadOnlyThrottleBasisProvider(),
ct => Task.Run(() => this.NoThrottleCreateFromTexFile(file), ct),
this.DynamicPriorityTextureLoader.LoadAsync(
null,
_ => Task.FromResult(this.NoThrottleCreateFromTexFile(file)),
cancellationToken);
/// <inheritdoc/>
@ -203,7 +202,7 @@ internal sealed partial class TextureManager
public unsafe bool IsDxgiFormatSupported(DXGI_FORMAT dxgiFormat)
{
D3D11_FORMAT_SUPPORT supported;
if (this.Device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
if (this.device.Get()->CheckFormatSupport(dxgiFormat, (uint*)&supported).FAILED)
return false;
const D3D11_FORMAT_SUPPORT required = D3D11_FORMAT_SUPPORT.D3D11_FORMAT_SUPPORT_TEXTURE2D;
@ -215,8 +214,6 @@ internal sealed partial class TextureManager
RawImageSpecification specs,
ReadOnlySpan<byte> bytes)
{
var device = this.Device;
var texd = new D3D11_TEXTURE2D_DESC
{
Width = (uint)specs.Width,
@ -234,7 +231,7 @@ internal sealed partial class TextureManager
fixed (void* dataPtr = bytes)
{
var subrdata = new D3D11_SUBRESOURCE_DATA { pSysMem = dataPtr, SysMemPitch = (uint)specs.Pitch };
device.Get()->CreateTexture2D(&texd, &subrdata, texture.GetAddressOf()).ThrowOnError();
this.device.Get()->CreateTexture2D(&texd, &subrdata, texture.GetAddressOf()).ThrowOnError();
}
var viewDesc = new D3D11_SHADER_RESOURCE_VIEW_DESC
@ -244,7 +241,7 @@ internal sealed partial class TextureManager
Texture2D = new() { MipLevels = texd.MipLevels },
};
using var view = default(ComPtr<ID3D11ShaderResourceView>);
device.Get()->CreateShaderResourceView((ID3D11Resource*)texture.Get(), &viewDesc, view.GetAddressOf())
this.device.Get()->CreateShaderResourceView((ID3D11Resource*)texture.Get(), &viewDesc, view.GetAddressOf())
.ThrowOnError();
return new UnknownTextureWrap((IUnknown*)view.Get(), specs.Width, specs.Height, true);
@ -294,4 +291,6 @@ internal sealed partial class TextureManager
// Note: FileInfo and FilePath are not used from TexFile; skip it.
return this.NoThrottleCreateFromTexFile(tf);
}
private void ReleaseUnmanagedResources() => this.device.Reset();
}

View file

@ -0,0 +1,56 @@
using System.IO;
using Dalamud.Interface.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
namespace Dalamud.Interface.Textures.Internal;
#pragma warning disable CS0618 // Type or member is obsolete
/// <summary>Plugin-scoped version of <see cref="TextureManager"/>.</summary>
internal sealed partial class TextureManagerPluginScoped
{
/// <inheritdoc/>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete("See interface definition.")]
public IDalamudTextureWrap? GetIcon(
uint iconId,
ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes,
ClientLanguage? language = null,
bool keepAlive = false)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete("See interface definition.")]
public string? GetIconPath(
uint iconId,
ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes,
ClientLanguage? language = null)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete("See interface definition.")]
public IDalamudTextureWrap? GetTextureFromGame(
string path,
bool keepAlive = false)
{
throw new NotImplementedException();
}
/// <inheritdoc/>
[Api10ToDo(Api10ToDoAttribute.DeleteCompatBehavior)]
[Obsolete("See interface definition.")]
public IDalamudTextureWrap? GetTextureFromFile(
FileInfo file,
bool keepAlive = false)
{
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,350 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Internal.Types.Manifest;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Lumina.Data.Files;
using TerraFX.Interop.DirectX;
namespace Dalamud.Interface.Textures.Internal;
/// <summary>Plugin-scoped version of <see cref="TextureManager"/>.</summary>
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.ScopedService]
#pragma warning disable SA1015
[ResolveVia<ITextureProvider>]
[ResolveVia<ITextureSubstitutionProvider>]
[ResolveVia<ITextureReadbackProvider>]
#pragma warning restore SA1015
internal sealed partial class TextureManagerPluginScoped
: IServiceType,
IDisposable,
ITextureProvider,
ITextureSubstitutionProvider,
ITextureReadbackProvider
{
private readonly LocalPlugin plugin;
private readonly bool nonAsyncFunctionAccessDuringLoadIsError;
private Task<TextureManager>? managerTaskNullable;
[ServiceManager.ServiceConstructor]
private TextureManagerPluginScoped(LocalPlugin plugin)
{
this.plugin = plugin;
if (plugin.Manifest is LocalPluginManifest lpm)
this.nonAsyncFunctionAccessDuringLoadIsError = lpm.LoadSync && lpm.LoadRequiredState != 0;
this.managerTaskNullable =
Service<TextureManager>
.GetAsync()
.ContinueWith(
r =>
{
if (r.IsCompletedSuccessfully)
r.Result.InterceptTexDataLoad += this.ResultOnInterceptTexDataLoad;
return r;
})
.Unwrap();
}
/// <inheritdoc/>
public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
/// <summary>Gets the task resulting in an instance of <see cref="TextureManager"/>.</summary>
/// <exception cref="ObjectDisposedException">Thrown if disposed.</exception>
private Task<TextureManager> ManagerTask
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.managerTaskNullable ?? throw new ObjectDisposedException(this.ToString());
}
/// <summary>Gets an instance of <see cref="TextureManager"/>.</summary>
/// <exception cref="ObjectDisposedException">Thrown if disposed.</exception>
/// <exception cref="InvalidOperationException">Thrown if called at an unfortune time.</exception>
private TextureManager ManagerOrThrow
{
get
{
var task = this.ManagerTask;
// Check for IMWS too, as TextureManager is constructed after IMWS, and UiBuilder.RunWhenUiPrepared gets
// resolved when IMWS is constructed.
if (!task.IsCompleted && Service<InterfaceManager.InterfaceManagerWithScene>.GetNullable() is null)
{
if (this.nonAsyncFunctionAccessDuringLoadIsError && this.plugin.State != PluginState.Loaded)
{
throw new InvalidOperationException(
"The function you've called will wait for the drawing facilities to be available, and as " +
"Dalamud is already waiting for your plugin to be fully constructed before even attempting " +
"to initialize the drawing facilities, calling this function will stall the game until and " +
"is forbidden until your plugin has been fully loaded.\n" +
$"Consider using {nameof(UiBuilder.RunWhenUiPrepared)} to wait for the right moment.\n" +
"\n" +
$"Note that your plugin has {nameof(LocalPluginManifest.LoadSync)} set and " +
$"{nameof(LocalPluginManifest.LoadRequiredState)} that is nonzero.");
}
if (ThreadSafety.IsMainThread)
{
throw new InvalidOperationException(
"The function you've called will wait for the drawing facilities to be available, and as " +
"the drawing facilities are initialized from the main thread, calling this function will " +
"stall the game until and is forbidden until your plugin has been fully loaded.\n" +
$"Consider using {nameof(UiBuilder.RunWhenUiPrepared)} to wait for the right moment.");
}
}
return task.Result;
}
}
/// <inheritdoc/>
public void Dispose()
{
if (Interlocked.Exchange(ref this.managerTaskNullable, null) is not { } task)
return;
task.ContinueWith(
r =>
{
if (r.IsCompletedSuccessfully)
r.Result.InterceptTexDataLoad -= this.ResultOnInterceptTexDataLoad;
});
}
/// <inheritdoc/>
public override string ToString()
{
return this.managerTaskNullable is null
? $"{nameof(TextureManagerPluginScoped)}({this.plugin.Name}, disposed)"
: $"{nameof(TextureManagerPluginScoped)}({this.plugin.Name})";
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> CreateFromExistingTextureAsync(
IDalamudTextureWrap wrap,
TextureModificationArgs args = default,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
var textureWrap = await manager.CreateFromExistingTextureAsync(
wrap,
args,
leaveWrapOpen,
cancellationToken);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
ImGuiViewportTextureArgs args,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
var textureWrap = await manager.CreateFromImGuiViewportAsync(args, cancellationToken);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> CreateFromImageAsync(
ReadOnlyMemory<byte> bytes,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
var textureWrap = await manager.CreateFromImageAsync(bytes, cancellationToken);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> CreateFromImageAsync(
Stream stream,
bool leaveOpen = false,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
var textureWrap = await manager.CreateFromImageAsync(stream, leaveOpen, cancellationToken);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public IDalamudTextureWrap CreateFromRaw(
RawImageSpecification specs,
ReadOnlySpan<byte> bytes)
{
var manager = this.ManagerOrThrow;
var textureWrap = manager.CreateFromRaw(specs, bytes);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> CreateFromRawAsync(
RawImageSpecification specs,
ReadOnlyMemory<byte> bytes,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
var textureWrap = await manager.CreateFromRawAsync(specs, bytes, cancellationToken);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> CreateFromRawAsync(
RawImageSpecification specs,
Stream stream,
bool leaveOpen = false,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
var textureWrap = await manager.CreateFromRawAsync(specs, stream, leaveOpen, cancellationToken);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public IDalamudTextureWrap CreateFromTexFile(TexFile file)
{
var manager = this.ManagerOrThrow;
var textureWrap = manager.CreateFromTexFile(file);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public async Task<IDalamudTextureWrap> CreateFromTexFileAsync(
TexFile file,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
var textureWrap = await manager.CreateFromTexFileAsync(file, cancellationToken);
manager.Blame(textureWrap, this.plugin);
return textureWrap;
}
/// <inheritdoc/>
public IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos() =>
this.ManagerOrThrow.Wic.GetSupportedDecoderInfos();
/// <inheritdoc/>
public ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup)
{
return this.ManagerOrThrow.Shared.GetFromGameIcon(lookup);
}
/// <inheritdoc/>
public ISharedImmediateTexture GetFromGame(string path)
{
return this.ManagerOrThrow.Shared.GetFromGame(path);
}
/// <inheritdoc/>
public ISharedImmediateTexture GetFromFile(string path)
{
return this.ManagerOrThrow.Shared.GetFromFile(path);
}
/// <inheritdoc/>
public ISharedImmediateTexture GetFromManifestResource(Assembly assembly, string name)
{
return this.ManagerOrThrow.Shared.GetFromManifestResource(assembly, name);
}
/// <inheritdoc/>
public string GetIconPath(in GameIconLookup lookup) => this.ManagerOrThrow.GetIconPath(lookup);
/// <inheritdoc/>
public bool TryGetIconPath(in GameIconLookup lookup, out string? path) =>
this.ManagerOrThrow.TryGetIconPath(lookup, out path);
/// <inheritdoc/>
public bool IsDxgiFormatSupported(int dxgiFormat) =>
this.ManagerOrThrow.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat);
/// <inheritdoc/>
public bool IsDxgiFormatSupportedForCreateFromExistingTextureAsync(int dxgiFormat) =>
this.ManagerOrThrow.IsDxgiFormatSupportedForCreateFromExistingTextureAsync((DXGI_FORMAT)dxgiFormat);
/// <inheritdoc/>
public string GetSubstitutedPath(string originalPath) =>
this.ManagerOrThrow.GetSubstitutedPath(originalPath);
/// <inheritdoc/>
public void InvalidatePaths(IEnumerable<string> paths) =>
this.ManagerOrThrow.InvalidatePaths(paths);
/// <inheritdoc/>
public async Task<(RawImageSpecification Specification, byte[] RawData)> GetRawImageAsync(
IDalamudTextureWrap wrap,
TextureModificationArgs args = default,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
return await manager.GetRawImageAsync(wrap, args, leaveWrapOpen, cancellationToken);
}
/// <inheritdoc/>
public IEnumerable<IBitmapCodecInfo> GetSupportedImageEncoderInfos() =>
this.ManagerOrThrow.Wic.GetSupportedEncoderInfos();
/// <inheritdoc/>
public async Task SaveToStreamAsync(
IDalamudTextureWrap wrap,
Guid containerGuid,
Stream stream,
IReadOnlyDictionary<string, object>? props = null,
bool leaveWrapOpen = false,
bool leaveStreamOpen = false,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
await manager.SaveToStreamAsync(
wrap,
containerGuid,
stream,
props,
leaveWrapOpen,
leaveStreamOpen,
cancellationToken);
}
/// <inheritdoc/>
public async Task SaveToFileAsync(
IDalamudTextureWrap wrap,
Guid containerGuid,
string path,
IReadOnlyDictionary<string, object>? props = null,
bool leaveWrapOpen = false,
CancellationToken cancellationToken = default)
{
var manager = await this.ManagerTask;
await manager.SaveToFileAsync(
wrap,
containerGuid,
path,
props,
leaveWrapOpen,
cancellationToken);
}
private void ResultOnInterceptTexDataLoad(string path, ref string? replacementPath) =>
this.InterceptTexDataLoad?.Invoke(path, ref replacementPath);
}

View file

@ -34,8 +34,6 @@ internal sealed class ViewportTextureWrap : IDalamudTextureWrap, IDeferredDispos
/// <param name="cancellationToken">The cancellation token.</param>
public ViewportTextureWrap(ImGuiViewportTextureArgs args, CancellationToken cancellationToken)
{
args.ThrowOnInvalidValues();
this.args = args;
this.cancellationToken = cancellationToken;
}

View file

@ -4,15 +4,10 @@ using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
namespace Dalamud.Utility;
namespace Dalamud.Interface.Textures.Internal;
/// <summary>
/// Service for managing texture loads.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class TextureLoadThrottler : IServiceType, IDisposable
/// <summary>Base class for loading resources in dynamic order.</summary>
internal class DynamicPriorityQueueLoader : IDisposable
{
private readonly CancellationTokenSource disposeCancellationTokenSource = new();
private readonly Task adderTask;
@ -24,19 +19,20 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
private bool disposing;
[ServiceManager.ServiceConstructor]
private TextureLoadThrottler()
/// <summary>Initializes a new instance of the <see cref="DynamicPriorityQueueLoader"/> class.</summary>
/// <param name="concurrency">Maximum number of concurrent load tasks.</param>
public DynamicPriorityQueueLoader(int concurrency)
{
this.newItemChannel = Channel.CreateUnbounded<WorkItem>(new() { SingleReader = true });
this.workTokenChannel = Channel.CreateUnbounded<object?>(new() { SingleWriter = true });
this.adderTask = Task.Run(this.LoopAddWorkItemAsync);
this.workerTasks = new Task[Math.Max(1, Environment.ProcessorCount - 1)];
this.workerTasks = new Task[concurrency];
foreach (ref var task in this.workerTasks.AsSpan())
task = Task.Run(this.LoopProcessWorkItemAsync);
}
/// <summary>Basis for throttling. Values may be changed anytime.</summary>
/// <summary>Provider for priority metrics.</summary>
internal interface IThrottleBasisProvider
{
/// <summary>Gets a value indicating whether the resource is requested in an opportunistic way.</summary>
@ -68,27 +64,36 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
_ = t.Exception;
}
/// <summary>Loads a texture according to some order.</summary>
/// <param name="basis">The throttle basis.</param>
/// <summary>Loads a resource according to some order.</summary>
/// <typeparam name="T">The type of resource.</typeparam>
/// <param name="basis">The throttle basis. <c>null</c> may be used to create a new instance of
/// <see cref="IThrottleBasisProvider"/> that is not opportunistic with time values of now.</param>
/// <param name="immediateLoadFunction">The immediate load function.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="disposables">Disposables to dispose when the task completes.</param>
/// <returns>The task.</returns>
public Task<IDalamudTextureWrap> LoadTextureAsync(
IThrottleBasisProvider basis,
Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction,
/// <remarks>
/// <paramref name="immediateLoadFunction"/> may throw immediately without returning anything, or the returned
/// <see cref="Task{TResult}"/> may complete in failure.
/// </remarks>
public Task<T> LoadAsync<T>(
IThrottleBasisProvider? basis,
Func<CancellationToken, Task<T>> immediateLoadFunction,
CancellationToken cancellationToken,
params IDisposable?[] disposables)
{
var work = new WorkItem(basis, immediateLoadFunction, cancellationToken, disposables);
basis ??= new ReadOnlyThrottleBasisProvider();
var work = new WorkItem<T>(basis, immediateLoadFunction, cancellationToken, disposables);
if (this.newItemChannel.Writer.TryWrite(work))
return work.Task;
work.Dispose();
return Task.FromException<IDalamudTextureWrap>(new ObjectDisposedException(nameof(TextureLoadThrottler)));
return Task.FromException<T>(new ObjectDisposedException(this.GetType().Name));
}
/// <summary>Continuously transfers work items added from <see cref="LoadAsync{T}"/> to
/// <see cref="workItemPending"/>, until all items are transferred and <see cref="Dispose"/> is called.</summary>
private async Task LoopAddWorkItemAsync()
{
const int batchAddSize = 64;
@ -109,6 +114,8 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
}
}
/// <summary>Continuously processes work items in <see cref="workItemPending"/>, until all items are processed and
/// <see cref="Dispose"/> is called.</summary>
private async Task LoopProcessWorkItemAsync()
{
var reader = this.workTokenChannel.Reader;
@ -167,10 +174,8 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
}
}
/// <summary>
/// A read-only implementation of <see cref="IThrottleBasisProvider"/>.
/// </summary>
public class ReadOnlyThrottleBasisProvider : IThrottleBasisProvider
/// <summary>A read-only implementation of <see cref="IThrottleBasisProvider"/>.</summary>
private class ReadOnlyThrottleBasisProvider : IThrottleBasisProvider
{
/// <inheritdoc/>
public bool IsOpportunistic { get; init; } = false;
@ -182,27 +187,23 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
public long LatestRequestedTick { get; init; } = Environment.TickCount64;
}
private sealed class WorkItem : IComparable<WorkItem>, IDisposable
/// <summary>Represents a work item added from <see cref="LoadAsync{T}"/>.</summary>
private abstract class WorkItem : IComparable<WorkItem>, IDisposable
{
private readonly TaskCompletionSource<IDalamudTextureWrap> taskCompletionSource;
private readonly IThrottleBasisProvider basis;
private readonly CancellationToken cancellationToken;
private readonly Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction;
private readonly IDisposable?[] disposables;
public WorkItem(
protected WorkItem(
IThrottleBasisProvider basis,
Func<CancellationToken, Task<IDalamudTextureWrap>> immediateLoadFunction,
CancellationToken cancellationToken, IDisposable?[] disposables)
CancellationToken cancellationToken,
params IDisposable?[] disposables)
{
this.taskCompletionSource = new();
this.basis = basis;
this.cancellationToken = cancellationToken;
this.CancellationToken = cancellationToken;
this.disposables = disposables;
this.immediateLoadFunction = immediateLoadFunction;
}
public Task<IDalamudTextureWrap> Task => this.taskCompletionSource.Task;
protected CancellationToken CancellationToken { get; }
public void Dispose()
{
@ -219,13 +220,37 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
return this.basis.FirstRequestedTick.CompareTo(other.basis.FirstRequestedTick);
}
public bool CancelAsRequested()
public abstract bool CancelAsRequested();
public abstract ValueTask Process(CancellationToken serviceDisposeToken);
}
/// <summary>Typed version of <see cref="WorkItem"/>.</summary>
private sealed class WorkItem<T> : WorkItem
{
private readonly TaskCompletionSource<T> taskCompletionSource;
private readonly Func<CancellationToken, Task<T>> immediateLoadFunction;
public WorkItem(
IThrottleBasisProvider basis,
Func<CancellationToken, Task<T>> immediateLoadFunction,
CancellationToken cancellationToken,
params IDisposable?[] disposables)
: base(basis, cancellationToken, disposables)
{
if (!this.cancellationToken.IsCancellationRequested)
this.taskCompletionSource = new();
this.immediateLoadFunction = immediateLoadFunction;
}
public Task<T> Task => this.taskCompletionSource.Task;
public override bool CancelAsRequested()
{
if (!this.CancellationToken.IsCancellationRequested)
return false;
// Cancel the load task and move on.
this.taskCompletionSource.TrySetCanceled(this.cancellationToken);
this.taskCompletionSource.TrySetCanceled(this.CancellationToken);
// Suppress the OperationCanceledException caused from the above.
_ = this.taskCompletionSource.Task.Exception;
@ -233,16 +258,16 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
return true;
}
public async ValueTask Process(CancellationToken serviceDisposeToken)
public override async ValueTask Process(CancellationToken serviceDisposeToken)
{
try
{
IDalamudTextureWrap wrap;
if (this.cancellationToken.CanBeCanceled)
T wrap;
if (this.CancellationToken.CanBeCanceled)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
serviceDisposeToken,
this.cancellationToken);
this.CancellationToken);
wrap = await this.immediateLoadFunction(cts.Token);
}
else
@ -251,7 +276,7 @@ internal class TextureLoadThrottler : IServiceType, IDisposable
}
if (!this.taskCompletionSource.TrySetResult(wrap))
wrap.Dispose();
(wrap as IDisposable)?.Dispose();
}
catch (Exception e)
{