mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Add DalamudAssetManager
This commit is contained in:
parent
01153a2480
commit
a72f407357
17 changed files with 867 additions and 179 deletions
146
Dalamud/DalamudAsset.cs
Normal file
146
Dalamud/DalamudAsset.cs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
using Dalamud.Storage.Assets;
|
||||
|
||||
namespace Dalamud;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies an asset that has been shipped as Dalamud Asset.<br />
|
||||
/// <strong>Any asset can cease to exist at any point, even if the enum value exists.</strong><br />
|
||||
/// Either ship your own assets, or be prepared for errors.
|
||||
/// </summary>
|
||||
public enum DalamudAsset
|
||||
{
|
||||
/// <summary>
|
||||
/// Nothing.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.Empty, data: new byte[0])]
|
||||
Unspecified = 0,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromRaw"/>: The fallback empty texture.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromRaw, data: new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 })]
|
||||
[DalamudAssetRawTexture(4, 8, 4, SharpDX.DXGI.Format.BC1_UNorm)]
|
||||
Empty4X4 = 1000,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The Dalamud logo.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "logo.png")]
|
||||
Logo = 1001,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The Dalamud logo, but smaller.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "tsmLogo.png")]
|
||||
LogoSmall = 1002,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The default plugin icon.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "defaultIcon.png")]
|
||||
DefaultIcon = 1003,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The disabled plugin icon.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "disabledIcon.png")]
|
||||
DisabledIcon = 1004,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The outdated installable plugin icon.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "outdatedInstallableIcon.png")]
|
||||
OutdatedInstallableIcon = 1005,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin trouble icon overlay.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "troubleIcon.png")]
|
||||
TroubleIcon = 1006,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin update icon overlay.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "updateIcon.png")]
|
||||
UpdateIcon = 1007,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The plugin installed icon overlay.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "installedIcon.png")]
|
||||
InstalledIcon = 1008,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The third party plugin icon overlay.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "thirdIcon.png")]
|
||||
ThirdIcon = 1009,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The installed third party plugin icon overlay.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "thirdInstalledIcon.png")]
|
||||
ThirdInstalledIcon = 1010,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The API bump explainer icon.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "changelogApiBump.png")]
|
||||
ChangelogApiBumpIcon = 1011,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.TextureFromPng"/>: The background shade for
|
||||
/// <see cref="Interface.Internal.Windows.TitleScreenMenuWindow"/>.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.TextureFromPng)]
|
||||
[DalamudAssetPath("UIRes", "tsmShade.png")]
|
||||
TitleScreenMenuShade = 1012,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK JP Medium.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||
[DalamudAssetPath("UIRes", "NotoSansCJKjp-Regular.otf")]
|
||||
[DalamudAssetPath("UIRes", "NotoSansCJKjp-Medium.otf")]
|
||||
NotoSansJpMedium = 2000,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.Font"/>: Noto Sans CJK KR Regular.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||
[DalamudAssetPath("UIRes", "NotoSansCJKkr-Regular.otf")]
|
||||
[DalamudAssetPath("UIRes", "NotoSansKR-Regular.otf")]
|
||||
NotoSansKrRegular = 2001,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.Font"/>: Inconsolata Regular.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||
[DalamudAssetPath("UIRes", "Inconsolata-Regular.ttf")]
|
||||
InconsolataRegular = 2002,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.Font"/>: FontAwesome Free Solid.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.Font)]
|
||||
[DalamudAssetPath("UIRes", "FontAwesomeFreeSolid.otf")]
|
||||
FontAwesomeFreeSolid = 2003,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DalamudAssetPurpose.Font"/>: Game symbol fonts being used as webfonts at Lodestone.
|
||||
/// </summary>
|
||||
[DalamudAsset(DalamudAssetPurpose.Font, required: true)]
|
||||
[DalamudAssetOnlineSource("https://img.finalfantasyxiv.com/lds/pc/global/fonts/FFXIV_Lodestone_SSF.ttf")]
|
||||
LodestoneGameSymbol = 2004,
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
using System.IO;
|
||||
|
||||
using Dalamud.IoC.Internal;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing various textures used by Dalamud windows for branding purposes.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[InherentDependency<InterfaceManager.InterfaceManagerWithScene>] // Can't load textures before this
|
||||
#pragma warning restore SA1015
|
||||
internal class Branding : IServiceType, IDisposable
|
||||
{
|
||||
private readonly Dalamud dalamud;
|
||||
private readonly TextureManager tm;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Branding"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dalamud">Dalamud instance.</param>
|
||||
/// <param name="tm">TextureManager instance.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public Branding(Dalamud dalamud, TextureManager tm)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
this.tm = tm;
|
||||
|
||||
this.LoadTextures();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a full-size Dalamud logo texture.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap Logo { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a small Dalamud logo texture.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap LogoSmall { get; private set; } = null!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.Logo.Dispose();
|
||||
this.LogoSmall.Dispose();
|
||||
}
|
||||
|
||||
private void LoadTextures()
|
||||
{
|
||||
this.Logo = this.tm.GetTextureFromFile(new FileInfo(Path.Combine(this.dalamud.AssetDirectory.FullName, "UIRes", "logo.png")))
|
||||
?? throw new Exception("Could not load logo.");
|
||||
|
||||
this.LogoSmall = this.tm.GetTextureFromFile(new FileInfo(Path.Combine(this.dalamud.AssetDirectory.FullName, "UIRes", "tsmLogo.png")))
|
||||
?? throw new Exception("Could not load TSM logo.");
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ using Dalamud.Interface.Utility.Raii;
|
|||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
|
|
@ -93,7 +94,7 @@ internal class DalamudInterface : IDisposable, IServiceType
|
|||
DalamudConfiguration configuration,
|
||||
InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene,
|
||||
PluginImageCache pluginImageCache,
|
||||
Branding branding,
|
||||
DalamudAssetManager dalamudAssetManager,
|
||||
Game.Framework framework,
|
||||
ClientState clientState,
|
||||
TitleScreenMenu titleScreenMenu,
|
||||
|
|
@ -118,11 +119,10 @@ internal class DalamudInterface : IDisposable, IServiceType
|
|||
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
|
||||
this.titleScreenMenuWindow = new TitleScreenMenuWindow(
|
||||
clientState,
|
||||
dalamud,
|
||||
configuration,
|
||||
dalamudAssetManager,
|
||||
framework,
|
||||
gameGui,
|
||||
this.interfaceManager,
|
||||
titleScreenMenu) { IsOpen = false };
|
||||
this.changelogWindow = new ChangelogWindow(this.titleScreenMenuWindow) { IsOpen = false };
|
||||
this.profilerWindow = new ProfilerWindow() { IsOpen = false };
|
||||
|
|
@ -152,12 +152,21 @@ internal class DalamudInterface : IDisposable, IServiceType
|
|||
this.interfaceManager.Draw += this.OnDraw;
|
||||
|
||||
var tsm = Service<TitleScreenMenu>.Get();
|
||||
tsm.AddEntryCore(Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), branding.LogoSmall, () => this.OpenPluginInstaller());
|
||||
tsm.AddEntryCore(Loc.Localize("TSMDalamudSettings", "Dalamud Settings"), branding.LogoSmall, this.OpenSettings);
|
||||
tsm.AddEntryCore(
|
||||
Loc.Localize("TSMDalamudPlugins", "Plugin Installer"),
|
||||
dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall),
|
||||
this.OpenPluginInstaller);
|
||||
tsm.AddEntryCore(
|
||||
Loc.Localize("TSMDalamudSettings", "Dalamud Settings"),
|
||||
dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall),
|
||||
this.OpenSettings);
|
||||
|
||||
if (!configuration.DalamudBetaKind.IsNullOrEmpty())
|
||||
{
|
||||
tsm.AddEntryCore(Loc.Localize("TSMDalamudDevMenu", "Developer Menu"), branding.LogoSmall, () => this.isImGuiDrawDevMenu = true);
|
||||
tsm.AddEntryCore(
|
||||
Loc.Localize("TSMDalamudDevMenu", "Developer Menu"),
|
||||
dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall),
|
||||
() => this.isImGuiDrawDevMenu = true);
|
||||
}
|
||||
|
||||
this.creditsDarkeningAnimation.Point1 = Vector2.Zero;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using Dalamud.Interface.Internal.Notifications;
|
|||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Timing;
|
||||
using ImGuiNET;
|
||||
|
|
@ -1063,10 +1064,15 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady]
|
||||
private void ContinueConstruction(TargetSigScanner sigScanner, Framework framework)
|
||||
private void ContinueConstruction(
|
||||
TargetSigScanner sigScanner,
|
||||
DalamudAssetManager dalamudAssetManager,
|
||||
DalamudConfiguration configuration)
|
||||
{
|
||||
dalamudAssetManager.WaitForAllRequiredAssets().Wait();
|
||||
|
||||
this.address.Setup(sigScanner);
|
||||
framework.RunOnFrameworkThread(() =>
|
||||
this.framework.RunOnFrameworkThread(() =>
|
||||
{
|
||||
while ((this.GameWindowHandle = NativeFunctions.FindWindowEx(IntPtr.Zero, this.GameWindowHandle, "FFXIVGAME", IntPtr.Zero)) != IntPtr.Zero)
|
||||
{
|
||||
|
|
@ -1078,7 +1084,7 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
|
||||
try
|
||||
{
|
||||
if (Service<DalamudConfiguration>.Get().WindowIsImmersive)
|
||||
if (configuration.WindowIsImmersive)
|
||||
this.SetImmersiveMode(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Dalamud.Interface.Utility;
|
|||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -32,7 +33,6 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
";
|
||||
|
||||
private readonly TitleScreenMenuWindow tsmWindow;
|
||||
private readonly IDalamudTextureWrap logoTexture;
|
||||
|
||||
private readonly InOutCubic windowFade = new(TimeSpan.FromSeconds(2.5f))
|
||||
{
|
||||
|
|
@ -47,6 +47,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
};
|
||||
|
||||
private IDalamudTextureWrap? apiBumpExplainerTexture;
|
||||
private IDalamudTextureWrap? logoTexture;
|
||||
private GameFontHandle? bannerFont;
|
||||
|
||||
private State state = State.WindowFadeIn;
|
||||
|
|
@ -63,8 +64,6 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
this.tsmWindow = tsmWindow;
|
||||
this.Namespace = "DalamudChangelogWindow";
|
||||
|
||||
this.logoTexture = Service<Branding>.Get().Logo;
|
||||
|
||||
// If we are going to show a changelog, make sure we have the font ready, otherwise it will hitch
|
||||
if (WarrantsChangelog())
|
||||
Service<GameFontManager>.GetAsync().ContinueWith(t => this.MakeFont(t.Result));
|
||||
|
|
@ -188,6 +187,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, Math.Clamp(this.windowFade.EasedPoint.X - 0.5f, 0f, 1f)))
|
||||
{
|
||||
this.logoTexture ??= Service<DalamudAssetManager>.Get().GetDalamudTextureWrap(DalamudAsset.Logo);
|
||||
ImGui.Image(this.logoTexture.ImGuiHandle, logoSize);
|
||||
}
|
||||
}
|
||||
|
|
@ -376,7 +376,6 @@ internal sealed class ChangelogWindow : Window, IDisposable
|
|||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
this.logoTexture.Dispose();
|
||||
}
|
||||
|
||||
private void MakeFont(GameFontManager gfm) =>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
|
@ -12,8 +11,8 @@ using Dalamud.Networking.Http;
|
|||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
using Dalamud.Plugin.Internal.Types.Manifest;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiScene;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows;
|
||||
|
|
@ -47,12 +46,6 @@ internal class PluginImageCache : IDisposable, IServiceType
|
|||
private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api6/{0}/{1}/images/{2}";
|
||||
private const string MainRepoDip17ImageUrl = "https://raw.githubusercontent.com/goatcorp/PluginDistD17/main/{0}/{1}/images/{2}";
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly InterfaceManager.InterfaceManagerWithScene imWithScene = Service<InterfaceManager.InterfaceManagerWithScene>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Branding branding = Service<Branding>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get();
|
||||
|
||||
|
|
@ -64,35 +57,12 @@ internal class PluginImageCache : IDisposable, IServiceType
|
|||
|
||||
private readonly ConcurrentDictionary<string, IDalamudTextureWrap?> pluginIconMap = new();
|
||||
private readonly ConcurrentDictionary<string, IDalamudTextureWrap?[]?> pluginImagesMap = new();
|
||||
|
||||
private readonly Task<IDalamudTextureWrap> emptyTextureTask;
|
||||
private readonly Task<IDalamudTextureWrap> disabledIconTask;
|
||||
private readonly Task<IDalamudTextureWrap> outdatedInstallableIconTask;
|
||||
private readonly Task<IDalamudTextureWrap> defaultIconTask;
|
||||
private readonly Task<IDalamudTextureWrap> troubleIconTask;
|
||||
private readonly Task<IDalamudTextureWrap> updateIconTask;
|
||||
private readonly Task<IDalamudTextureWrap> installedIconTask;
|
||||
private readonly Task<IDalamudTextureWrap> thirdIconTask;
|
||||
private readonly Task<IDalamudTextureWrap> thirdInstalledIconTask;
|
||||
private readonly Task<IDalamudTextureWrap> corePluginIconTask;
|
||||
private readonly DalamudAssetManager dalamudAssetManager;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PluginImageCache(Dalamud dalamud)
|
||||
private PluginImageCache(Dalamud dalamud, DalamudAssetManager dalamudAssetManager)
|
||||
{
|
||||
Task<IDalamudTextureWrap>? TaskWrapIfNonNull(IDalamudTextureWrap? tw) => tw == null ? null : Task.FromResult(tw!);
|
||||
var imwst = Task.Run(() => this.imWithScene);
|
||||
|
||||
this.emptyTextureTask = imwst.ContinueWith(task => task.Result.Manager.LoadImageRaw(new byte[64], 8, 8, 4)!);
|
||||
this.defaultIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||
this.disabledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "disabledIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||
this.outdatedInstallableIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "outdatedInstallableIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||
this.troubleIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||
this.updateIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||
this.installedIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "installedIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||
this.thirdIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||
this.thirdInstalledIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(task.Result.Manager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "thirdInstalledIcon.png"))) ?? this.emptyTextureTask).Unwrap();
|
||||
this.corePluginIconTask = imwst.ContinueWith(task => TaskWrapIfNonNull(this.branding.LogoSmall)).Unwrap();
|
||||
|
||||
this.dalamudAssetManager = dalamudAssetManager;
|
||||
this.downloadTask = Task.Factory.StartNew(
|
||||
() => this.DownloadTask(8), TaskCreationOptions.LongRunning);
|
||||
this.loadTask = Task.Factory.StartNew(
|
||||
|
|
@ -102,72 +72,62 @@ internal class PluginImageCache : IDisposable, IServiceType
|
|||
/// <summary>
|
||||
/// Gets the fallback empty texture.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap EmptyTexture => this.emptyTextureTask.IsCompleted
|
||||
? this.emptyTextureTask.Result
|
||||
: this.emptyTextureTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap EmptyTexture =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.Empty4X4);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disabled plugin icon.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap DisabledIcon => this.disabledIconTask.IsCompleted
|
||||
? this.disabledIconTask.Result
|
||||
: this.disabledIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap DisabledIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.DisabledIcon, this.EmptyTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the outdated installable plugin icon.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap OutdatedInstallableIcon => this.outdatedInstallableIconTask.IsCompleted
|
||||
? this.outdatedInstallableIconTask.Result
|
||||
: this.outdatedInstallableIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap OutdatedInstallableIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.OutdatedInstallableIcon, this.EmptyTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default plugin icon.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap DefaultIcon => this.defaultIconTask.IsCompleted
|
||||
? this.defaultIconTask.Result
|
||||
: this.defaultIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap DefaultIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.DefaultIcon, this.EmptyTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin trouble icon overlay.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap TroubleIcon => this.troubleIconTask.IsCompleted
|
||||
? this.troubleIconTask.Result
|
||||
: this.troubleIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap TroubleIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.TroubleIcon, this.EmptyTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin update icon overlay.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap UpdateIcon => this.updateIconTask.IsCompleted
|
||||
? this.updateIconTask.Result
|
||||
: this.updateIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap UpdateIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.UpdateIcon, this.EmptyTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin installed icon overlay.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap InstalledIcon => this.installedIconTask.IsCompleted
|
||||
? this.installedIconTask.Result
|
||||
: this.installedIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap InstalledIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.InstalledIcon, this.EmptyTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the third party plugin icon overlay.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap ThirdIcon => this.thirdIconTask.IsCompleted
|
||||
? this.thirdIconTask.Result
|
||||
: this.thirdIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap ThirdIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.ThirdIcon, this.EmptyTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the installed third party plugin icon overlay.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap ThirdInstalledIcon => this.thirdInstalledIconTask.IsCompleted
|
||||
? this.thirdInstalledIconTask.Result
|
||||
: this.thirdInstalledIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap ThirdInstalledIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.ThirdInstalledIcon, this.EmptyTexture);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the core plugin icon.
|
||||
/// </summary>
|
||||
public IDalamudTextureWrap CorePluginIcon => this.corePluginIconTask.IsCompleted
|
||||
? this.corePluginIconTask.Result
|
||||
: this.corePluginIconTask.GetAwaiter().GetResult();
|
||||
public IDalamudTextureWrap CorePluginIcon =>
|
||||
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall, this.EmptyTexture);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
|
|
@ -185,22 +145,6 @@ internal class PluginImageCache : IDisposable, IServiceType
|
|||
this.downloadQueue.Dispose();
|
||||
this.loadQueue.Dispose();
|
||||
|
||||
foreach (var task in new[]
|
||||
{
|
||||
this.defaultIconTask,
|
||||
this.troubleIconTask,
|
||||
this.updateIconTask,
|
||||
this.installedIconTask,
|
||||
this.thirdIconTask,
|
||||
this.thirdInstalledIconTask,
|
||||
this.corePluginIconTask,
|
||||
})
|
||||
{
|
||||
task.Wait();
|
||||
if (task.IsCompletedSuccessfully)
|
||||
task.Result.Dispose();
|
||||
}
|
||||
|
||||
foreach (var icon in this.pluginIconMap.Values)
|
||||
{
|
||||
icon?.Dispose();
|
||||
|
|
@ -319,7 +263,7 @@ internal class PluginImageCache : IDisposable, IServiceType
|
|||
if (bytes == null)
|
||||
return null;
|
||||
|
||||
var interfaceManager = this.imWithScene.Manager;
|
||||
var interfaceManager = (await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync()).Manager;
|
||||
var framework = await Service<Framework>.GetAsync();
|
||||
|
||||
IDalamudTextureWrap? image;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using Dalamud.Interface.GameFonts;
|
|||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Storage.Assets;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using ImGuiNET;
|
||||
|
|
@ -171,19 +172,16 @@ Dalamud is licensed under AGPL v3 or later.
|
|||
Contribute at: https://github.com/goatcorp/Dalamud
|
||||
";
|
||||
|
||||
private readonly IDalamudTextureWrap logoTexture;
|
||||
private readonly Stopwatch creditsThrottler;
|
||||
|
||||
private string creditsText;
|
||||
|
||||
private bool resetNow = false;
|
||||
private IDalamudTextureWrap? logoTexture;
|
||||
private GameFontHandle? thankYouFont;
|
||||
|
||||
public SettingsTabAbout()
|
||||
{
|
||||
var branding = Service<Branding>.Get();
|
||||
|
||||
this.logoTexture = branding.Logo;
|
||||
this.creditsThrottler = new();
|
||||
}
|
||||
|
||||
|
|
@ -251,6 +249,7 @@ Contribute at: https://github.com/goatcorp/Dalamud
|
|||
|
||||
const float imageSize = 190f;
|
||||
ImGui.SameLine((ImGui.GetWindowWidth() / 2) - (imageSize / 2));
|
||||
this.logoTexture ??= Service<DalamudAssetManager>.Get().GetDalamudTextureWrap(DalamudAsset.Logo);
|
||||
ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(imageSize));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(0, 20f);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
|
|
@ -12,6 +11,7 @@ using Dalamud.Interface.Utility;
|
|||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Storage.Assets;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -46,19 +46,17 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
/// Initializes a new instance of the <see cref="TitleScreenMenuWindow"/> class.
|
||||
/// </summary>
|
||||
/// <param name="clientState">An instance of <see cref="ClientState"/>.</param>
|
||||
/// <param name="dalamud">An instance of <see cref="Dalamud"/>.</param>
|
||||
/// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
|
||||
/// <param name="dalamudAssetManager">An instance of <see cref="DalamudAssetManager"/>.</param>
|
||||
/// <param name="framework">An instance of <see cref="Framework"/>.</param>
|
||||
/// <param name="interfaceManager">An instance of <see cref="InterfaceManager"/>.</param>
|
||||
/// <param name="titleScreenMenu">An instance of <see cref="TitleScreenMenu"/>.</param>
|
||||
/// <param name="gameGui">An instance of <see cref="gameGui"/>.</param>
|
||||
public TitleScreenMenuWindow(
|
||||
ClientState clientState,
|
||||
Dalamud dalamud,
|
||||
DalamudConfiguration configuration,
|
||||
DalamudAssetManager dalamudAssetManager,
|
||||
Framework framework,
|
||||
GameGui gameGui,
|
||||
InterfaceManager interfaceManager,
|
||||
TitleScreenMenu titleScreenMenu)
|
||||
: base(
|
||||
"TitleScreenMenuOverlay",
|
||||
|
|
@ -79,9 +77,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
this.PositionCondition = ImGuiCond.Always;
|
||||
this.RespectCloseHotkey = false;
|
||||
|
||||
var shadeTex =
|
||||
interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmShade.png"));
|
||||
this.shadeTexture = shadeTex ?? throw new Exception("Could not load TSM background texture.");
|
||||
this.shadeTexture = dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.TitleScreenMenuShade);
|
||||
|
||||
framework.Update += this.FrameworkOnUpdate;
|
||||
}
|
||||
|
|
@ -116,7 +112,6 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.shadeTexture.Dispose();
|
||||
this.framework.Update -= this.FrameworkOnUpdate;
|
||||
}
|
||||
|
||||
|
|
@ -386,7 +381,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
|
|||
return isHover;
|
||||
}
|
||||
|
||||
private void FrameworkOnUpdate(IFramework framework)
|
||||
private void FrameworkOnUpdate(IFramework unused)
|
||||
{
|
||||
this.IsOpen = !this.clientState.IsLoggedIn;
|
||||
|
||||
|
|
|
|||
36
Dalamud/Storage/Assets/DalamudAssetAttribute.cs
Normal file
36
Dalamud/Storage/Assets/DalamudAssetAttribute.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
namespace Dalamud.Storage.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the basic information of a Dalamud asset.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
internal class DalamudAssetAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudAssetAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="purpose">The purpose.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="required">Whether the asset is required.</param>
|
||||
public DalamudAssetAttribute(DalamudAssetPurpose purpose, byte[]? data = null, bool required = true)
|
||||
{
|
||||
this.Purpose = purpose;
|
||||
this.Data = data;
|
||||
this.Required = required;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the purpose of the asset.
|
||||
/// </summary>
|
||||
public DalamudAssetPurpose Purpose { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the data, if available.
|
||||
/// </summary>
|
||||
public byte[]? Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the asset is required.
|
||||
/// </summary>
|
||||
public bool Required { get; }
|
||||
}
|
||||
17
Dalamud/Storage/Assets/DalamudAssetExtensions.cs
Normal file
17
Dalamud/Storage/Assets/DalamudAssetExtensions.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Storage.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="DalamudAsset"/>.
|
||||
/// </summary>
|
||||
public static class DalamudAssetExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the purpose.
|
||||
/// </summary>
|
||||
/// <param name="asset">The asset.</param>
|
||||
/// <returns>The purpose.</returns>
|
||||
public static DalamudAssetPurpose GetPurpose(this DalamudAsset asset) =>
|
||||
asset.GetAttribute<DalamudAssetAttribute>()?.Purpose ?? DalamudAssetPurpose.Empty;
|
||||
}
|
||||
365
Dalamud/Storage/Assets/DalamudAssetManager.cs
Normal file
365
Dalamud/Storage/Assets/DalamudAssetManager.cs
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Networking.Http;
|
||||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Timing;
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Storage.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// A concrete class for <see cref="IDalamudAssetManager"/>.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IDalamudAssetManager>]
|
||||
#pragma warning restore SA1015
|
||||
internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudAssetManager
|
||||
{
|
||||
private const int DownloadAttemptCount = 10;
|
||||
private const int RenameAttemptCount = 10;
|
||||
|
||||
private readonly object syncRoot = new();
|
||||
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
|
||||
private readonly Dictionary<DalamudAsset, Task<FileStream>?> fileStreams;
|
||||
private readonly Dictionary<DalamudAsset, Task<IDalamudTextureWrap>?> textureWraps;
|
||||
private readonly Dalamud dalamud;
|
||||
private readonly HappyHttpClient httpClient;
|
||||
private readonly string localSourceDirectory;
|
||||
private readonly CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private DalamudAssetManager(Dalamud dalamud, HappyHttpClient httpClient)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
this.httpClient = httpClient;
|
||||
this.localSourceDirectory = Path.Combine(this.dalamud.AssetDirectory.FullName, "..", "local");
|
||||
Directory.CreateDirectory(this.localSourceDirectory);
|
||||
this.scopedFinalizer.Add(this.cancellationTokenSource = new());
|
||||
|
||||
this.fileStreams = Enum.GetValues<DalamudAsset>().ToDictionary(x => x, _ => (Task<FileStream>?)null);
|
||||
this.textureWraps = Enum.GetValues<DalamudAsset>().ToDictionary(x => x, _ => (Task<IDalamudTextureWrap>?)null);
|
||||
|
||||
var loadTimings = Timings.Start("DAM LoadAll");
|
||||
this.WaitForAllRequiredAssets().ContinueWith(_ => loadTimings.Dispose());
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IDalamudTextureWrap Empty4X4 => this.GetDalamudTextureWrap(DalamudAsset.Empty4X4);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
if (this.isDisposed)
|
||||
return;
|
||||
|
||||
this.isDisposed = true;
|
||||
}
|
||||
|
||||
this.cancellationTokenSource.Cancel();
|
||||
Task.WaitAll(
|
||||
Array.Empty<Task>()
|
||||
.Concat(this.fileStreams.Values)
|
||||
.Concat(this.textureWraps.Values)
|
||||
.Where(x => x is not null)
|
||||
.ToArray());
|
||||
this.scopedFinalizer.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for all the required assets to be ready. Will result in a faulted task, if any of the required assets
|
||||
/// has failed to load.
|
||||
/// </summary>
|
||||
/// <returns>The task.</returns>
|
||||
[Pure]
|
||||
public Task WaitForAllRequiredAssets()
|
||||
{
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
return Task.WhenAll(
|
||||
Enum.GetValues<DalamudAsset>()
|
||||
.Where(x => x is not DalamudAsset.Empty4X4)
|
||||
.Select(this.CreateStreamAsync)
|
||||
.Select(x => x.ToContentDisposedTask()));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public bool IsStreamImmediatelyAvailable(DalamudAsset asset) =>
|
||||
asset.GetAttribute<DalamudAssetAttribute>()?.Data is not null
|
||||
|| this.fileStreams[asset]?.IsCompletedSuccessfully is true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public Stream CreateStream(DalamudAsset asset)
|
||||
{
|
||||
var s = this.CreateStreamAsync(asset);
|
||||
s.Wait();
|
||||
if (s.IsCompletedSuccessfully)
|
||||
return s.Result;
|
||||
if (s.Exception is not null)
|
||||
throw new AggregateException(s.Exception.InnerExceptions);
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public Task<Stream> CreateStreamAsync(DalamudAsset asset)
|
||||
{
|
||||
if (asset.GetAttribute<DalamudAssetAttribute>() is { Data: { } rawData })
|
||||
return Task.FromResult<Stream>(new MemoryStream(rawData, false));
|
||||
|
||||
Task<FileStream> task;
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
if (this.isDisposed)
|
||||
throw new ObjectDisposedException(nameof(DalamudAssetManager));
|
||||
|
||||
task = this.fileStreams[asset] ??= CreateInnerAsync();
|
||||
}
|
||||
|
||||
return this.TransformImmediate(
|
||||
task,
|
||||
x => (Stream)new FileStream(
|
||||
x.Name,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.Read,
|
||||
4096,
|
||||
FileOptions.Asynchronous | FileOptions.SequentialScan));
|
||||
|
||||
async Task<FileStream> CreateInnerAsync()
|
||||
{
|
||||
string path;
|
||||
List<Exception?> exceptions = null;
|
||||
foreach (var name in asset.GetAttributes<DalamudAssetPathAttribute>().Select(x => x.FileName))
|
||||
{
|
||||
if (!File.Exists(path = Path.Combine(this.dalamud.AssetDirectory.FullName, name)))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
return File.OpenRead(path);
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
{
|
||||
exceptions ??= new();
|
||||
exceptions.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(path = Path.Combine(this.localSourceDirectory, asset.ToString())))
|
||||
{
|
||||
try
|
||||
{
|
||||
return File.OpenRead(path);
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
{
|
||||
exceptions ??= new();
|
||||
exceptions.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
var tempPath = $"{path}.{Environment.ProcessId:x}.{Environment.CurrentManagedThreadId:x}";
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < DownloadAttemptCount; i++)
|
||||
{
|
||||
var attemptedAny = false;
|
||||
foreach (var url in asset.GetAttributes<DalamudAssetOnlineSourceAttribute>())
|
||||
{
|
||||
Log.Information("[{who}] {asset}: Trying {url}", nameof(DalamudAssetManager), asset, url);
|
||||
attemptedAny = true;
|
||||
|
||||
try
|
||||
{
|
||||
await using var tempPathStream = File.Open(tempPath, FileMode.Create, FileAccess.Write);
|
||||
await url.DownloadAsync(
|
||||
this.httpClient.SharedHttpClient,
|
||||
tempPathStream,
|
||||
this.cancellationTokenSource.Token);
|
||||
tempPathStream.Dispose();
|
||||
for (var j = RenameAttemptCount; ; j--)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Move(tempPath, path);
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
if (j == 0)
|
||||
throw;
|
||||
Log.Warning(
|
||||
ioe,
|
||||
"[{who}] {asset}: Renaming failed; trying again {n} more times",
|
||||
nameof(DalamudAssetManager),
|
||||
asset,
|
||||
j);
|
||||
await Task.Delay(1000, this.cancellationTokenSource.Token);
|
||||
continue;
|
||||
}
|
||||
|
||||
return File.OpenRead(path);
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
{
|
||||
Log.Error(e, "[{who}] {asset}: Failed {url}", nameof(DalamudAssetManager), asset, url);
|
||||
}
|
||||
}
|
||||
|
||||
if (!attemptedAny)
|
||||
throw new FileNotFoundException($"Failed to find the asset {asset}.", asset.ToString());
|
||||
|
||||
// Wait up to 5 minutes
|
||||
var delay = Math.Min(300, (1 << i) * 1000);
|
||||
Log.Error(
|
||||
"[{who}] {asset}: Failed to download. Trying again in {sec} seconds...",
|
||||
nameof(DalamudAssetManager),
|
||||
asset,
|
||||
delay);
|
||||
await Task.Delay(delay * 1000, this.cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"Failed to load the asset {asset}.", asset.ToString());
|
||||
}
|
||||
catch (Exception e) when (e is not OperationCanceledException)
|
||||
{
|
||||
exceptions ??= new();
|
||||
exceptions.Add(e);
|
||||
try
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public IDalamudTextureWrap GetDalamudTextureWrap(DalamudAsset asset) =>
|
||||
ExtractResult(this.GetDalamudTextureWrapAsync(asset));
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
[return: NotNullIfNotNull(nameof(defaultWrap))]
|
||||
public IDalamudTextureWrap? GetDalamudTextureWrap(DalamudAsset asset, IDalamudTextureWrap? defaultWrap)
|
||||
{
|
||||
var task = this.GetDalamudTextureWrapAsync(asset);
|
||||
return task.IsCompletedSuccessfully ? task.Result : defaultWrap;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Pure]
|
||||
public Task<IDalamudTextureWrap> GetDalamudTextureWrapAsync(DalamudAsset asset)
|
||||
{
|
||||
var purpose = asset.GetPurpose();
|
||||
if (purpose is not DalamudAssetPurpose.TextureFromPng and not DalamudAssetPurpose.TextureFromRaw)
|
||||
throw new ArgumentOutOfRangeException(nameof(asset), asset, "The asset cannot be taken as a Texture2D.");
|
||||
|
||||
Task<IDalamudTextureWrap> task;
|
||||
lock (this.syncRoot)
|
||||
{
|
||||
if (this.isDisposed)
|
||||
throw new ObjectDisposedException(nameof(DalamudAssetManager));
|
||||
|
||||
task = this.textureWraps[asset] ??= CreateInnerAsync();
|
||||
}
|
||||
|
||||
return task;
|
||||
|
||||
async Task<IDalamudTextureWrap> CreateInnerAsync()
|
||||
{
|
||||
var buf = Array.Empty<byte>();
|
||||
try
|
||||
{
|
||||
var im = (await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync()).Manager;
|
||||
await using var stream = await this.CreateStreamAsync(asset);
|
||||
var length = checked((int)stream.Length);
|
||||
buf = ArrayPool<byte>.Shared.Rent(length);
|
||||
stream.ReadExactly(buf, 0, length);
|
||||
var image = purpose switch
|
||||
{
|
||||
DalamudAssetPurpose.TextureFromPng => im.LoadImage(buf),
|
||||
DalamudAssetPurpose.TextureFromRaw =>
|
||||
asset.GetAttribute<DalamudAssetRawTextureAttribute>() is { } raw
|
||||
? im.LoadImageFromDxgiFormat(buf, raw.Pitch, raw.Width, raw.Height, raw.Format)
|
||||
: throw new InvalidOperationException(
|
||||
"TextureFromRaw must accompany a DalamudAssetRawTextureAttribute."),
|
||||
_ => null,
|
||||
};
|
||||
var disposeDeferred =
|
||||
this.scopedFinalizer.Add(image)
|
||||
?? throw new InvalidOperationException("Something went wrong very badly");
|
||||
return new DisposeSuppressingDalamudTextureWrap(disposeDeferred);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "[{name}] Failed to load {asset}.", nameof(DalamudAssetManager), asset);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static T ExtractResult<T>(Task<T> t) => t.IsCompleted ? t.Result : t.GetAwaiter().GetResult();
|
||||
|
||||
private Task<TOut> TransformImmediate<TIn, TOut>(Task<TIn> task, Func<TIn, TOut> transformer)
|
||||
{
|
||||
if (task.IsCompletedSuccessfully)
|
||||
return Task.FromResult(transformer(task.Result));
|
||||
if (task.Exception is { } exc)
|
||||
return Task.FromException<TOut>(exc);
|
||||
return task.ContinueWith(_ => this.TransformImmediate(task, transformer)).Unwrap();
|
||||
}
|
||||
|
||||
private class DisposeSuppressingDalamudTextureWrap : IDalamudTextureWrap
|
||||
{
|
||||
private readonly IDalamudTextureWrap innerWrap;
|
||||
|
||||
public DisposeSuppressingDalamudTextureWrap(IDalamudTextureWrap wrap) => this.innerWrap = wrap;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr ImGuiHandle => this.innerWrap.ImGuiHandle;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Width => this.innerWrap.Width;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Height => this.innerWrap.Height;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
// suppressed
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Dalamud/Storage/Assets/DalamudAssetOnlineSourceAttribute.cs
Normal file
48
Dalamud/Storage/Assets/DalamudAssetOnlineSourceAttribute.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dalamud.Storage.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// Marks that an asset can be download from online.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||
internal class DalamudAssetOnlineSourceAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudAssetOnlineSourceAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="url">The URL.</param>
|
||||
public DalamudAssetOnlineSourceAttribute(string url)
|
||||
{
|
||||
this.Url = url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source URL of the file.
|
||||
/// </summary>
|
||||
public string Url { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Downloads to the given stream.
|
||||
/// </summary>
|
||||
/// <param name="client">The client.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The task.</returns>
|
||||
public async Task DownloadAsync(HttpClient client, Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
using var resp = await client.GetAsync(this.Url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
if (resp.StatusCode != HttpStatusCode.OK)
|
||||
throw new NotSupportedException($"Only 200 OK is supported; got {resp.StatusCode}");
|
||||
|
||||
await using var readStream = await resp.Content.ReadAsStreamAsync(cancellationToken);
|
||||
await readStream.CopyToAsync(stream, cancellationToken);
|
||||
if (resp.Content.Headers.ContentLength is { } length && stream.Length != length)
|
||||
throw new IOException($"Expected {length} bytes; got {stream.Length} bytes.");
|
||||
}
|
||||
}
|
||||
21
Dalamud/Storage/Assets/DalamudAssetPathAttribute.cs
Normal file
21
Dalamud/Storage/Assets/DalamudAssetPathAttribute.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System.IO;
|
||||
|
||||
namespace Dalamud.Storage.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// File names to look up in Dalamud assets.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||
internal class DalamudAssetPathAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudAssetPathAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pathComponents">The path components.</param>
|
||||
public DalamudAssetPathAttribute(params string[] pathComponents) => this.FileName = Path.Join(pathComponents);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file name.
|
||||
/// </summary>
|
||||
public string FileName { get; }
|
||||
}
|
||||
27
Dalamud/Storage/Assets/DalamudAssetPurpose.cs
Normal file
27
Dalamud/Storage/Assets/DalamudAssetPurpose.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace Dalamud.Storage.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// Purposes of a Dalamud asset.
|
||||
/// </summary>
|
||||
public enum DalamudAssetPurpose
|
||||
{
|
||||
/// <summary>
|
||||
/// The asset has no purpose.
|
||||
/// </summary>
|
||||
Empty = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The asset is a .png file, and can be purposed as a <see cref="SharpDX.Direct3D11.Texture2D"/>.
|
||||
/// </summary>
|
||||
TextureFromPng = 10,
|
||||
|
||||
/// <summary>
|
||||
/// The asset is a raw texture, and can be purposed as a <see cref="SharpDX.Direct3D11.Texture2D"/>.
|
||||
/// </summary>
|
||||
TextureFromRaw = 1001,
|
||||
|
||||
/// <summary>
|
||||
/// The asset is a font file.
|
||||
/// </summary>
|
||||
Font = 2000,
|
||||
}
|
||||
45
Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs
Normal file
45
Dalamud/Storage/Assets/DalamudAssetRawTextureAttribute.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using SharpDX.DXGI;
|
||||
|
||||
namespace Dalamud.Storage.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// Provide raw texture data directly.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
internal class DalamudAssetRawTextureAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudAssetRawTextureAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="width">The width.</param>
|
||||
/// <param name="pitch">The pitch.</param>
|
||||
/// <param name="height">The height.</param>
|
||||
/// <param name="format">The format.</param>
|
||||
public DalamudAssetRawTextureAttribute(int width, int pitch, int height, Format format)
|
||||
{
|
||||
this.Width = width;
|
||||
this.Pitch = pitch;
|
||||
this.Height = height;
|
||||
this.Format = format;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the width.
|
||||
/// </summary>
|
||||
public int Width { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pitch.
|
||||
/// </summary>
|
||||
public int Pitch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the height.
|
||||
/// </summary>
|
||||
public int Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the format.
|
||||
/// </summary>
|
||||
public Format Format { get; }
|
||||
}
|
||||
79
Dalamud/Storage/Assets/IDalamudAssetManager.cs
Normal file
79
Dalamud/Storage/Assets/IDalamudAssetManager.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dalamud.Interface.Internal;
|
||||
|
||||
namespace Dalamud.Storage.Assets;
|
||||
|
||||
/// <summary>
|
||||
/// Holds Dalamud Assets' handles hostage, so that they do not get closed while Dalamud is running.<br />
|
||||
/// Also, attempts to load optional assets.<br />
|
||||
/// <br />
|
||||
/// <strong>Note on <see cref="PureAttribute"/></strong><br />
|
||||
/// It will help you get notified if you discard the result of functions, mostly likely because of a mistake.
|
||||
/// Think of C++ [[nodiscard]]. Also, like the intended meaning of the attribute, such methods will not have
|
||||
/// externally visible state changes.
|
||||
/// </summary>
|
||||
internal interface IDalamudAssetManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the shared texture wrap for <see cref="DalamudAsset.Empty4X4"/>.
|
||||
/// </summary>
|
||||
IDalamudTextureWrap Empty4X4 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the stream for the asset is instantly available.
|
||||
/// </summary>
|
||||
/// <param name="asset">The asset.</param>
|
||||
/// <returns>Whether the stream of an asset is immediately available.</returns>
|
||||
[Pure]
|
||||
bool IsStreamImmediatelyAvailable(DalamudAsset asset);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a stream backed by the specified asset, waiting as necessary.<br />
|
||||
/// <strong>Call <see cref="IDisposable.Dispose"/> after use.</strong>
|
||||
/// </summary>
|
||||
/// <param name="asset">The asset.</param>
|
||||
/// <returns>The stream.</returns>
|
||||
[Pure]
|
||||
Stream CreateStream(DalamudAsset asset);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a stream backed by the specified asset.<br />
|
||||
/// <strong>Call <see cref="IDisposable.Dispose"/> after use.</strong>
|
||||
/// </summary>
|
||||
/// <param name="asset">The asset.</param>
|
||||
/// <returns>The stream, wrapped inside a <see cref="Stream"/>.</returns>
|
||||
[Pure]
|
||||
Task<Stream> CreateStreamAsync(DalamudAsset asset);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a shared instance of <see cref="IDalamudTextureWrap"/>, after waiting as necessary.<br />
|
||||
/// Calls to <see cref="IDisposable.Dispose"/> is unnecessary; they will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="asset">The texture asset.</param>
|
||||
/// <returns>The texture wrap.</returns>
|
||||
[Pure]
|
||||
IDalamudTextureWrap GetDalamudTextureWrap(DalamudAsset asset);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a shared instance of <see cref="IDalamudTextureWrap"/> if it is available instantly;
|
||||
/// if it is not ready, returns <paramref name="defaultWrap"/>.<br />
|
||||
/// Calls to <see cref="IDisposable.Dispose"/> is unnecessary; they will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="asset">The texture asset.</param>
|
||||
/// <param name="defaultWrap">The default return value, if the asset is not ready for whatever reason.</param>
|
||||
/// <returns>The texture wrap.</returns>
|
||||
[Pure]
|
||||
IDalamudTextureWrap? GetDalamudTextureWrap(DalamudAsset asset, IDalamudTextureWrap? defaultWrap);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a shared instance of <see cref="IDalamudTextureWrap"/> in a <see cref="Task{T}"/>.<br />
|
||||
/// Calls to <see cref="IDisposable.Dispose"/> is unnecessary; they will be ignored.
|
||||
/// </summary>
|
||||
/// <param name="asset">The texture asset.</param>
|
||||
/// <returns>The new texture wrap, wrapped inside a <see cref="Task{T}"/>.</returns>
|
||||
[Pure]
|
||||
Task<IDalamudTextureWrap> GetDalamudTextureWrapAsync(DalamudAsset asset);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Dalamud.Utility;
|
||||
|
|
@ -8,6 +8,26 @@ namespace Dalamud.Utility;
|
|||
/// </summary>
|
||||
public static class EnumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets attributes on an enum.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAttribute">The type of attribute to get.</typeparam>
|
||||
/// <param name="value">The enum value that has an attached attribute.</param>
|
||||
/// <returns>The enumerable of the attached attributes.</returns>
|
||||
public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this Enum value)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
var type = value.GetType();
|
||||
var name = Enum.GetName(type, value);
|
||||
if (name.IsNullOrEmpty())
|
||||
return Array.Empty<TAttribute>();
|
||||
|
||||
return type.GetField(name)?
|
||||
.GetCustomAttributes(false)
|
||||
.OfType<TAttribute>()
|
||||
?? Array.Empty<TAttribute>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an attribute on an enum.
|
||||
/// </summary>
|
||||
|
|
@ -15,18 +35,8 @@ public static class EnumExtensions
|
|||
/// <param name="value">The enum value that has an attached attribute.</param>
|
||||
/// <returns>The attached attribute, if any.</returns>
|
||||
public static TAttribute? GetAttribute<TAttribute>(this Enum value)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
var type = value.GetType();
|
||||
var name = Enum.GetName(type, value);
|
||||
if (name.IsNullOrEmpty())
|
||||
return null;
|
||||
|
||||
return type.GetField(name)?
|
||||
.GetCustomAttributes(false)
|
||||
.OfType<TAttribute>()
|
||||
.SingleOrDefault();
|
||||
}
|
||||
where TAttribute : Attribute =>
|
||||
value.GetAttributes<TAttribute>().SingleOrDefault();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an indicator if enum has been flagged as obsolete (deprecated).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue