mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
381 lines
14 KiB
C#
381 lines
14 KiB
C#
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.Interface.Textures.TextureWraps;
|
|
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]
|
|
[ServiceManager.ScopedService]
|
|
#pragma warning disable SA1015
|
|
[ResolveVia<ITextureProvider>]
|
|
[ResolveVia<ITextureSubstitutionProvider>]
|
|
[ResolveVia<ITextureReadbackProvider>]
|
|
#pragma warning restore SA1015
|
|
internal sealed class TextureManagerPluginScoped
|
|
: IInternalDisposableService,
|
|
ITextureProvider,
|
|
ITextureSubstitutionProvider,
|
|
ITextureReadbackProvider
|
|
{
|
|
private readonly LocalPlugin plugin;
|
|
private readonly bool nonAsyncFunctionAccessDuringLoadIsError;
|
|
|
|
private Task<TextureManager>? managerTaskNullable;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="TextureManagerPluginScoped"/> class.</summary>
|
|
/// <param name="plugin">The plugin.</param>
|
|
[ServiceManager.ServiceConstructor]
|
|
public 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/>
|
|
void IInternalDisposableService.DisposeService()
|
|
{
|
|
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 IDalamudTextureWrap CreateEmpty(
|
|
RawImageSpecification specs,
|
|
bool cpuRead,
|
|
bool cpuWrite,
|
|
string? debugName = null)
|
|
{
|
|
var manager = this.ManagerOrThrow;
|
|
var textureWrap = manager.CreateEmpty(specs, cpuRead, cpuWrite, debugName);
|
|
manager.Blame(textureWrap, this.plugin);
|
|
return textureWrap;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IDalamudTextureWrap> CreateFromExistingTextureAsync(
|
|
IDalamudTextureWrap wrap,
|
|
TextureModificationArgs args = default,
|
|
bool leaveWrapOpen = false,
|
|
string? debugName = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var manager = await this.ManagerTask;
|
|
var textureWrap = await manager.CreateFromExistingTextureAsync(
|
|
wrap,
|
|
args,
|
|
leaveWrapOpen,
|
|
debugName,
|
|
cancellationToken);
|
|
manager.Blame(textureWrap, this.plugin);
|
|
return textureWrap;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IDalamudTextureWrap> CreateFromImGuiViewportAsync(
|
|
ImGuiViewportTextureArgs args,
|
|
string? debugName = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var manager = await this.ManagerTask;
|
|
var textureWrap = await manager.CreateFromImGuiViewportAsync(args, this.plugin, debugName, cancellationToken);
|
|
manager.Blame(textureWrap, this.plugin);
|
|
return textureWrap;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IDalamudTextureWrap> CreateFromImageAsync(
|
|
ReadOnlyMemory<byte> bytes,
|
|
string? debugName = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var manager = await this.ManagerTask;
|
|
var textureWrap = await manager.CreateFromImageAsync(bytes, debugName, cancellationToken);
|
|
manager.Blame(textureWrap, this.plugin);
|
|
return textureWrap;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IDalamudTextureWrap> CreateFromImageAsync(
|
|
Stream stream,
|
|
bool leaveOpen = false,
|
|
string? debugName = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var manager = await this.ManagerTask;
|
|
var textureWrap = await manager.CreateFromImageAsync(stream, leaveOpen, debugName, cancellationToken);
|
|
manager.Blame(textureWrap, this.plugin);
|
|
return textureWrap;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IDalamudTextureWrap CreateFromRaw(
|
|
RawImageSpecification specs,
|
|
ReadOnlySpan<byte> bytes,
|
|
string? debugName = null)
|
|
{
|
|
var manager = this.ManagerOrThrow;
|
|
var textureWrap = manager.CreateFromRaw(specs, bytes, debugName);
|
|
manager.Blame(textureWrap, this.plugin);
|
|
return textureWrap;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IDalamudTextureWrap> CreateFromRawAsync(
|
|
RawImageSpecification specs,
|
|
ReadOnlyMemory<byte> bytes,
|
|
string? debugName = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var manager = await this.ManagerTask;
|
|
var textureWrap = await manager.CreateFromRawAsync(specs, bytes, debugName, cancellationToken);
|
|
manager.Blame(textureWrap, this.plugin);
|
|
return textureWrap;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<IDalamudTextureWrap> CreateFromRawAsync(
|
|
RawImageSpecification specs,
|
|
Stream stream,
|
|
bool leaveOpen = false,
|
|
string? debugName = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var manager = await this.ManagerTask;
|
|
var textureWrap = await manager.CreateFromRawAsync(specs, stream, leaveOpen, debugName, 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,
|
|
string? debugName = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var manager = await this.ManagerTask;
|
|
var textureWrap = await manager.CreateFromTexFileAsync(file, debugName, cancellationToken);
|
|
manager.Blame(textureWrap, this.plugin);
|
|
return textureWrap;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public IEnumerable<IBitmapCodecInfo> GetSupportedImageDecoderInfos() =>
|
|
this.ManagerOrThrow.Wic.GetSupportedDecoderInfos();
|
|
|
|
/// <inheritdoc/>
|
|
public ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup)
|
|
{
|
|
var shared = this.ManagerOrThrow.Shared.GetFromGameIcon(lookup);
|
|
shared.AddOwnerPlugin(this.plugin);
|
|
return shared;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ISharedImmediateTexture GetFromGame(string path)
|
|
{
|
|
var shared = this.ManagerOrThrow.Shared.GetFromGame(path);
|
|
shared.AddOwnerPlugin(this.plugin);
|
|
return shared;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ISharedImmediateTexture GetFromFile(string path)
|
|
{
|
|
var shared = this.ManagerOrThrow.Shared.GetFromFile(path);
|
|
shared.AddOwnerPlugin(this.plugin);
|
|
return shared;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ISharedImmediateTexture GetFromManifestResource(Assembly assembly, string name)
|
|
{
|
|
var shared = this.ManagerOrThrow.Shared.GetFromManifestResource(assembly, name);
|
|
shared.AddOwnerPlugin(this.plugin);
|
|
return shared;
|
|
}
|
|
|
|
/// <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);
|
|
}
|