mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Merge remote-tracking branch 'origin/master' into v9-rollup
This commit is contained in:
commit
b5dd771843
14 changed files with 1019 additions and 85 deletions
|
|
@ -380,7 +380,7 @@ namespace Dalamud.Injector
|
|||
startInfo.BootShowConsole = args.Contains("--console");
|
||||
startInfo.BootEnableEtw = args.Contains("--etw");
|
||||
startInfo.BootLogPath = GetLogPath(startInfo.LogPath, "dalamud.boot", startInfo.LogName);
|
||||
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save", "clr_failfast_hijack", "prevent_icmphandle_crashes" };
|
||||
startInfo.BootEnabledGameFixes = new List<string> { "prevent_devicechange_crashes", "disable_game_openprocess_access_check", "redirect_openprocess", "backup_userdata_save", "prevent_icmphandle_crashes" };
|
||||
startInfo.BootDotnetOpenProcessHookMode = 0;
|
||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox1") ? 1 : 0;
|
||||
startInfo.BootWaitMessageBox |= args.Contains("--msgbox2") ? 2 : 0;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
|
|
@ -16,9 +17,11 @@ using JetBrains.Annotations;
|
|||
using Lumina;
|
||||
using Lumina.Data;
|
||||
using Lumina.Data.Files;
|
||||
using Lumina.Data.Parsing.Tex.Buffers;
|
||||
using Lumina.Excel;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using SharpDX.DXGI;
|
||||
|
||||
namespace Dalamud.Data;
|
||||
|
||||
|
|
@ -187,15 +190,17 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
|||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
/// TODO(v9): remove in api9 in favor of GetIcon(uint iconId, bool highResolution)
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(uint iconId)
|
||||
=> this.GetIcon(this.Language, iconId, false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(uint iconId, bool highResolution)
|
||||
=> this.GetIcon(this.Language, iconId, highResolution);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(bool isHq, uint iconId)
|
||||
{
|
||||
var type = isHq ? "hq/" : string.Empty;
|
||||
|
|
@ -208,11 +213,12 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
|||
/// <param name="iconLanguage">The requested language.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
/// TODO(v9): remove in api9 in favor of GetIcon(ClientLanguage iconLanguage, uint iconId, bool highResolution)
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId)
|
||||
=> this.GetIcon(iconLanguage, iconId, false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId, bool highResolution)
|
||||
{
|
||||
var type = iconLanguage switch
|
||||
|
|
@ -233,11 +239,12 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
|||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
/// TODO(v9): remove in api9 in favor of GetIcon(string? type, uint iconId, bool highResolution)
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(string? type, uint iconId)
|
||||
=> this.GetIcon(type, iconId, false);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(string? type, uint iconId, bool highResolution)
|
||||
{
|
||||
var format = highResolution ? HighResolutionIconFileFormat : IconFileFormat;
|
||||
|
|
@ -259,14 +266,39 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetHqIcon(uint iconId)
|
||||
=> this.GetIcon(true, iconId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
[return: NotNullIfNotNull(nameof(tex))]
|
||||
public TextureWrap? GetImGuiTexture(TexFile? tex)
|
||||
=> tex == null ? null : Service<InterfaceManager>.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4);
|
||||
{
|
||||
if (tex is null)
|
||||
return null;
|
||||
|
||||
var im = Service<InterfaceManager>.Get();
|
||||
var buffer = tex.TextureBuffer;
|
||||
var bpp = 1 << (((int)tex.Header.Format & (int)TexFile.TextureFormat.BppMask) >>
|
||||
(int)TexFile.TextureFormat.BppShift);
|
||||
|
||||
var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(tex.Header.Format, false);
|
||||
if (conversion != TexFile.DxgiFormatConversion.NoConversion || !im.SupportsDxgiFormat((Format)dxgiFormat))
|
||||
{
|
||||
dxgiFormat = (int)Format.B8G8R8A8_UNorm;
|
||||
buffer = buffer.Filter(0, 0, TexFile.TextureFormat.B8G8R8A8);
|
||||
bpp = 32;
|
||||
}
|
||||
|
||||
var pitch = buffer is BlockCompressionTextureBuffer
|
||||
? Math.Max(1, (buffer.Width + 3) / 4) * 2 * bpp
|
||||
: ((buffer.Width * bpp) + 7) / 8;
|
||||
return im.LoadImageFromDxgiFormat(buffer.RawData, pitch, buffer.Width, buffer.Height, (Format)dxgiFormat);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTexture(string path)
|
||||
=> this.GetImGuiTexture(this.GetFile<TexFile>(path));
|
||||
|
||||
|
|
@ -276,26 +308,32 @@ public sealed class DataManager : IDisposable, IServiceType, IDataManager
|
|||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
/// TODO(v9): remove in api9 in favor of GetImGuiTextureIcon(uint iconId, bool highResolution)
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconId, false));
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(uint iconId, bool highResolution)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconId, highResolution));
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(isHq, iconId));
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId));
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(string type, uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetIcon(type, iconId));
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureHqIcon(uint iconId)
|
||||
=> this.GetImGuiTexture(this.GetHqIcon(iconId));
|
||||
|
||||
|
|
|
|||
|
|
@ -26,52 +26,52 @@ public unsafe class Character : GameObject
|
|||
/// <summary>
|
||||
/// Gets the current HP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentHp => this.Struct->Health;
|
||||
public uint CurrentHp => this.Struct->CharacterData.Health;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum HP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxHp => this.Struct->MaxHealth;
|
||||
public uint MaxHp => this.Struct->CharacterData.MaxHealth;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current MP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentMp => this.Struct->Mana;
|
||||
public uint CurrentMp => this.Struct->CharacterData.Mana;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum MP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxMp => this.Struct->MaxMana;
|
||||
public uint MaxMp => this.Struct->CharacterData.MaxMana;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current GP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentGp => this.Struct->GatheringPoints;
|
||||
public uint CurrentGp => this.Struct->CharacterData.GatheringPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum GP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxGp => this.Struct->MaxGatheringPoints;
|
||||
public uint MaxGp => this.Struct->CharacterData.MaxGatheringPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current CP of this Chara.
|
||||
/// </summary>
|
||||
public uint CurrentCp => this.Struct->CraftingPoints;
|
||||
public uint CurrentCp => this.Struct->CharacterData.CraftingPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum CP of this Chara.
|
||||
/// </summary>
|
||||
public uint MaxCp => this.Struct->MaxCraftingPoints;
|
||||
public uint MaxCp => this.Struct->CharacterData.MaxCraftingPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClassJob of this Chara.
|
||||
/// </summary>
|
||||
public ExcelResolver<ClassJob> ClassJob => new(this.Struct->ClassJob);
|
||||
public ExcelResolver<ClassJob> ClassJob => new(this.Struct->CharacterData.ClassJob);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the level of this Chara.
|
||||
/// </summary>
|
||||
public byte Level => this.Struct->Level;
|
||||
public byte Level => this.Struct->CharacterData.Level;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a byte array describing the visual appearance of this Chara.
|
||||
|
|
@ -97,7 +97,7 @@ public unsafe class Character : GameObject
|
|||
/// <summary>
|
||||
/// Gets the current online status of the character.
|
||||
/// </summary>
|
||||
public ExcelResolver<OnlineStatus> OnlineStatus => new(this.Struct->OnlineStatus);
|
||||
public ExcelResolver<OnlineStatus> OnlineStatus => new(this.Struct->CharacterData.OnlineStatus);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status flags.
|
||||
|
|
|
|||
|
|
@ -4,11 +4,19 @@ using ImGuiScene;
|
|||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Base TextureWrap interface for all Dalamud-owned texture wraps.
|
||||
/// Used to avoid referencing ImGuiScene.
|
||||
/// </summary>
|
||||
public interface IDalamudTextureWrap : TextureWrap
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safety harness for ImGuiScene textures that will defer destruction until
|
||||
/// the end of the frame.
|
||||
/// </summary>
|
||||
public class DalamudTextureWrap : TextureWrap
|
||||
public class DalamudTextureWrap : IDalamudTextureWrap
|
||||
{
|
||||
private readonly TextureWrap wrappedWrap;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ using ImGuiNET;
|
|||
using ImGuiScene;
|
||||
using PInvoke;
|
||||
using Serilog;
|
||||
using SharpDX;
|
||||
using SharpDX.Direct3D;
|
||||
using SharpDX.Direct3D11;
|
||||
using SharpDX.DXGI;
|
||||
|
||||
// general dev notes, here because it's easiest
|
||||
|
||||
|
|
@ -303,6 +307,62 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the current D3D11 Device supports the given DXGI format.
|
||||
/// </summary>
|
||||
/// <param name="dxgiFormat">DXGI format to check.</param>
|
||||
/// <returns>Whether it is supported.</returns>
|
||||
public bool SupportsDxgiFormat(Format dxgiFormat) => this.scene is null
|
||||
? throw new InvalidOperationException("Scene isn't ready.")
|
||||
: this.scene.Device.CheckFormatSupport(dxgiFormat).HasFlag(FormatSupport.Texture2D);
|
||||
|
||||
/// <summary>
|
||||
/// Load an image from a span of bytes of specified format.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to load.</param>
|
||||
/// <param name="pitch">The pitch(stride) in bytes.</param>
|
||||
/// <param name="width">The width in pixels.</param>
|
||||
/// <param name="height">The height in pixels.</param>
|
||||
/// <param name="dxgiFormat">Format of the texture.</param>
|
||||
/// <returns>A texture, ready to use in ImGui.</returns>
|
||||
public TextureWrap LoadImageFromDxgiFormat(Span<byte> data, int pitch, int width, int height, Format dxgiFormat)
|
||||
{
|
||||
if (this.scene == null)
|
||||
throw new InvalidOperationException("Scene isn't ready.");
|
||||
|
||||
ShaderResourceView resView;
|
||||
unsafe
|
||||
{
|
||||
fixed (void* pData = data)
|
||||
{
|
||||
var texDesc = new Texture2DDescription
|
||||
{
|
||||
Width = width,
|
||||
Height = height,
|
||||
MipLevels = 1,
|
||||
ArraySize = 1,
|
||||
Format = dxgiFormat,
|
||||
SampleDescription = new(1, 0),
|
||||
Usage = ResourceUsage.Immutable,
|
||||
BindFlags = BindFlags.ShaderResource,
|
||||
CpuAccessFlags = CpuAccessFlags.None,
|
||||
OptionFlags = ResourceOptionFlags.None,
|
||||
};
|
||||
|
||||
using var texture = new Texture2D(this.Device, texDesc, new DataRectangle(new(pData), pitch));
|
||||
resView = new(this.Device, texture, new()
|
||||
{
|
||||
Format = texDesc.Format,
|
||||
Dimension = ShaderResourceViewDimension.Texture2D,
|
||||
Texture2D = { MipLevels = texDesc.MipLevels },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// no sampler for now because the ImGui implementation we copied doesn't allow for changing it
|
||||
return new D3DTextureWrap(resView, width, height);
|
||||
}
|
||||
|
||||
#nullable restore
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -609,12 +669,19 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags);
|
||||
|
||||
this.RenderImGui();
|
||||
this.DisposeTextures();
|
||||
|
||||
return pRes;
|
||||
}
|
||||
|
||||
this.RenderImGui();
|
||||
this.DisposeTextures();
|
||||
|
||||
return this.presentHook.Original(swapChain, syncInterval, presentFlags);
|
||||
}
|
||||
|
||||
private void DisposeTextures()
|
||||
{
|
||||
if (this.deferredDisposeTextures.Count > 0)
|
||||
{
|
||||
Log.Verbose("[IM] Disposing {Count} textures", this.deferredDisposeTextures.Count);
|
||||
|
|
@ -625,8 +692,6 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
|
||||
this.deferredDisposeTextures.Clear();
|
||||
}
|
||||
|
||||
return this.presentHook.Original(swapChain, syncInterval, presentFlags);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
|
|
|||
598
Dalamud/Interface/Internal/TextureManager.cs
Normal file
598
Dalamud/Interface/Internal/TextureManager.cs
Normal file
|
|
@ -0,0 +1,598 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using ImGuiScene;
|
||||
using Lumina.Data.Files;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for loading and disposing ImGui texture wraps.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITextureSubstitutionProvider>]
|
||||
#pragma warning restore SA1015
|
||||
internal class TextureManager : IDisposable, IServiceType, ITextureSubstitutionProvider
|
||||
{
|
||||
private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
|
||||
private const string HighResolutionIconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}_hr1.tex";
|
||||
|
||||
private const uint MillisecondsEvictionTime = 2000;
|
||||
|
||||
private static readonly ModuleLog Log = new("TEXM");
|
||||
|
||||
private readonly Framework framework;
|
||||
private readonly DataManager dataManager;
|
||||
private readonly InterfaceManager im;
|
||||
private readonly DalamudStartInfo startInfo;
|
||||
|
||||
private readonly Dictionary<string, TextureInfo> activeTextures = new();
|
||||
|
||||
private TextureWrap? fallbackTextureWrap;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextureManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="framework">Framework instance.</param>
|
||||
/// <param name="dataManager">DataManager instance.</param>
|
||||
/// <param name="im">InterfaceManager instance.</param>
|
||||
/// <param name="startInfo">DalamudStartInfo instance.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public TextureManager(Framework framework, DataManager dataManager, InterfaceManager im, DalamudStartInfo startInfo)
|
||||
{
|
||||
this.framework = framework;
|
||||
this.dataManager = dataManager;
|
||||
this.im = im;
|
||||
this.startInfo = startInfo;
|
||||
|
||||
this.framework.Update += this.FrameworkOnUpdate;
|
||||
|
||||
Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync().ContinueWith(_ => this.CreateFallbackTexture());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event ITextureSubstitutionProvider.TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for a specific icon.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The ID of the icon to load.</param>
|
||||
/// <param name="flags">Options to be considered when loading the icon.</param>
|
||||
/// <param name="language">
|
||||
/// The language to be considered when loading the icon, if the icon has versions for multiple languages.
|
||||
/// If null, default to the game's current language.
|
||||
/// </param>
|
||||
/// <param name="keepAlive">
|
||||
/// Prevent Dalamud from automatically unloading this icon to save memory. Usually does not need to be set.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Null, if the icon does not exist in the specified configuration, or a texture wrap that can be used
|
||||
/// to render the icon.
|
||||
/// </returns>
|
||||
public TextureManagerTextureWrap? GetIcon(uint iconId, ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes, ClientLanguage? language = null, bool keepAlive = false)
|
||||
{
|
||||
var path = this.GetIconPath(iconId, flags, language);
|
||||
return path == null ? null : this.CreateWrap(path, keepAlive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a path for a specific icon's .tex file.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The ID of the icon to look up.</param>
|
||||
/// <param name="flags">Options to be considered when loading the icon.</param>
|
||||
/// <param name="language">
|
||||
/// The language to be considered when loading the icon, if the icon has versions for multiple languages.
|
||||
/// If null, default to the game's current language.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Null, if the icon does not exist in the specified configuration, or the path to the texture's .tex file,
|
||||
/// which can be loaded via IDataManager.
|
||||
/// </returns>
|
||||
public string? GetIconPath(uint iconId, ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes, ClientLanguage? language = null)
|
||||
{
|
||||
var hiRes = flags.HasFlag(ITextureProvider.IconFlags.HiRes);
|
||||
|
||||
// 1. Item
|
||||
var path = FormatIconPath(
|
||||
iconId,
|
||||
flags.HasFlag(ITextureProvider.IconFlags.ItemHighQuality) ? "hq/" : string.Empty,
|
||||
hiRes);
|
||||
if (this.dataManager.FileExists(path))
|
||||
return path;
|
||||
|
||||
language ??= this.startInfo.Language;
|
||||
var languageFolder = language switch
|
||||
{
|
||||
ClientLanguage.Japanese => "ja/",
|
||||
ClientLanguage.English => "en/",
|
||||
ClientLanguage.German => "de/",
|
||||
ClientLanguage.French => "fr/",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(language), $"Unknown Language: {language}"),
|
||||
};
|
||||
|
||||
// 2. Regular icon, with language, hi-res
|
||||
path = FormatIconPath(
|
||||
iconId,
|
||||
languageFolder,
|
||||
hiRes);
|
||||
if (this.dataManager.FileExists(path))
|
||||
return path;
|
||||
|
||||
if (hiRes)
|
||||
{
|
||||
// 3. Regular icon, with language, no hi-res
|
||||
path = FormatIconPath(
|
||||
iconId,
|
||||
languageFolder,
|
||||
false);
|
||||
if (this.dataManager.FileExists(path))
|
||||
return path;
|
||||
}
|
||||
|
||||
// 4. Regular icon, without language, hi-res
|
||||
path = FormatIconPath(
|
||||
iconId,
|
||||
null,
|
||||
hiRes);
|
||||
if (this.dataManager.FileExists(path))
|
||||
return path;
|
||||
|
||||
// 4. Regular icon, without language, no hi-res
|
||||
if (hiRes)
|
||||
{
|
||||
path = FormatIconPath(
|
||||
iconId,
|
||||
null,
|
||||
false);
|
||||
if (this.dataManager.FileExists(path))
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the texture at the specified path.
|
||||
/// You may only specify paths in the game's VFS.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the texture in the game's VFS.</param>
|
||||
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
||||
/// <returns>Null, if the icon does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||
public TextureManagerTextureWrap? GetTextureFromGame(string path, bool keepAlive = false)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||
|
||||
if (Path.IsPathRooted(path))
|
||||
throw new ArgumentException("Use GetTextureFromFile() to load textures directly from a file.", nameof(path));
|
||||
|
||||
return !this.dataManager.FileExists(path) ? null : this.CreateWrap(path, keepAlive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the image or texture, specified by the passed FileInfo.
|
||||
/// You may only specify paths on the native file system.
|
||||
///
|
||||
/// This API can load .png and .tex files.
|
||||
/// </summary>
|
||||
/// <param name="file">The FileInfo describing the image or texture file.</param>
|
||||
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
||||
/// <returns>Null, if the file does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||
public TextureManagerTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(file);
|
||||
return !file.Exists ? null : this.CreateWrap(file.FullName, keepAlive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the specified Lumina TexFile.
|
||||
/// </summary>
|
||||
/// <param name="file">The texture to obtain a handle to.</param>
|
||||
/// <returns>A texture wrap that can be used to render the texture.</returns>
|
||||
public IDalamudTextureWrap? GetTexture(TexFile file)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(file);
|
||||
|
||||
if (!this.im.IsReady)
|
||||
throw new InvalidOperationException("Cannot create textures before scene is ready");
|
||||
|
||||
#pragma warning disable CS0618
|
||||
return this.dataManager.GetImGuiTexture(file) as IDalamudTextureWrap;
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.fallbackTextureWrap?.Dispose();
|
||||
this.framework.Update -= this.FrameworkOnUpdate;
|
||||
|
||||
Log.Verbose("Disposing {Num} left behind textures.");
|
||||
|
||||
foreach (var activeTexture in this.activeTextures)
|
||||
{
|
||||
activeTexture.Value.Wrap?.Dispose();
|
||||
}
|
||||
|
||||
this.activeTextures.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get texture info.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the texture.</param>
|
||||
/// <param name="refresh">Whether or not the texture should be reloaded if it was unloaded.</param>
|
||||
/// <param name="rethrow">
|
||||
/// If true, exceptions caused by texture load will not be caught.
|
||||
/// If false, exceptions will be caught and a dummy texture will be returned to prevent plugins from using invalid texture handles.
|
||||
/// </param>
|
||||
/// <returns>Info object storing texture metadata.</returns>
|
||||
internal TextureInfo GetInfo(string path, bool refresh = true, bool rethrow = false)
|
||||
{
|
||||
TextureInfo? info;
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
this.activeTextures.TryGetValue(path, out info);
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
info = new TextureInfo();
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
if (!this.activeTextures.TryAdd(path, info))
|
||||
Log.Warning("Texture {Path} tracked twice", path);
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh && info.KeepAliveCount == 0)
|
||||
info.LastAccess = DateTime.UtcNow;
|
||||
|
||||
if (info is { Wrap: not null })
|
||||
return info;
|
||||
|
||||
if (refresh)
|
||||
{
|
||||
if (!this.im.IsReady)
|
||||
throw new InvalidOperationException("Cannot create textures before scene is ready");
|
||||
|
||||
string? interceptPath = null;
|
||||
this.InterceptTexDataLoad?.Invoke(path, ref interceptPath);
|
||||
|
||||
if (interceptPath != null)
|
||||
{
|
||||
Log.Verbose("Intercept: {OriginalPath} => {ReplacePath}", path, interceptPath);
|
||||
path = interceptPath;
|
||||
}
|
||||
|
||||
TextureWrap? wrap;
|
||||
try
|
||||
{
|
||||
// We want to load this from the disk, probably, if the path has a root
|
||||
// Not sure if this can cause issues with e.g. network drives, might have to rethink
|
||||
// and add a flag instead if it does.
|
||||
if (Path.IsPathRooted(path))
|
||||
{
|
||||
if (Path.GetExtension(path) == ".tex")
|
||||
{
|
||||
// Attempt to load via Lumina
|
||||
var file = this.dataManager.GameData.GetFileFromDisk<TexFile>(path);
|
||||
wrap = this.GetTexture(file);
|
||||
Log.Verbose("Texture {Path} loaded FS via Lumina", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Attempt to load image
|
||||
wrap = this.im.LoadImage(path);
|
||||
Log.Verbose("Texture {Path} loaded FS via LoadImage", path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load regularly from dats
|
||||
var file = this.dataManager.GetFile<TexFile>(path);
|
||||
if (file == null)
|
||||
throw new Exception("Could not load TexFile from dat.");
|
||||
|
||||
wrap = this.GetTexture(file);
|
||||
Log.Verbose("Texture {Path} loaded from SqPack", path);
|
||||
}
|
||||
|
||||
if (wrap == null)
|
||||
throw new Exception("Could not create texture");
|
||||
|
||||
// TODO: We could support this, but I don't think it's worth it at the moment.
|
||||
var extents = new Vector2(wrap.Width, wrap.Height);
|
||||
if (info.Extents != Vector2.Zero && info.Extents != extents)
|
||||
Log.Warning("Texture at {Path} changed size between reloads, this is currently not supported.", path);
|
||||
|
||||
info.Extents = extents;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not load texture from {Path}", path);
|
||||
|
||||
// When creating the texture initially, we want to be able to pass errors back to the plugin
|
||||
if (rethrow)
|
||||
throw;
|
||||
|
||||
// This means that the load failed due to circumstances outside of our control,
|
||||
// and we can't do anything about it. Return a dummy texture so that the plugin still
|
||||
// has something to draw.
|
||||
wrap = this.fallbackTextureWrap;
|
||||
}
|
||||
|
||||
info.Wrap = wrap;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify the system about an instance of a texture wrap being disposed.
|
||||
/// If required conditions are met, the texture will be unloaded at the next update.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the texture.</param>
|
||||
/// <param name="keepAlive">Whether or not this handle was created in keep-alive mode.</param>
|
||||
internal void NotifyTextureDisposed(string path, bool keepAlive)
|
||||
{
|
||||
var info = this.GetInfo(path, false);
|
||||
info.RefCount--;
|
||||
|
||||
if (keepAlive)
|
||||
info.KeepAliveCount--;
|
||||
|
||||
// Clean it up by the next update. If it's re-requested in-between, we don't reload it.
|
||||
if (info.RefCount <= 0)
|
||||
info.LastAccess = default;
|
||||
}
|
||||
|
||||
private static string FormatIconPath(uint iconId, string? type, bool highResolution)
|
||||
{
|
||||
var format = highResolution ? HighResolutionIconFileFormat : IconFileFormat;
|
||||
|
||||
type ??= string.Empty;
|
||||
if (type.Length > 0 && !type.EndsWith("/"))
|
||||
type += "/";
|
||||
|
||||
return string.Format(format, iconId / 1000, type, iconId);
|
||||
}
|
||||
|
||||
private TextureManagerTextureWrap? CreateWrap(string path, bool keepAlive)
|
||||
{
|
||||
// This will create the texture.
|
||||
// That's fine, it's probably used immediately and this will let the plugin catch load errors.
|
||||
var info = this.GetInfo(path, rethrow: true);
|
||||
info.RefCount++;
|
||||
|
||||
if (keepAlive)
|
||||
info.KeepAliveCount++;
|
||||
|
||||
return new TextureManagerTextureWrap(path, info.Extents, keepAlive, this);
|
||||
}
|
||||
|
||||
private void FrameworkOnUpdate(Framework fw)
|
||||
{
|
||||
lock (this.activeTextures)
|
||||
{
|
||||
var toRemove = new List<string>();
|
||||
|
||||
foreach (var texInfo in this.activeTextures)
|
||||
{
|
||||
if (texInfo.Value.RefCount == 0)
|
||||
{
|
||||
Log.Verbose("Evicting {Path} since no refs", texInfo.Key);
|
||||
|
||||
Debug.Assert(texInfo.Value.KeepAliveCount == 0, "texInfo.Value.KeepAliveCount == 0");
|
||||
|
||||
texInfo.Value.Wrap?.Dispose();
|
||||
texInfo.Value.Wrap = null;
|
||||
toRemove.Add(texInfo.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (texInfo.Value.KeepAliveCount > 0 || texInfo.Value.Wrap == null)
|
||||
continue;
|
||||
|
||||
if (DateTime.UtcNow - texInfo.Value.LastAccess > TimeSpan.FromMilliseconds(MillisecondsEvictionTime))
|
||||
{
|
||||
Log.Verbose("Evicting {Path} since too old", texInfo.Key);
|
||||
texInfo.Value.Wrap.Dispose();
|
||||
texInfo.Value.Wrap = null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var path in toRemove)
|
||||
{
|
||||
this.activeTextures.Remove(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateFallbackTexture()
|
||||
{
|
||||
var fallbackTexBytes = new byte[] { 0xFF, 0x00, 0xDC, 0xFF };
|
||||
this.fallbackTextureWrap = this.im.LoadImageRaw(fallbackTexBytes, 1, 1, 4);
|
||||
Debug.Assert(this.fallbackTextureWrap != null, "this.fallbackTextureWrap != null");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal representation of a managed texture.
|
||||
/// </summary>
|
||||
internal class TextureInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the actual texture wrap. May be unpopulated.
|
||||
/// </summary>
|
||||
public TextureWrap? Wrap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time the texture was last accessed.
|
||||
/// </summary>
|
||||
public DateTime LastAccess { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of active holders of this texture.
|
||||
/// </summary>
|
||||
public uint RefCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of active holders that want this texture to stay alive forever.
|
||||
/// </summary>
|
||||
public uint KeepAliveCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the extents of the texture.
|
||||
/// </summary>
|
||||
public Vector2 Extents { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a texture manager.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<ITextureProvider>]
|
||||
#pragma warning restore SA1015
|
||||
internal class TextureManagerPluginScoped : ITextureProvider, IServiceType, IDisposable
|
||||
{
|
||||
private readonly TextureManager textureManager;
|
||||
|
||||
private readonly List<TextureManagerTextureWrap> trackedTextures = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextureManagerPluginScoped"/> class.
|
||||
/// </summary>
|
||||
/// <param name="textureManager">TextureManager instance.</param>
|
||||
public TextureManagerPluginScoped(TextureManager textureManager)
|
||||
{
|
||||
this.textureManager = textureManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap? GetIcon(
|
||||
uint iconId,
|
||||
ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.ItemHighQuality,
|
||||
ClientLanguage? language = null,
|
||||
bool keepAlive = false)
|
||||
{
|
||||
var wrap = this.textureManager.GetIcon(iconId, flags, language, keepAlive);
|
||||
if (wrap == null)
|
||||
return null;
|
||||
|
||||
this.trackedTextures.Add(wrap);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string? GetIconPath(uint iconId, ITextureProvider.IconFlags flags = ITextureProvider.IconFlags.HiRes, ClientLanguage? language = null)
|
||||
=> this.textureManager.GetIconPath(iconId, flags, language);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap? GetTextureFromGame(string path, bool keepAlive = false)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(path);
|
||||
|
||||
var wrap = this.textureManager.GetTextureFromGame(path, keepAlive);
|
||||
if (wrap == null)
|
||||
return null;
|
||||
|
||||
this.trackedTextures.Add(wrap);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(file);
|
||||
|
||||
var wrap = this.textureManager.GetTextureFromFile(file, keepAlive);
|
||||
if (wrap == null)
|
||||
return null;
|
||||
|
||||
this.trackedTextures.Add(wrap);
|
||||
return wrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap? GetTexture(TexFile file)
|
||||
=> this.textureManager.GetTexture(file);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// Dispose all leaked textures
|
||||
foreach (var textureWrap in this.trackedTextures.Where(x => !x.IsDisposed))
|
||||
{
|
||||
textureWrap.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrap.
|
||||
/// </summary>
|
||||
internal class TextureManagerTextureWrap : IDalamudTextureWrap
|
||||
{
|
||||
private readonly TextureManager manager;
|
||||
private readonly string path;
|
||||
private readonly bool keepAlive;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TextureManagerTextureWrap"/> class.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the texture.</param>
|
||||
/// <param name="extents">The extents of the texture.</param>
|
||||
/// <param name="keepAlive">Keep alive or not.</param>
|
||||
/// <param name="manager">Manager that we obtained this from.</param>
|
||||
internal TextureManagerTextureWrap(string path, Vector2 extents, bool keepAlive, TextureManager manager)
|
||||
{
|
||||
this.path = path;
|
||||
this.keepAlive = keepAlive;
|
||||
this.manager = manager;
|
||||
this.Width = (int)extents.X;
|
||||
this.Height = (int)extents.Y;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr ImGuiHandle => this.manager.GetInfo(this.path).Wrap!.ImGuiHandle;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Width { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Height { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this wrap has already been disposed.
|
||||
/// If true, the handle may be invalid.
|
||||
/// </summary>
|
||||
internal bool IsDisposed { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (!this.IsDisposed)
|
||||
this.manager.NotifyTextureDisposed(this.path, this.keepAlive);
|
||||
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Plugin.Services;
|
||||
using ImGuiNET;
|
||||
using ImGuiScene;
|
||||
using Serilog;
|
||||
|
|
@ -14,13 +15,18 @@ namespace Dalamud.Interface.Internal.Windows.Data;
|
|||
/// </summary>
|
||||
internal class TexWidget : IDataWindowWidget
|
||||
{
|
||||
private readonly List<TextureWrap> addedTextures = new();
|
||||
|
||||
private string iconId = "18";
|
||||
private bool hiRes = true;
|
||||
private bool hq = false;
|
||||
private bool keepAlive = false;
|
||||
private string inputTexPath = string.Empty;
|
||||
private TextureWrap? debugTex;
|
||||
private Vector2 inputTexUv0 = Vector2.Zero;
|
||||
private Vector2 inputTexUv1 = Vector2.One;
|
||||
private Vector4 inputTintCol = Vector4.One;
|
||||
private Vector2 inputTexScale = Vector2.Zero;
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DataKind DataKind { get; init; } = DataKind.Tex;
|
||||
|
||||
|
|
@ -36,34 +42,87 @@ internal class TexWidget : IDataWindowWidget
|
|||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var dataManager = Service<DataManager>.Get();
|
||||
var texManager = Service<TextureManager>.Get();
|
||||
|
||||
ImGui.InputText("Tex Path", ref this.inputTexPath, 255);
|
||||
ImGui.InputFloat2("UV0", ref this.inputTexUv0);
|
||||
ImGui.InputFloat2("UV1", ref this.inputTexUv1);
|
||||
ImGui.InputFloat4("Tint", ref this.inputTintCol);
|
||||
ImGui.InputFloat2("Scale", ref this.inputTexScale);
|
||||
|
||||
if (ImGui.Button("Load Tex"))
|
||||
ImGui.InputText("Icon ID", ref this.iconId, 32);
|
||||
ImGui.Checkbox("HQ Item", ref this.hq);
|
||||
ImGui.Checkbox("Hi-Res", ref this.hiRes);
|
||||
ImGui.Checkbox("Keep alive", ref this.keepAlive);
|
||||
if (ImGui.Button("Load Icon"))
|
||||
{
|
||||
try
|
||||
{
|
||||
this.debugTex = dataManager.GetImGuiTexture(this.inputTexPath);
|
||||
this.inputTexScale = new Vector2(this.debugTex?.Width ?? 0, this.debugTex?.Height ?? 0);
|
||||
var flags = ITextureProvider.IconFlags.None;
|
||||
if (this.hq)
|
||||
flags |= ITextureProvider.IconFlags.ItemHighQuality;
|
||||
|
||||
if (this.hiRes)
|
||||
flags |= ITextureProvider.IconFlags.HiRes;
|
||||
|
||||
this.addedTextures.Add(texManager.GetIcon(uint.Parse(this.iconId), flags, keepAlive: this.keepAlive));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not load tex");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.InputText("Tex Path", ref this.inputTexPath, 255);
|
||||
if (ImGui.Button("Load Tex"))
|
||||
{
|
||||
try
|
||||
{
|
||||
this.addedTextures.Add(texManager.GetTextureFromGame(this.inputTexPath, this.keepAlive));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not load tex");
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui.Button("Load File"))
|
||||
{
|
||||
try
|
||||
{
|
||||
this.addedTextures.Add(texManager.GetTextureFromFile(new FileInfo(this.inputTexPath), this.keepAlive));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Could not load tex");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.InputFloat2("UV0", ref this.inputTexUv0);
|
||||
ImGui.InputFloat2("UV1", ref this.inputTexUv1);
|
||||
ImGui.InputFloat4("Tint", ref this.inputTintCol);
|
||||
ImGui.InputFloat2("Scale", ref this.inputTexScale);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10);
|
||||
|
||||
if (this.debugTex != null)
|
||||
TextureWrap? toRemove = null;
|
||||
for (var i = 0; i < this.addedTextures.Count; i++)
|
||||
{
|
||||
ImGui.Image(this.debugTex.ImGuiHandle, this.inputTexScale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol);
|
||||
ImGuiHelpers.ScaledDummy(5);
|
||||
Util.ShowObject(this.debugTex);
|
||||
if (ImGui.CollapsingHeader($"Tex #{i}"))
|
||||
{
|
||||
var tex = this.addedTextures[i];
|
||||
|
||||
var scale = new Vector2(tex.Width, tex.Height);
|
||||
if (this.inputTexScale != Vector2.Zero)
|
||||
scale = this.inputTexScale;
|
||||
|
||||
ImGui.Image(tex.ImGuiHandle, scale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol);
|
||||
|
||||
if (ImGui.Button($"X##{i}"))
|
||||
toRemove = tex;
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
toRemove.Dispose();
|
||||
this.addedTextures.Remove(toRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -569,7 +569,12 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
this.filterText = selectable.Localization;
|
||||
|
||||
lock (this.listLock)
|
||||
{
|
||||
this.ResortPlugins();
|
||||
|
||||
// Positions of plugins within the list is likely to change
|
||||
this.openPluginCollapsibles.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2963,7 +2968,18 @@ internal class PluginInstallerWindow : Window, IDisposable
|
|||
break;
|
||||
case PluginSortKind.LastUpdate:
|
||||
this.pluginListAvailable.Sort((p1, p2) => p2.LastUpdate.CompareTo(p1.LastUpdate));
|
||||
this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.LastUpdate.CompareTo(p1.Manifest.LastUpdate));
|
||||
this.pluginListInstalled.Sort((p1, p2) =>
|
||||
{
|
||||
// We need to get remote manifests here, as the local manifests will have the time when the current version is installed,
|
||||
// not the actual time of the last update, as the plugin may be pending an update
|
||||
IPluginManifest? p2Considered = this.pluginListAvailable.FirstOrDefault(x => x.InternalName == p2.InternalName);
|
||||
p2Considered ??= p2.Manifest;
|
||||
|
||||
IPluginManifest? p1Considered = this.pluginListAvailable.FirstOrDefault(x => x.InternalName == p1.InternalName);
|
||||
p1Considered ??= p1.Manifest;
|
||||
|
||||
return p2Considered.LastUpdate.CompareTo(p1Considered.LastUpdate);
|
||||
});
|
||||
break;
|
||||
case PluginSortKind.NewOrNot:
|
||||
this.pluginListAvailable.Sort((p1, p2) => this.WasPluginSeen(p1.InternalName)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ namespace Dalamud.IoC.Internal;
|
|||
|
||||
/// <summary>
|
||||
/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution.
|
||||
///
|
||||
/// This is only used to resolve dependencies for plugins.
|
||||
/// Dalamud services are constructed via Service{T}.ConstructObject at the moment.
|
||||
/// </summary>
|
||||
internal class ServiceContainer : IServiceProvider, IServiceType
|
||||
{
|
||||
|
|
@ -31,7 +34,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
|||
/// Register a singleton object of any type into the current IOC container.
|
||||
/// </summary>
|
||||
/// <param name="instance">The existing instance to register in the container.</param>
|
||||
/// <typeparam name="T">The interface to register.</typeparam>
|
||||
/// <typeparam name="T">The type to register.</typeparam>
|
||||
public void RegisterSingleton<T>(Task<T> instance)
|
||||
{
|
||||
if (instance == null)
|
||||
|
|
@ -40,19 +43,27 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
|||
}
|
||||
|
||||
this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T));
|
||||
this.RegisterInterfaces(typeof(T));
|
||||
}
|
||||
|
||||
var resolveViaTypes = typeof(T)
|
||||
.GetCustomAttributes()
|
||||
.OfType<ResolveViaAttribute>()
|
||||
.Select(x => x.GetType().GetGenericArguments().First());
|
||||
/// <summary>
|
||||
/// Register the interfaces that can resolve this type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to register.</param>
|
||||
public void RegisterInterfaces(Type type)
|
||||
{
|
||||
var resolveViaTypes = type
|
||||
.GetCustomAttributes()
|
||||
.OfType<ResolveViaAttribute>()
|
||||
.Select(x => x.GetType().GetGenericArguments().First());
|
||||
foreach (var resolvableType in resolveViaTypes)
|
||||
{
|
||||
Log.Verbose("=> {InterfaceName} provides for {TName}", resolvableType.FullName ?? "???", typeof(T).FullName ?? "???");
|
||||
Log.Verbose("=> {InterfaceName} provides for {TName}", resolvableType.FullName ?? "???", type.FullName ?? "???");
|
||||
|
||||
Debug.Assert(!this.interfaceToTypeMap.ContainsKey(resolvableType), "A service already implements this interface, this is not allowed");
|
||||
Debug.Assert(typeof(T).IsAssignableTo(resolvableType), "Service does not inherit from indicated ResolveVia type");
|
||||
Debug.Assert(type.IsAssignableTo(resolvableType), "Service does not inherit from indicated ResolveVia type");
|
||||
|
||||
this.interfaceToTypeMap[resolvableType] = typeof(T);
|
||||
this.interfaceToTypeMap[resolvableType] = type;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,18 +106,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
|||
parameters
|
||||
.Select(async p =>
|
||||
{
|
||||
if (p.parameterType.GetCustomAttribute<ServiceManager.ScopedService>() != null)
|
||||
{
|
||||
if (scopeImpl == null)
|
||||
{
|
||||
Log.Error("Failed to create {TypeName}, depends on scoped service but no scope", objectType.FullName!);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await scopeImpl.CreatePrivateScopedObject(p.parameterType, scopedObjects);
|
||||
}
|
||||
|
||||
var service = await this.GetService(p.parameterType, scopedObjects);
|
||||
var service = await this.GetService(p.parameterType, scopeImpl, scopedObjects);
|
||||
|
||||
if (service == null)
|
||||
{
|
||||
|
|
@ -168,22 +168,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
|||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
object service = null;
|
||||
|
||||
if (prop.propertyInfo.PropertyType.GetCustomAttribute<ServiceManager.ScopedService>() != null)
|
||||
{
|
||||
if (scopeImpl == null)
|
||||
{
|
||||
Log.Error("Failed to create {TypeName}, depends on scoped service but no scope", objectType.FullName!);
|
||||
}
|
||||
else
|
||||
{
|
||||
service = await scopeImpl.CreatePrivateScopedObject(prop.propertyInfo.PropertyType, publicScopes);
|
||||
}
|
||||
}
|
||||
|
||||
service ??= await this.GetService(prop.propertyInfo.PropertyType, publicScopes);
|
||||
|
||||
var service = await this.GetService(prop.propertyInfo.PropertyType, scopeImpl, publicScopes);
|
||||
if (service == null)
|
||||
{
|
||||
Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName!);
|
||||
|
|
@ -203,7 +188,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
|||
public IServiceScope GetScope() => new ServiceScopeImpl(this);
|
||||
|
||||
/// <inheritdoc/>
|
||||
object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType);
|
||||
object? IServiceProvider.GetService(Type serviceType) => this.GetSingletonService(serviceType);
|
||||
|
||||
private static bool CheckInterfaceVersion(RequiredVersionAttribute? requiredVersion, Type parameterType)
|
||||
{
|
||||
|
|
@ -228,9 +213,23 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
|||
return false;
|
||||
}
|
||||
|
||||
private async Task<object?> GetService(Type serviceType, object[] scopedObjects)
|
||||
private async Task<object?> GetService(Type serviceType, ServiceScopeImpl? scope, object[] scopedObjects)
|
||||
{
|
||||
var singletonService = await this.GetService(serviceType);
|
||||
if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
|
||||
serviceType = implementingType;
|
||||
|
||||
if (serviceType.GetCustomAttribute<ServiceManager.ScopedService>() != null)
|
||||
{
|
||||
if (scope == null)
|
||||
{
|
||||
Log.Error("Failed to create {TypeName}, is scoped but no scope provided", serviceType.FullName!);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await scope.CreatePrivateScopedObject(serviceType, scopedObjects);
|
||||
}
|
||||
|
||||
var singletonService = await this.GetSingletonService(serviceType, false);
|
||||
if (singletonService != null)
|
||||
{
|
||||
return singletonService;
|
||||
|
|
@ -246,9 +245,9 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
|||
return scoped;
|
||||
}
|
||||
|
||||
private async Task<object?> GetService(Type serviceType)
|
||||
private async Task<object?> GetSingletonService(Type serviceType, bool tryGetInterface = true)
|
||||
{
|
||||
if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
|
||||
if (tryGetInterface && this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
|
||||
serviceType = implementingType;
|
||||
|
||||
if (!this.instances.TryGetValue(serviceType, out var service))
|
||||
|
|
@ -285,13 +284,24 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
|||
|
||||
private bool ValidateCtor(ConstructorInfo ctor, Type[] types)
|
||||
{
|
||||
bool IsTypeValid(Type type)
|
||||
{
|
||||
var contains = types.Any(x => x.IsAssignableTo(type));
|
||||
|
||||
// Scoped services are created on-demand
|
||||
return contains || type.GetCustomAttribute<ServiceManager.ScopedService>() != null;
|
||||
}
|
||||
|
||||
var parameters = ctor.GetParameters();
|
||||
foreach (var parameter in parameters)
|
||||
{
|
||||
var contains = types.Any(x => x.IsAssignableTo(parameter.ParameterType));
|
||||
var valid = IsTypeValid(parameter.ParameterType);
|
||||
|
||||
// If this service is provided by an interface
|
||||
if (!valid && this.interfaceToTypeMap.TryGetValue(parameter.ParameterType, out var implementationType))
|
||||
valid = IsTypeValid(implementationType);
|
||||
|
||||
// Scoped services are created on-demand
|
||||
if (!contains && parameter.ParameterType.GetCustomAttribute<ServiceManager.ScopedService>() == null)
|
||||
if (!valid)
|
||||
{
|
||||
Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName!);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System.Collections.ObjectModel;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using ImGuiScene;
|
||||
using Lumina;
|
||||
|
|
@ -86,6 +88,7 @@ public interface IDataManager
|
|||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <param name="highResolution">Return high resolution version.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(uint iconId, bool highResolution = false);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -95,6 +98,7 @@ public interface IDataManager
|
|||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <param name="highResolution">Return high resolution version.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId, bool highResolution = false);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -104,6 +108,7 @@ public interface IDataManager
|
|||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <param name="highResolution">Return high resolution version.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(string? type, uint iconId, bool highResolution = false);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -112,6 +117,7 @@ public interface IDataManager
|
|||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <param name="highResolution">Return the high resolution version.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(uint iconId, bool highResolution = false);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -120,6 +126,7 @@ public interface IDataManager
|
|||
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetIcon(bool isHq, uint iconId);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -127,6 +134,7 @@ public interface IDataManager
|
|||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TexFile"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TexFile? GetHqIcon(uint iconId);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -134,6 +142,8 @@ public interface IDataManager
|
|||
/// </summary>
|
||||
/// <param name="tex">The Lumina <see cref="TexFile"/>.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
[return: NotNullIfNotNull(nameof(tex))]
|
||||
public TextureWrap? GetImGuiTexture(TexFile? tex);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -141,6 +151,7 @@ public interface IDataManager
|
|||
/// </summary>
|
||||
/// <param name="path">The internal path to the texture.</param>
|
||||
/// <returns>A <see cref="TextureWrap"/> that can be used to draw the texture.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTexture(string path);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -149,6 +160,7 @@ public interface IDataManager
|
|||
/// <param name="isHq">A value indicating whether the icon should be HQ.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -157,6 +169,7 @@ public interface IDataManager
|
|||
/// <param name="iconLanguage">The requested language.</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -165,6 +178,7 @@ public interface IDataManager
|
|||
/// <param name="type">The type of the icon (e.g. 'hq' to get the HQ variant of an item icon).</param>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureIcon(string type, uint iconId);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -172,5 +186,6 @@ public interface IDataManager
|
|||
/// </summary>
|
||||
/// <param name="iconId">The icon ID.</param>
|
||||
/// <returns>The <see cref="TextureWrap"/> containing the icon.</returns>
|
||||
[Obsolete("Use ITextureProvider instead")]
|
||||
public TextureWrap? GetImGuiTextureHqIcon(uint iconId);
|
||||
}
|
||||
|
|
|
|||
96
Dalamud/Plugin/Services/ITextureProvider.cs
Normal file
96
Dalamud/Plugin/Services/ITextureProvider.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Lumina.Data.Files;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service that grants you access to textures you may render via ImGui.
|
||||
/// </summary>
|
||||
public interface ITextureProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags describing the icon you wish to receive.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum IconFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// Low-resolution, standard quality icon.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// If this icon is an item icon, and it has a high-quality variant, receive the high-quality version.
|
||||
/// Null if the item does not have a high-quality variant.
|
||||
/// </summary>
|
||||
ItemHighQuality = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Get the hi-resolution version of the icon, if it exists.
|
||||
/// </summary>
|
||||
HiRes = 1 << 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for a specific icon.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The ID of the icon to load.</param>
|
||||
/// <param name="flags">Options to be considered when loading the icon.</param>
|
||||
/// <param name="language">
|
||||
/// The language to be considered when loading the icon, if the icon has versions for multiple languages.
|
||||
/// If null, default to the game's current language.
|
||||
/// </param>
|
||||
/// <param name="keepAlive">
|
||||
/// Prevent Dalamud from automatically unloading this icon to save memory. Usually does not need to be set.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Null, if the icon does not exist in the specified configuration, or a texture wrap that can be used
|
||||
/// to render the icon.
|
||||
/// </returns>
|
||||
public IDalamudTextureWrap? GetIcon(uint iconId, IconFlags flags = IconFlags.HiRes, ClientLanguage? language = null, bool keepAlive = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a path for a specific icon's .tex file.
|
||||
/// </summary>
|
||||
/// <param name="iconId">The ID of the icon to look up.</param>
|
||||
/// <param name="flags">Options to be considered when loading the icon.</param>
|
||||
/// <param name="language">
|
||||
/// The language to be considered when loading the icon, if the icon has versions for multiple languages.
|
||||
/// If null, default to the game's current language.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Null, if the icon does not exist in the specified configuration, or the path to the texture's .tex file,
|
||||
/// which can be loaded via IDataManager.
|
||||
/// </returns>
|
||||
public string? GetIconPath(uint iconId, IconFlags flags = IconFlags.HiRes, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the texture at the specified path.
|
||||
/// You may only specify paths in the game's VFS.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the texture in the game's VFS.</param>
|
||||
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
||||
/// <returns>Null, if the icon does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||
public IDalamudTextureWrap? GetTextureFromGame(string path, bool keepAlive = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the image or texture, specified by the passed FileInfo.
|
||||
/// You may only specify paths on the native file system.
|
||||
///
|
||||
/// This API can load .png and .tex files.
|
||||
/// </summary>
|
||||
/// <param name="file">The FileInfo describing the image or texture file.</param>
|
||||
/// <param name="keepAlive">Prevent Dalamud from automatically unloading this texture to save memory. Usually does not need to be set.</param>
|
||||
/// <returns>Null, if the file does not exist, or a texture wrap that can be used to render the texture.</returns>
|
||||
public IDalamudTextureWrap? GetTextureFromFile(FileInfo file, bool keepAlive = false);
|
||||
|
||||
/// <summary>
|
||||
/// Get a texture handle for the specified Lumina TexFile.
|
||||
/// </summary>
|
||||
/// <param name="file">The texture to obtain a handle to.</param>
|
||||
/// <returns>A texture wrap that can be used to render the texture.</returns>
|
||||
public IDalamudTextureWrap GetTexture(TexFile file);
|
||||
}
|
||||
20
Dalamud/Plugin/Services/ITextureSubstitutionProvider.cs
Normal file
20
Dalamud/Plugin/Services/ITextureSubstitutionProvider.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service that grants you the ability to replace texture data that is to be loaded by Dalamud.
|
||||
/// </summary>
|
||||
public interface ITextureSubstitutionProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate describing a function that may be used to intercept and replace texture data.
|
||||
/// The path assigned may point to another texture inside the game's dats, or a .tex file or image on the disk.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the texture that is to be loaded.</param>
|
||||
/// <param name="replacementPath">The path that should be loaded instead.</param>
|
||||
public delegate void TextureDataInterceptorDelegate(string path, ref string? replacementPath);
|
||||
|
||||
/// <summary>
|
||||
/// Event that will be called once Dalamud wants to load texture data.
|
||||
/// </summary>
|
||||
public event TextureDataInterceptorDelegate? InterceptTexDataLoad;
|
||||
}
|
||||
|
|
@ -132,11 +132,20 @@ internal static class ServiceManager
|
|||
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
|
||||
var getAsyncTaskMap = new Dictionary<Type, Task>();
|
||||
|
||||
var serviceContainer = Service<ServiceContainer>.Get();
|
||||
|
||||
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
|
||||
{
|
||||
var serviceKind = serviceType.GetServiceKind();
|
||||
if (serviceKind is ServiceKind.None or ServiceKind.ScopedService)
|
||||
if (serviceKind is ServiceKind.None)
|
||||
continue;
|
||||
|
||||
// Scoped service do not go through Service<T>, so we must let ServiceContainer know what their interfaces map to
|
||||
if (serviceKind is ServiceKind.ScopedService)
|
||||
{
|
||||
serviceContainer.RegisterInterfaces(serviceType);
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.Assert(
|
||||
!serviceKind.HasFlag(ServiceKind.ManualService) && !serviceKind.HasFlag(ServiceKind.ScopedService),
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 8962a47b95f96bec53e58680bd9d1e7f38610d40
|
||||
Subproject commit 782d7317176f232d7108b2b3a4cb75de67fc3a8a
|
||||
Loading…
Add table
Add a link
Reference in a new issue