Better Service dependency handling (#1535)

This commit is contained in:
srkizer 2023-11-29 06:20:16 +09:00 committed by GitHub
parent fcebd15077
commit b66be84b93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 415 additions and 197 deletions

View file

@ -21,7 +21,7 @@ namespace Dalamud.Configuration.Internal;
/// Class containing Dalamud settings. /// Class containing Dalamud settings.
/// </summary> /// </summary>
[Serializable] [Serializable]
[ServiceManager.Service] [ServiceManager.ProvidedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[InherentDependency<ReliableFileStorage>] // We must still have this when unloading [InherentDependency<ReliableFileStorage>] // We must still have this when unloading
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -30,7 +30,7 @@ namespace Dalamud;
/// <summary> /// <summary>
/// The main Dalamud class containing all subsystems. /// The main Dalamud class containing all subsystems.
/// </summary> /// </summary>
[ServiceManager.Service] [ServiceManager.ProvidedService]
internal sealed class Dalamud : IServiceType internal sealed class Dalamud : IServiceType
{ {
#region Internals #region Internals

View file

@ -18,7 +18,7 @@ namespace Dalamud.Game.Addon.Events;
/// Service provider for addon event management. /// Service provider for addon event management.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal unsafe class AddonEventManager : IDisposable, IServiceType internal unsafe class AddonEventManager : IDisposable, IServiceType
{ {
/// <summary> /// <summary>

View file

@ -18,7 +18,7 @@ namespace Dalamud.Game.Addon.Lifecycle;
/// This class provides events for in-game addon lifecycles. /// This class provides events for in-game addon lifecycles.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal unsafe class AddonLifecycle : IDisposable, IServiceType internal unsafe class AddonLifecycle : IDisposable, IServiceType
{ {
private static readonly ModuleLog Log = new("AddonLifecycle"); private static readonly ModuleLog Log = new("AddonLifecycle");

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.Config;
/// This class represents the game's configuration. /// This class represents the game's configuration.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable
{ {
private readonly GameConfigAddressResolver address = new(); private readonly GameConfigAddressResolver address = new();

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.DutyState;
/// This class represents the state of the currently occupied duty. /// This class represents the state of the currently occupied duty.
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal unsafe class DutyState : IDisposable, IServiceType, IDutyState internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
{ {
private readonly DutyStateAddressResolver address; private readonly DutyStateAddressResolver address;

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
@ -22,14 +21,14 @@ internal class UniversalisMarketBoardUploader : IMarketBoardUploader
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT"; private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
private readonly HttpClient httpClient = Service<HappyHttpClient>.Get().SharedHttpClient; private readonly HttpClient httpClient;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UniversalisMarketBoardUploader"/> class. /// Initializes a new instance of the <see cref="UniversalisMarketBoardUploader"/> class.
/// </summary> /// </summary>
public UniversalisMarketBoardUploader() /// <param name="happyHttpClient">An instance of <see cref="HappyHttpClient"/>.</param>
{ public UniversalisMarketBoardUploader(HappyHttpClient happyHttpClient) =>
} this.httpClient = happyHttpClient.SharedHttpClient;
/// <inheritdoc/> /// <inheritdoc/>
public async Task Upload(MarketBoardItemRequest request) public async Task Upload(MarketBoardItemRequest request)

View file

@ -13,6 +13,7 @@ using Dalamud.Game.Network.Internal.MarketBoardUploaders;
using Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis; using Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis;
using Dalamud.Game.Network.Structures; using Dalamud.Game.Network.Structures;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Networking.Http;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.UI.Info; using FFXIVClientStructs.FFXIV.Client.UI.Info;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
@ -23,7 +24,7 @@ namespace Dalamud.Game.Network.Internal;
/// <summary> /// <summary>
/// This class handles network notifications and uploading market board data. /// This class handles network notifications and uploading market board data.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal unsafe class NetworkHandlers : IDisposable, IServiceType internal unsafe class NetworkHandlers : IDisposable, IServiceType
{ {
private readonly IMarketBoardUploader uploader; private readonly IMarketBoardUploader uploader;
@ -55,9 +56,12 @@ internal unsafe class NetworkHandlers : IDisposable, IServiceType
private bool disposing; private bool disposing;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private NetworkHandlers(GameNetwork gameNetwork, TargetSigScanner sigScanner) private NetworkHandlers(
GameNetwork gameNetwork,
TargetSigScanner sigScanner,
HappyHttpClient happyHttpClient)
{ {
this.uploader = new UniversalisMarketBoardUploader(); this.uploader = new UniversalisMarketBoardUploader(happyHttpClient);
this.addressResolver = new NetworkHandlersAddressResolver(); this.addressResolver = new NetworkHandlersAddressResolver();
this.addressResolver.Setup(sigScanner); this.addressResolver.Setup(sigScanner);

View file

@ -11,7 +11,7 @@ namespace Dalamud.Game;
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.Service] [ServiceManager.ProvidedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ISigScanner>] [ResolveVia<ISigScanner>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -15,7 +15,7 @@ namespace Dalamud.Interface.DragDrop;
/// and can be used to create ImGui drag and drop sources and targets for those external events. /// and can be used to create ImGui drag and drop sources and targets for those external events.
/// </summary> /// </summary>
[PluginInterface] [PluginInterface]
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDragDropManager>] [ResolveVia<IDragDropManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015

View file

@ -22,7 +22,7 @@ namespace Dalamud.Interface.GameFonts;
/// <summary> /// <summary>
/// Loads game font for use in ImGui. /// Loads game font for use in ImGui.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal class GameFontManager : IServiceType internal class GameFontManager : IServiceType
{ {
private static readonly string?[] FontNames = private static readonly string?[] FontNames =

View file

@ -1,7 +1,5 @@
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Reflection; using System.Reflection;
@ -9,6 +7,7 @@ using System.Runtime.InteropServices;
using CheapLoc; using CheapLoc;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui; using Dalamud.Game.Gui;
using Dalamud.Game.Internal; using Dalamud.Game.Internal;
@ -25,14 +24,14 @@ using Dalamud.Interface.Style;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Logging;
using Dalamud.Logging.Internal; using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal; using Dalamud.Plugin.Internal;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI;
using ImGuiNET; using ImGuiNET;
using ImGuiScene;
using ImPlotNET; using ImPlotNET;
using PInvoke; using PInvoke;
using Serilog.Events; using Serilog.Events;
@ -49,7 +48,9 @@ internal class DalamudInterface : IDisposable, IServiceType
private static readonly ModuleLog Log = new("DUI"); private static readonly ModuleLog Log = new("DUI");
private readonly Dalamud dalamud;
private readonly DalamudConfiguration configuration; private readonly DalamudConfiguration configuration;
private readonly InterfaceManager interfaceManager;
private readonly ChangelogWindow changelogWindow; private readonly ChangelogWindow changelogWindow;
private readonly ColorDemoWindow colorDemoWindow; private readonly ColorDemoWindow colorDemoWindow;
@ -92,11 +93,16 @@ internal class DalamudInterface : IDisposable, IServiceType
DalamudConfiguration configuration, DalamudConfiguration configuration,
InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene, InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene,
PluginImageCache pluginImageCache, PluginImageCache pluginImageCache,
Branding branding) Branding branding,
Game.Framework framework,
ClientState clientState,
TitleScreenMenu titleScreenMenu,
GameGui gameGui)
{ {
this.dalamud = dalamud;
this.configuration = configuration; this.configuration = configuration;
this.interfaceManager = interfaceManagerWithScene.Manager;
var interfaceManager = interfaceManagerWithScene.Manager;
this.WindowSystem = new WindowSystem("DalamudCore"); this.WindowSystem = new WindowSystem("DalamudCore");
this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false }; this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false };
@ -104,13 +110,20 @@ internal class DalamudInterface : IDisposable, IServiceType
this.dataWindow = new DataWindow() { IsOpen = false }; this.dataWindow = new DataWindow() { IsOpen = false };
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false }; this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false };
this.imeWindow = new ImeWindow() { IsOpen = false }; this.imeWindow = new ImeWindow() { IsOpen = false };
this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup }; this.consoleWindow = new ConsoleWindow(configuration) { IsOpen = configuration.LogOpenAtStartup };
this.pluginStatWindow = new PluginStatWindow() { IsOpen = false }; this.pluginStatWindow = new PluginStatWindow() { IsOpen = false };
this.pluginWindow = new PluginInstallerWindow(pluginImageCache) { IsOpen = false }; this.pluginWindow = new PluginInstallerWindow(pluginImageCache, configuration) { IsOpen = false };
this.settingsWindow = new SettingsWindow() { IsOpen = false }; this.settingsWindow = new SettingsWindow() { IsOpen = false };
this.selfTestWindow = new SelfTestWindow() { IsOpen = false }; this.selfTestWindow = new SelfTestWindow() { IsOpen = false };
this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false }; this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false };
this.titleScreenMenuWindow = new TitleScreenMenuWindow() { IsOpen = false }; this.titleScreenMenuWindow = new TitleScreenMenuWindow(
clientState,
dalamud,
configuration,
framework,
gameGui,
this.interfaceManager,
titleScreenMenu) { IsOpen = false };
this.changelogWindow = new ChangelogWindow(this.titleScreenMenuWindow) { IsOpen = false }; this.changelogWindow = new ChangelogWindow(this.titleScreenMenuWindow) { IsOpen = false };
this.profilerWindow = new ProfilerWindow() { IsOpen = false }; this.profilerWindow = new ProfilerWindow() { IsOpen = false };
this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false }; this.branchSwitcherWindow = new BranchSwitcherWindow() { IsOpen = false };
@ -136,7 +149,7 @@ internal class DalamudInterface : IDisposable, IServiceType
ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup;
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup; this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
interfaceManager.Draw += this.OnDraw; this.interfaceManager.Draw += this.OnDraw;
var tsm = Service<TitleScreenMenu>.Get(); var tsm = Service<TitleScreenMenu>.Get();
tsm.AddEntryCore(Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), branding.LogoSmall, () => this.OpenPluginInstaller()); tsm.AddEntryCore(Loc.Localize("TSMDalamudPlugins", "Plugin Installer"), branding.LogoSmall, () => this.OpenPluginInstaller());
@ -173,7 +186,7 @@ internal class DalamudInterface : IDisposable, IServiceType
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
Service<InterfaceManager>.Get().Draw -= this.OnDraw; this.interfaceManager.Draw -= this.OnDraw;
this.WindowSystem.RemoveAllWindows(); this.WindowSystem.RemoveAllWindows();
@ -356,7 +369,7 @@ internal class DalamudInterface : IDisposable, IServiceType
/// Toggles the <see cref="DataWindow"/>. /// Toggles the <see cref="DataWindow"/>.
/// </summary> /// </summary>
/// <param name="dataKind">The data kind to switch to after opening.</param> /// <param name="dataKind">The data kind to switch to after opening.</param>
public void ToggleDataWindow(string dataKind = null) public void ToggleDataWindow(string? dataKind = null)
{ {
this.dataWindow.Toggle(); this.dataWindow.Toggle();
if (dataKind != null && this.dataWindow.IsOpen) if (dataKind != null && this.dataWindow.IsOpen)
@ -378,7 +391,7 @@ internal class DalamudInterface : IDisposable, IServiceType
/// <summary> /// <summary>
/// Toggles the <see cref="ImeWindow"/>. /// Toggles the <see cref="ImeWindow"/>.
/// </summary> /// </summary>
public void ToggleIMEWindow() => this.imeWindow.Toggle(); public void ToggleImeWindow() => this.imeWindow.Toggle();
/// <summary> /// <summary>
/// Toggles the <see cref="ConsoleWindow"/>. /// Toggles the <see cref="ConsoleWindow"/>.
@ -504,7 +517,8 @@ internal class DalamudInterface : IDisposable, IServiceType
private void DrawCreditsDarkeningAnimation() private void DrawCreditsDarkeningAnimation()
{ {
using var style = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding | ImGuiStyleVar.WindowBorderSize, 0f); using var style1 = ImRaii.PushStyle(ImGuiStyleVar.WindowRounding, 0f);
using var style2 = ImRaii.PushStyle(ImGuiStyleVar.WindowBorderSize, 0f);
using var color = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 0)); using var color = ImRaii.PushColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 0));
ImGui.SetNextWindowPos(new Vector2(0, 0)); ImGui.SetNextWindowPos(new Vector2(0, 0));
@ -579,18 +593,16 @@ internal class DalamudInterface : IDisposable, IServiceType
{ {
if (ImGui.BeginMainMenuBar()) if (ImGui.BeginMainMenuBar())
{ {
var dalamud = Service<Dalamud>.Get();
var configuration = Service<DalamudConfiguration>.Get();
var pluginManager = Service<PluginManager>.Get(); var pluginManager = Service<PluginManager>.Get();
if (ImGui.BeginMenu("Dalamud")) if (ImGui.BeginMenu("Dalamud"))
{ {
ImGui.MenuItem("Draw dev menu", string.Empty, ref this.isImGuiDrawDevMenu); ImGui.MenuItem("Draw dev menu", string.Empty, ref this.isImGuiDrawDevMenu);
var devBarAtStartup = configuration.DevBarOpenAtStartup; var devBarAtStartup = this.configuration.DevBarOpenAtStartup;
if (ImGui.MenuItem("Draw dev menu at startup", string.Empty, ref devBarAtStartup)) if (ImGui.MenuItem("Draw dev menu at startup", string.Empty, ref devBarAtStartup))
{ {
configuration.DevBarOpenAtStartup ^= true; this.configuration.DevBarOpenAtStartup ^= true;
configuration.QueueSave(); this.configuration.QueueSave();
} }
ImGui.Separator(); ImGui.Separator();
@ -607,25 +619,25 @@ internal class DalamudInterface : IDisposable, IServiceType
if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, EntryPoint.LogLevelSwitch.MinimumLevel == logLevel)) if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, EntryPoint.LogLevelSwitch.MinimumLevel == logLevel))
{ {
EntryPoint.LogLevelSwitch.MinimumLevel = logLevel; EntryPoint.LogLevelSwitch.MinimumLevel = logLevel;
configuration.LogLevel = logLevel; this.configuration.LogLevel = logLevel;
configuration.QueueSave(); this.configuration.QueueSave();
} }
} }
ImGui.EndMenu(); ImGui.EndMenu();
} }
var logSynchronously = configuration.LogSynchronously; var logSynchronously = this.configuration.LogSynchronously;
if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously)) if (ImGui.MenuItem("Log Synchronously", null, ref logSynchronously))
{ {
configuration.LogSynchronously = logSynchronously; this.configuration.LogSynchronously = logSynchronously;
configuration.QueueSave(); this.configuration.QueueSave();
EntryPoint.InitLogging( EntryPoint.InitLogging(
dalamud.StartInfo.LogPath!, this.dalamud.StartInfo.LogPath!,
dalamud.StartInfo.BootShowConsole, this.dalamud.StartInfo.BootShowConsole,
configuration.LogSynchronously, this.configuration.LogSynchronously,
dalamud.StartInfo.LogName); this.dalamud.StartInfo.LogName);
} }
var antiDebug = Service<AntiDebug>.Get(); var antiDebug = Service<AntiDebug>.Get();
@ -637,8 +649,8 @@ internal class DalamudInterface : IDisposable, IServiceType
else else
antiDebug.Disable(); antiDebug.Disable();
configuration.IsAntiAntiDebugEnabled = newEnabled; this.configuration.IsAntiAntiDebugEnabled = newEnabled;
configuration.QueueSave(); this.configuration.QueueSave();
} }
ImGui.Separator(); ImGui.Separator();
@ -730,10 +742,10 @@ internal class DalamudInterface : IDisposable, IServiceType
} }
} }
if (ImGui.MenuItem("Report crashes at shutdown", null, configuration.ReportShutdownCrashes)) if (ImGui.MenuItem("Report crashes at shutdown", null, this.configuration.ReportShutdownCrashes))
{ {
configuration.ReportShutdownCrashes = !configuration.ReportShutdownCrashes; this.configuration.ReportShutdownCrashes = !this.configuration.ReportShutdownCrashes;
configuration.QueueSave(); this.configuration.QueueSave();
} }
ImGui.Separator(); ImGui.Separator();
@ -744,7 +756,7 @@ internal class DalamudInterface : IDisposable, IServiceType
} }
ImGui.MenuItem(Util.AssemblyVersion, false); ImGui.MenuItem(Util.AssemblyVersion, false);
ImGui.MenuItem(dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false); ImGui.MenuItem(this.dalamud.StartInfo.GameVersion?.ToString() ?? "Unknown version", false);
ImGui.MenuItem($"D: {Util.GetGitHash()}[{Util.GetGitCommitCount()}] CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.Interop.Resolver.Version}]", false); ImGui.MenuItem($"D: {Util.GetGitHash()}[{Util.GetGitCommitCount()}] CS: {Util.GetGitHashClientStructs()}[{FFXIVClientStructs.Interop.Resolver.Version}]", false);
ImGui.MenuItem($"CLR: {Environment.Version}", false); ImGui.MenuItem($"CLR: {Environment.Version}", false);
@ -766,10 +778,10 @@ internal class DalamudInterface : IDisposable, IServiceType
ImGuiManagedAsserts.AssertsEnabled = val; ImGuiManagedAsserts.AssertsEnabled = val;
} }
if (ImGui.MenuItem("Enable asserts at startup", null, configuration.AssertsEnabledAtStartup)) if (ImGui.MenuItem("Enable asserts at startup", null, this.configuration.AssertsEnabledAtStartup))
{ {
configuration.AssertsEnabledAtStartup = !configuration.AssertsEnabledAtStartup; this.configuration.AssertsEnabledAtStartup = !this.configuration.AssertsEnabledAtStartup;
configuration.QueueSave(); this.configuration.QueueSave();
} }
if (ImGui.MenuItem("Clear focus")) if (ImGui.MenuItem("Clear focus"))
@ -779,7 +791,7 @@ internal class DalamudInterface : IDisposable, IServiceType
if (ImGui.MenuItem("Clear stacks")) if (ImGui.MenuItem("Clear stacks"))
{ {
Service<InterfaceManager>.Get().ClearStacks(); this.interfaceManager.ClearStacks();
} }
if (ImGui.MenuItem("Dump style")) if (ImGui.MenuItem("Dump style"))
@ -792,7 +804,7 @@ internal class DalamudInterface : IDisposable, IServiceType
{ {
if (propertyInfo.PropertyType == typeof(Vector2)) if (propertyInfo.PropertyType == typeof(Vector2))
{ {
var vec2 = (Vector2)propertyInfo.GetValue(style); var vec2 = (Vector2)propertyInfo.GetValue(style)!;
info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n"; info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n";
} }
else else
@ -815,9 +827,9 @@ internal class DalamudInterface : IDisposable, IServiceType
Log.Information(info); Log.Information(info);
} }
if (ImGui.MenuItem("Show dev bar info", null, configuration.ShowDevBarInfo)) if (ImGui.MenuItem("Show dev bar info", null, this.configuration.ShowDevBarInfo))
{ {
configuration.ShowDevBarInfo = !configuration.ShowDevBarInfo; this.configuration.ShowDevBarInfo = !this.configuration.ShowDevBarInfo;
} }
ImGui.EndMenu(); ImGui.EndMenu();
@ -827,7 +839,7 @@ internal class DalamudInterface : IDisposable, IServiceType
{ {
if (ImGui.MenuItem("Replace ExceptionHandler")) if (ImGui.MenuItem("Replace ExceptionHandler"))
{ {
dalamud.ReplaceExceptionHandler(); this.dalamud.ReplaceExceptionHandler();
} }
ImGui.EndMenu(); ImGui.EndMenu();
@ -922,7 +934,7 @@ internal class DalamudInterface : IDisposable, IServiceType
if (Service<GameGui>.Get().GameUiHidden) if (Service<GameGui>.Get().GameUiHidden)
ImGui.BeginMenu("UI is hidden...", false); ImGui.BeginMenu("UI is hidden...", false);
if (configuration.ShowDevBarInfo) if (this.configuration.ShowDevBarInfo)
{ {
ImGui.PushFont(InterfaceManager.MonoFont); ImGui.PushFont(InterfaceManager.MonoFont);
@ -931,9 +943,9 @@ internal class DalamudInterface : IDisposable, IServiceType
ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false); ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("000"), false);
ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false); ImGui.BeginMenu($"W:{Util.FormatBytes(GC.GetTotalMemory(false))}", false);
var videoMem = Service<InterfaceManager>.Get().GetD3dMemoryInfo(); var videoMem = this.interfaceManager.GetD3dMemoryInfo();
ImGui.BeginMenu( ImGui.BeginMenu(
!videoMem.HasValue ? $"V:???" : $"V:{Util.FormatBytes(videoMem.Value.Used)}", !videoMem.HasValue ? "V:???" : $"V:{Util.FormatBytes(videoMem.Value.Used)}",
false); false);
ImGui.PopFont(); ImGui.PopFont();

View file

@ -1285,7 +1285,7 @@ internal class InterfaceManager : IDisposable, IServiceType
/// <summary> /// <summary>
/// Represents an instance of InstanceManager with scene ready for use. /// Represents an instance of InstanceManager with scene ready for use.
/// </summary> /// </summary>
[ServiceManager.Service] [ServiceManager.ProvidedService]
public class InterfaceManagerWithScene : IServiceType public class InterfaceManagerWithScene : IServiceType
{ {
/// <summary> /// <summary>

View file

@ -67,7 +67,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
// If we are going to show a changelog, make sure we have the font ready, otherwise it will hitch // If we are going to show a changelog, make sure we have the font ready, otherwise it will hitch
if (WarrantsChangelog()) if (WarrantsChangelog())
this.MakeFont(); Service<GameFontManager>.GetAsync().ContinueWith(t => this.MakeFont(t.Result));
} }
private enum State private enum State
@ -98,7 +98,7 @@ internal sealed class ChangelogWindow : Window, IDisposable
Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(true); Service<DalamudInterface>.Get().SetCreditsDarkeningAnimation(true);
this.tsmWindow.AllowDrawing = false; this.tsmWindow.AllowDrawing = false;
this.MakeFont(); this.MakeFont(Service<GameFontManager>.Get());
this.state = State.WindowFadeIn; this.state = State.WindowFadeIn;
this.windowFade.Reset(); this.windowFade.Reset();
@ -379,12 +379,6 @@ internal sealed class ChangelogWindow : Window, IDisposable
this.logoTexture.Dispose(); this.logoTexture.Dispose();
} }
private void MakeFont() private void MakeFont(GameFontManager gfm) =>
{ this.bannerFont ??= gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.MiedingerMid18));
if (this.bannerFont == null)
{
var gfm = Service<GameFontManager>.Get();
this.bannerFont = gfm.NewFontRef(new GameFontStyle(GameFontFamilyAndSize.MiedingerMid18));
}
}
} }

View file

@ -56,11 +56,10 @@ internal class ConsoleWindow : Window, IDisposable
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class. /// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
/// </summary> /// </summary>
public ConsoleWindow() /// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
public ConsoleWindow(DalamudConfiguration configuration)
: base("Dalamud Console", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse) : base("Dalamud Console", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
{ {
var configuration = Service<DalamudConfiguration>.Get();
this.autoScroll = configuration.LogAutoScroll; this.autoScroll = configuration.LogAutoScroll;
this.autoOpen = configuration.LogOpenAtStartup; this.autoOpen = configuration.LogOpenAtStartup;
SerilogEventSink.Instance.LogLine += this.OnLogLine; SerilogEventSink.Instance.LogLine += this.OnLogLine;

View file

@ -69,7 +69,7 @@ internal class PluginInstallerWindow : Window, IDisposable
private string[] testerImagePaths = new string[5]; private string[] testerImagePaths = new string[5];
private string testerIconPath = string.Empty; private string testerIconPath = string.Empty;
private IDalamudTextureWrap?[] testerImages; private IDalamudTextureWrap?[]? testerImages;
private IDalamudTextureWrap? testerIcon; private IDalamudTextureWrap? testerIcon;
private bool testerError = false; private bool testerError = false;
@ -132,9 +132,10 @@ internal class PluginInstallerWindow : Window, IDisposable
/// Initializes a new instance of the <see cref="PluginInstallerWindow"/> class. /// Initializes a new instance of the <see cref="PluginInstallerWindow"/> class.
/// </summary> /// </summary>
/// <param name="imageCache">An instance of <see cref="PluginImageCache"/> class.</param> /// <param name="imageCache">An instance of <see cref="PluginImageCache"/> class.</param>
public PluginInstallerWindow(PluginImageCache imageCache) /// <param name="configuration">An instance of <see cref="DalamudConfiguration"/>.</param>
public PluginInstallerWindow(PluginImageCache imageCache, DalamudConfiguration configuration)
: base( : base(
Locs.WindowTitle + (Service<DalamudConfiguration>.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller", Locs.WindowTitle + (configuration.DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar) ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar)
{ {
this.IsOpen = true; this.IsOpen = true;

View file

@ -12,8 +12,8 @@ using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using ImGuiNET; using ImGuiNET;
using ImGuiScene;
namespace Dalamud.Interface.Internal.Windows; namespace Dalamud.Interface.Internal.Windows;
@ -25,6 +25,12 @@ internal class TitleScreenMenuWindow : Window, IDisposable
private const float TargetFontSizePt = 18f; private const float TargetFontSizePt = 18f;
private const float TargetFontSizePx = TargetFontSizePt * 4 / 3; private const float TargetFontSizePx = TargetFontSizePt * 4 / 3;
private readonly ClientState clientState;
private readonly DalamudConfiguration configuration;
private readonly Framework framework;
private readonly GameGui gameGui;
private readonly TitleScreenMenu titleScreenMenu;
private readonly IDalamudTextureWrap shadeTexture; private readonly IDalamudTextureWrap shadeTexture;
private readonly Dictionary<Guid, InOutCubic> shadeEasings = new(); private readonly Dictionary<Guid, InOutCubic> shadeEasings = new();
@ -39,12 +45,32 @@ internal class TitleScreenMenuWindow : Window, IDisposable
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TitleScreenMenuWindow"/> class. /// Initializes a new instance of the <see cref="TitleScreenMenuWindow"/> class.
/// </summary> /// </summary>
public TitleScreenMenuWindow() /// <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="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,
Framework framework,
GameGui gameGui,
InterfaceManager interfaceManager,
TitleScreenMenu titleScreenMenu)
: base( : base(
"TitleScreenMenuOverlay", "TitleScreenMenuOverlay",
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar |
ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus) ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNavFocus)
{ {
this.clientState = clientState;
this.configuration = configuration;
this.framework = framework;
this.gameGui = gameGui;
this.titleScreenMenu = titleScreenMenu;
this.IsOpen = true; this.IsOpen = true;
this.DisableWindowSounds = true; this.DisableWindowSounds = true;
this.ForceMainWindow = true; this.ForceMainWindow = true;
@ -53,14 +79,10 @@ internal class TitleScreenMenuWindow : Window, IDisposable
this.PositionCondition = ImGuiCond.Always; this.PositionCondition = ImGuiCond.Always;
this.RespectCloseHotkey = false; this.RespectCloseHotkey = false;
var dalamud = Service<Dalamud>.Get();
var interfaceManager = Service<InterfaceManager>.Get();
var shadeTex = var shadeTex =
interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmShade.png")); interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "tsmShade.png"));
this.shadeTexture = shadeTex ?? throw new Exception("Could not load TSM background texture."); this.shadeTexture = shadeTex ?? throw new Exception("Could not load TSM background texture.");
var framework = Service<Framework>.Get();
framework.Update += this.FrameworkOnUpdate; framework.Update += this.FrameworkOnUpdate;
} }
@ -95,8 +117,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
public void Dispose() public void Dispose()
{ {
this.shadeTexture.Dispose(); this.shadeTexture.Dispose();
var framework = Service<Framework>.Get(); this.framework.Update -= this.FrameworkOnUpdate;
framework.Update -= this.FrameworkOnUpdate;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -106,9 +127,7 @@ internal class TitleScreenMenuWindow : Window, IDisposable
return; return;
var scale = ImGui.GetIO().FontGlobalScale; var scale = ImGui.GetIO().FontGlobalScale;
var entries = Service<TitleScreenMenu>.Get().Entries var entries = this.titleScreenMenu.Entries.OrderByDescending(x => x.IsInternal).ToList();
.OrderByDescending(x => x.IsInternal)
.ToList();
switch (this.state) switch (this.state)
{ {
@ -369,17 +388,14 @@ internal class TitleScreenMenuWindow : Window, IDisposable
private void FrameworkOnUpdate(IFramework framework) private void FrameworkOnUpdate(IFramework framework)
{ {
var clientState = Service<ClientState>.Get(); this.IsOpen = !this.clientState.IsLoggedIn;
this.IsOpen = !clientState.IsLoggedIn;
var configuration = Service<DalamudConfiguration>.Get(); if (!this.configuration.ShowTsm)
if (!configuration.ShowTsm)
this.IsOpen = false; this.IsOpen = false;
var gameGui = Service<GameGui>.Get(); var charaSelect = this.gameGui.GetAddonByName("CharaSelect", 1);
var charaSelect = gameGui.GetAddonByName("CharaSelect", 1); var charaMake = this.gameGui.GetAddonByName("CharaMake", 1);
var charaMake = gameGui.GetAddonByName("CharaMake", 1); var titleDcWorldMap = this.gameGui.GetAddonByName("TitleDCWorldMap", 1);
var titleDcWorldMap = gameGui.GetAddonByName("TitleDCWorldMap", 1);
if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero) if (charaMake != IntPtr.Zero || charaSelect != IntPtr.Zero || titleDcWorldMap != IntPtr.Zero)
this.IsOpen = false; this.IsOpen = false;
} }

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -16,7 +15,7 @@ namespace Dalamud.IoC.Internal;
/// This is only used to resolve dependencies for plugins. /// This is only used to resolve dependencies for plugins.
/// Dalamud services are constructed via Service{T}.ConstructObject at the moment. /// Dalamud services are constructed via Service{T}.ConstructObject at the moment.
/// </summary> /// </summary>
[ServiceManager.Service] [ServiceManager.ProvidedService]
internal class ServiceContainer : IServiceProvider, IServiceType internal class ServiceContainer : IServiceProvider, IServiceType
{ {
private static readonly ModuleLog Log = new("SERVICECONTAINER"); private static readonly ModuleLog Log = new("SERVICECONTAINER");
@ -228,7 +227,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType)) if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
serviceType = implementingType; serviceType = implementingType;
if (serviceType.GetCustomAttribute<ServiceManager.ScopedService>() != null) if (serviceType.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null)
{ {
if (scope == null) if (scope == null)
{ {
@ -299,7 +298,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
var contains = types.Any(x => x.IsAssignableTo(type)); var contains = types.Any(x => x.IsAssignableTo(type));
// Scoped services are created on-demand // Scoped services are created on-demand
return contains || type.GetCustomAttribute<ServiceManager.ScopedService>() != null; return contains || type.GetCustomAttribute<ServiceManager.ScopedServiceAttribute>() != null;
} }
var parameters = ctor.GetParameters(); var parameters = ctor.GetParameters();

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -40,7 +39,7 @@ namespace Dalamud.Plugin.Internal;
/// Class responsible for loading and unloading plugins. /// Class responsible for loading and unloading plugins.
/// NOTE: ALL plugin exposed services are marked as dependencies for PluginManager in Service{T}. /// NOTE: ALL plugin exposed services are marked as dependencies for PluginManager in Service{T}.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
#pragma warning disable SA1015 #pragma warning disable SA1015
// DalamudTextureWrap registers textures to dispose with IM // DalamudTextureWrap registers textures to dispose with IM
@ -85,6 +84,9 @@ internal partial class PluginManager : IDisposable, IServiceType
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get(); private readonly HappyHttpClient happyHttpClient = Service<HappyHttpClient>.Get();
[ServiceManager.ServiceDependency]
private readonly ChatGui chatGui = Service<ChatGui>.Get();
static PluginManager() static PluginManager()
{ {
DalamudApiLevel = typeof(PluginManager).Assembly.GetName().Version!.Major; DalamudApiLevel = typeof(PluginManager).Assembly.GetName().Version!.Major;
@ -131,12 +133,13 @@ internal partial class PluginManager : IDisposable, IServiceType
throw new InvalidDataException("Couldn't deserialize banned plugins manifest."); throw new InvalidDataException("Couldn't deserialize banned plugins manifest.");
} }
this.openInstallerWindowPluginChangelogsLink = Service<ChatGui>.Get().AddChatLinkHandler("Dalamud", 1003, (_, _) => this.openInstallerWindowPluginChangelogsLink = this.chatGui.AddChatLinkHandler("Dalamud", 1003, (_, _) =>
{ {
Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(PluginInstallerWindow.PluginInstallerOpenKind.Changelogs); Service<DalamudInterface>.GetNullable()?.OpenPluginInstallerTo(PluginInstallerWindow.PluginInstallerOpenKind.Changelogs);
}); });
this.configuration.PluginTestingOptIns ??= new List<PluginTestingOptIn>(); this.configuration.PluginTestingOptIns ??= new();
this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient);
this.ApplyPatches(); this.ApplyPatches();
} }
@ -199,6 +202,11 @@ internal partial class PluginManager : IDisposable, IServiceType
} }
} }
/// <summary>
/// Gets the main repository.
/// </summary>
public PluginRepository MainRepo { get; }
/// <summary> /// <summary>
/// Gets a list of all plugin repositories. The main repo should always be first. /// Gets a list of all plugin repositories. The main repo should always be first.
/// </summary> /// </summary>
@ -284,11 +292,9 @@ internal partial class PluginManager : IDisposable, IServiceType
/// <param name="header">The header text to send to chat prior to any update info.</param> /// <param name="header">The header text to send to chat prior to any update info.</param>
public void PrintUpdatedPlugins(List<PluginUpdateStatus>? updateMetadata, string header) public void PrintUpdatedPlugins(List<PluginUpdateStatus>? updateMetadata, string header)
{ {
var chatGui = Service<ChatGui>.Get();
if (updateMetadata is { Count: > 0 }) if (updateMetadata is { Count: > 0 })
{ {
chatGui.Print(new XivChatEntry this.chatGui.Print(new XivChatEntry
{ {
Message = new SeString(new List<Payload>() Message = new SeString(new List<Payload>()
{ {
@ -307,11 +313,11 @@ internal partial class PluginManager : IDisposable, IServiceType
{ {
if (metadata.Status == PluginUpdateStatus.StatusKind.Success) if (metadata.Status == PluginUpdateStatus.StatusKind.Success)
{ {
chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version)); this.chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version));
} }
else else
{ {
chatGui.Print(new XivChatEntry this.chatGui.Print(new XivChatEntry
{ {
Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version, PluginUpdateStatus.LocalizeUpdateStatusKind(metadata.Status)), Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version, PluginUpdateStatus.LocalizeUpdateStatusKind(metadata.Status)),
Type = XivChatType.Urgent, Type = XivChatType.Urgent,
@ -407,10 +413,10 @@ internal partial class PluginManager : IDisposable, IServiceType
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public async Task SetPluginReposFromConfigAsync(bool notify) public async Task SetPluginReposFromConfigAsync(bool notify)
{ {
var repos = new List<PluginRepository>() { PluginRepository.MainRepo }; var repos = new List<PluginRepository> { this.MainRepo };
repos.AddRange(this.configuration.ThirdRepoList repos.AddRange(this.configuration.ThirdRepoList
.Where(repo => repo.IsEnabled) .Where(repo => repo.IsEnabled)
.Select(repo => new PluginRepository(repo.Url, repo.IsEnabled))); .Select(repo => new PluginRepository(this.happyHttpClient, repo.Url, repo.IsEnabled)));
this.Repos = repos; this.Repos = repos;
await this.ReloadPluginMastersAsync(notify); await this.ReloadPluginMastersAsync(notify);

View file

@ -267,10 +267,6 @@ internal class LocalPlugin : IDisposable
var pluginManager = await Service<PluginManager>.GetAsync(); var pluginManager = await Service<PluginManager>.GetAsync();
var dalamud = await Service<Dalamud>.GetAsync(); var dalamud = await Service<Dalamud>.GetAsync();
// UiBuilder constructor requires the following two.
await Service<InterfaceManager>.GetAsync();
await Service<GameFontManager>.GetAsync();
if (this.manifest.LoadRequiredState == 0) if (this.manifest.LoadRequiredState == 0)
_ = await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync(); _ = await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync();

View file

@ -28,47 +28,44 @@ internal class PluginRepository
private static readonly ModuleLog Log = new("PLUGINR"); private static readonly ModuleLog Log = new("PLUGINR");
private static readonly HttpClient HttpClient = new(new SocketsHttpHandler private readonly HttpClient httpClient;
{
AutomaticDecompression = DecompressionMethods.All,
ConnectCallback = Service<HappyHttpClient>.Get().SharedHappyEyeballsCallback.ConnectCallback,
})
{
Timeout = TimeSpan.FromSeconds(20),
DefaultRequestHeaders =
{
Accept =
{
new MediaTypeWithQualityHeaderValue("application/json"),
},
CacheControl = new CacheControlHeaderValue
{
NoCache = true,
},
UserAgent =
{
new ProductInfoHeaderValue("Dalamud", Util.AssemblyVersion),
},
},
};
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PluginRepository"/> class. /// Initializes a new instance of the <see cref="PluginRepository"/> class.
/// </summary> /// </summary>
/// <param name="happyHttpClient">An instance of <see cref="HappyHttpClient"/>.</param>
/// <param name="pluginMasterUrl">The plugin master URL.</param> /// <param name="pluginMasterUrl">The plugin master URL.</param>
/// <param name="isEnabled">Whether the plugin repo is enabled.</param> /// <param name="isEnabled">Whether the plugin repo is enabled.</param>
public PluginRepository(string pluginMasterUrl, bool isEnabled) public PluginRepository(HappyHttpClient happyHttpClient, string pluginMasterUrl, bool isEnabled)
{ {
this.httpClient = new(new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.All,
ConnectCallback = happyHttpClient.SharedHappyEyeballsCallback.ConnectCallback,
})
{
Timeout = TimeSpan.FromSeconds(20),
DefaultRequestHeaders =
{
Accept =
{
new MediaTypeWithQualityHeaderValue("application/json"),
},
CacheControl = new CacheControlHeaderValue
{
NoCache = true,
},
UserAgent =
{
new ProductInfoHeaderValue("Dalamud", Util.AssemblyVersion),
},
},
};
this.PluginMasterUrl = pluginMasterUrl; this.PluginMasterUrl = pluginMasterUrl;
this.IsThirdParty = pluginMasterUrl != MainRepoUrl; this.IsThirdParty = pluginMasterUrl != MainRepoUrl;
this.IsEnabled = isEnabled; this.IsEnabled = isEnabled;
} }
/// <summary>
/// Gets a new instance of the <see cref="PluginRepository"/> class for the main repo.
/// </summary>
public static PluginRepository MainRepo => new(MainRepoUrl, true);
/// <summary> /// <summary>
/// Gets the pluginmaster.json URL. /// Gets the pluginmaster.json URL.
/// </summary> /// </summary>
@ -94,6 +91,14 @@ internal class PluginRepository
/// </summary> /// </summary>
public PluginRepositoryState State { get; private set; } public PluginRepositoryState State { get; private set; }
/// <summary>
/// Gets a new instance of the <see cref="PluginRepository"/> class for the main repo.
/// </summary>
/// <param name="happyHttpClient">An instance of <see cref="HappyHttpClient"/>.</param>
/// <returns>The new instance of main repository.</returns>
public static PluginRepository CreateMainRepo(HappyHttpClient happyHttpClient) =>
new(happyHttpClient, MainRepoUrl, true);
/// <summary> /// <summary>
/// Reload the plugin master asynchronously in a task. /// Reload the plugin master asynchronously in a task.
/// </summary> /// </summary>
@ -107,7 +112,7 @@ internal class PluginRepository
{ {
Log.Information($"Fetching repo: {this.PluginMasterUrl}"); Log.Information($"Fetching repo: {this.PluginMasterUrl}");
using var response = await HttpClient.GetAsync(this.PluginMasterUrl); using var response = await this.httpClient.GetAsync(this.PluginMasterUrl);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsStringAsync(); var data = await response.Content.ReadAsStringAsync();

View file

@ -13,7 +13,7 @@ namespace Dalamud.Plugin.Ipc.Internal;
/// <summary> /// <summary>
/// This class facilitates sharing data-references of standard types between plugins without using more expensive IPC. /// This class facilitates sharing data-references of standard types between plugins without using more expensive IPC.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal class DataShare : IServiceType internal class DataShare : IServiceType
{ {
private readonly Dictionary<string, DataCache> caches = new(); private readonly Dictionary<string, DataCache> caches = new();

View file

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
@ -29,9 +30,17 @@ internal static class ServiceManager
/// </summary> /// </summary>
public static readonly ModuleLog Log = new("SVC"); public static readonly ModuleLog Log = new("SVC");
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new(); #if DEBUG
/// <summary>
/// Marks which service constructor the current thread's in. For use from <see cref="Service{T}"/> only.
/// </summary>
internal static readonly ThreadLocal<Type?> CurrentConstructorServiceType = new();
[SuppressMessage("ReSharper", "CollectionNeverQueried.Local", Justification = "Debugging purposes")]
private static readonly List<Type> LoadedServices = new(); private static readonly List<Type> LoadedServices = new();
#endif
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
private static ManualResetEvent unloadResetEvent = new(false); private static ManualResetEvent unloadResetEvent = new(false);
@ -86,21 +95,34 @@ internal static class ServiceManager
/// <param name="scanner">Instance of <see cref="TargetSigScanner"/>.</param> /// <param name="scanner">Instance of <see cref="TargetSigScanner"/>.</param>
public static void InitializeProvidedServices(Dalamud dalamud, ReliableFileStorage fs, DalamudConfiguration configuration, TargetSigScanner scanner) public static void InitializeProvidedServices(Dalamud dalamud, ReliableFileStorage fs, DalamudConfiguration configuration, TargetSigScanner scanner)
{ {
#if DEBUG
lock (LoadedServices) lock (LoadedServices)
{ {
void ProvideService<T>(T service) where T : IServiceType
{
Debug.Assert(typeof(T).GetServiceKind().HasFlag(ServiceKind.ProvidedService), "Provided service must have Service attribute");
Service<T>.Provide(service);
LoadedServices.Add(typeof(T));
}
ProvideService(dalamud); ProvideService(dalamud);
ProvideService(fs); ProvideService(fs);
ProvideService(configuration); ProvideService(configuration);
ProvideService(new ServiceContainer()); ProvideService(new ServiceContainer());
ProvideService(scanner); ProvideService(scanner);
} }
return;
void ProvideService<T>(T service) where T : IServiceType
{
Debug.Assert(typeof(T).GetServiceKind().HasFlag(ServiceKind.ProvidedService), "Provided service must have Service attribute");
Service<T>.Provide(service);
LoadedServices.Add(typeof(T));
}
#else
ProvideService(dalamud);
ProvideService(fs);
ProvideService(configuration);
ProvideService(new ServiceContainer());
ProvideService(scanner);
return;
void ProvideService<T>(T service) where T : IServiceType => Service<T>.Provide(service);
#endif
} }
/// <summary> /// <summary>
@ -171,7 +193,22 @@ internal static class ServiceManager
{ {
try try
{ {
await Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x])); var whenBlockingComplete = Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
while (await Task.WhenAny(whenBlockingComplete, Task.Delay(30000)) != whenBlockingComplete)
{
if (NativeFunctions.MessageBoxW(
IntPtr.Zero,
"Dalamud is taking a long time to load. Would you like to continue without Dalamud?\n" +
"This can be caused by a faulty plugin, or a bug in Dalamud.",
"Dalamud",
NativeFunctions.MessageBoxType.IconWarning | NativeFunctions.MessageBoxType.YesNo) == 6)
{
throw new TimeoutException(
"Failed to load services in the given time limit, " +
"and the user chose to continue without Dalamud.");
}
}
BlockingServicesLoadedTaskCompletionSource.SetResult(); BlockingServicesLoadedTaskCompletionSource.SetResult();
Timings.Event("BlockingServices Initialized"); Timings.Event("BlockingServices Initialized");
} }
@ -215,13 +252,14 @@ internal static class ServiceManager
tasks.Add((Task)typeof(Service<>) tasks.Add((Task)typeof(Service<>)
.MakeGenericType(serviceType) .MakeGenericType(serviceType)
.InvokeMember( .InvokeMember(
"StartLoader", nameof(Service<IServiceType>.StartLoader),
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic, BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.NonPublic,
null, null,
null, null,
null)); null));
servicesToLoad.Remove(serviceType); servicesToLoad.Remove(serviceType);
#if DEBUG
tasks.Add(tasks.Last().ContinueWith(task => tasks.Add(tasks.Last().ContinueWith(task =>
{ {
if (task.IsFaulted) if (task.IsFaulted)
@ -231,6 +269,7 @@ internal static class ServiceManager
LoadedServices.Add(serviceType); LoadedServices.Add(serviceType);
} }
})); }));
#endif
} }
if (!tasks.Any()) if (!tasks.Any())
@ -350,10 +389,12 @@ internal static class ServiceManager
null); null);
} }
#if DEBUG
lock (LoadedServices) lock (LoadedServices)
{ {
LoadedServices.Clear(); LoadedServices.Clear();
} }
#endif
unloadResetEvent.Set(); unloadResetEvent.Set();
} }
@ -373,7 +414,7 @@ internal static class ServiceManager
/// <returns>The type of service this type is.</returns> /// <returns>The type of service this type is.</returns>
public static ServiceKind GetServiceKind(this Type type) public static ServiceKind GetServiceKind(this Type type)
{ {
var attr = type.GetCustomAttribute<Service>(true)?.GetType(); var attr = type.GetCustomAttribute<ServiceAttribute>(true)?.GetType();
if (attr == null) if (attr == null)
return ServiceKind.None; return ServiceKind.None;
@ -381,13 +422,13 @@ internal static class ServiceManager
type.IsAssignableTo(typeof(IServiceType)), type.IsAssignableTo(typeof(IServiceType)),
"Service did not inherit from IServiceType"); "Service did not inherit from IServiceType");
if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedService))) if (attr.IsAssignableTo(typeof(BlockingEarlyLoadedServiceAttribute)))
return ServiceKind.BlockingEarlyLoadedService; return ServiceKind.BlockingEarlyLoadedService;
if (attr.IsAssignableTo(typeof(EarlyLoadedService))) if (attr.IsAssignableTo(typeof(EarlyLoadedServiceAttribute)))
return ServiceKind.EarlyLoadedService; return ServiceKind.EarlyLoadedService;
if (attr.IsAssignableTo(typeof(ScopedService))) if (attr.IsAssignableTo(typeof(ScopedServiceAttribute)))
return ServiceKind.ScopedService; return ServiceKind.ScopedService;
return ServiceKind.ProvidedService; return ServiceKind.ProvidedService;
@ -414,16 +455,57 @@ internal static class ServiceManager
/// Indicates that the class is a service. /// Indicates that the class is a service.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class Service : Attribute public abstract class ServiceAttribute : Attribute
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ServiceAttribute"/> class.
/// </summary>
/// <param name="kind">The kind of the service.</param>
protected ServiceAttribute(ServiceKind kind) => this.Kind = kind;
/// <summary>
/// Gets the kind of the service.
/// </summary>
public ServiceKind Kind { get; }
}
/// <summary>
/// Indicates that the class is a service, that is provided by some other source.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ProvidedServiceAttribute : ServiceAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="ProvidedServiceAttribute"/> class.
/// </summary>
public ProvidedServiceAttribute()
: base(ServiceKind.ProvidedService)
{
}
} }
/// <summary> /// <summary>
/// Indicates that the class is a service, and will be instantiated automatically on startup. /// Indicates that the class is a service, and will be instantiated automatically on startup.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class EarlyLoadedService : Service public class EarlyLoadedServiceAttribute : ServiceAttribute
{ {
/// <summary>
/// Initializes a new instance of the <see cref="EarlyLoadedServiceAttribute"/> class.
/// </summary>
public EarlyLoadedServiceAttribute()
: this(ServiceKind.EarlyLoadedService)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EarlyLoadedServiceAttribute"/> class.
/// </summary>
/// <param name="kind">The service kind.</param>
protected EarlyLoadedServiceAttribute(ServiceKind kind)
: base(kind)
{
}
} }
/// <summary> /// <summary>
@ -431,8 +513,15 @@ internal static class ServiceManager
/// blocking game main thread until it completes. /// blocking game main thread until it completes.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class BlockingEarlyLoadedService : EarlyLoadedService public class BlockingEarlyLoadedServiceAttribute : EarlyLoadedServiceAttribute
{ {
/// <summary>
/// Initializes a new instance of the <see cref="BlockingEarlyLoadedServiceAttribute"/> class.
/// </summary>
public BlockingEarlyLoadedServiceAttribute()
: base(ServiceKind.BlockingEarlyLoadedService)
{
}
} }
/// <summary> /// <summary>
@ -440,8 +529,15 @@ internal static class ServiceManager
/// service scope, and that it cannot be created outside of a scope. /// service scope, and that it cannot be created outside of a scope.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]
public class ScopedService : Service public class ScopedServiceAttribute : ServiceAttribute
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ScopedServiceAttribute"/> class.
/// </summary>
public ScopedServiceAttribute()
: base(ServiceKind.ScopedService)
{
}
} }
/// <summary> /// <summary>

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -20,17 +19,26 @@ namespace Dalamud;
/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI. /// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI.
/// </remarks> /// </remarks>
/// <typeparam name="T">The class you want to store in the service locator.</typeparam> /// <typeparam name="T">The class you want to store in the service locator.</typeparam>
[SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "Service container static type")]
internal static class Service<T> where T : IServiceType internal static class Service<T> where T : IServiceType
{ {
private static readonly ServiceManager.ServiceAttribute ServiceAttribute;
private static TaskCompletionSource<T> instanceTcs = new(); private static TaskCompletionSource<T> instanceTcs = new();
private static List<Type>? dependencyServices;
static Service() static Service()
{ {
var exposeToPlugins = typeof(T).GetCustomAttribute<PluginInterfaceAttribute>() != null; var type = typeof(T);
ServiceAttribute =
type.GetCustomAttribute<ServiceManager.ServiceAttribute>(true)
?? throw new InvalidOperationException(
$"{nameof(T)} is missing {nameof(ServiceManager.ServiceAttribute)} annotations.");
var exposeToPlugins = type.GetCustomAttribute<PluginInterfaceAttribute>() != null;
if (exposeToPlugins) if (exposeToPlugins)
ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Static ctor called; will be exposed to plugins", type.Name);
else else
ServiceManager.Log.Debug("Service<{0}>: Static ctor called", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Static ctor called", type.Name);
if (exposeToPlugins) if (exposeToPlugins)
Service<ServiceContainer>.Get().RegisterSingleton(instanceTcs.Task); Service<ServiceContainer>.Get().RegisterSingleton(instanceTcs.Task);
@ -63,8 +71,8 @@ internal static class Service<T> where T : IServiceType
/// <param name="obj">Object to set.</param> /// <param name="obj">Object to set.</param>
public static void Provide(T obj) public static void Provide(T obj)
{ {
instanceTcs.SetResult(obj);
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
instanceTcs.SetResult(obj);
} }
/// <summary> /// <summary>
@ -83,6 +91,21 @@ internal static class Service<T> where T : IServiceType
/// <returns>The object.</returns> /// <returns>The object.</returns>
public static T Get() public static T Get()
{ {
#if DEBUG
if (ServiceAttribute.Kind != ServiceManager.ServiceKind.ProvidedService
&& ServiceManager.CurrentConstructorServiceType.Value is { } currentServiceType)
{
var deps = ServiceHelpers.GetDependencies(currentServiceType);
if (!deps.Contains(typeof(T)))
{
throw new InvalidOperationException(
$"Calling {nameof(Service<IServiceType>)}<{typeof(T)}>.{nameof(Get)} which is not one of the" +
$" dependency services is forbidden from the service constructor of {currentServiceType}." +
$" This has a high chance of introducing hard-to-debug hangs.");
}
}
#endif
if (!instanceTcs.Task.IsCompleted) if (!instanceTcs.Task.IsCompleted)
instanceTcs.Task.Wait(); instanceTcs.Task.Wait();
return instanceTcs.Task.Result; return instanceTcs.Task.Result;
@ -116,12 +139,16 @@ internal static class Service<T> where T : IServiceType
} }
/// <summary> /// <summary>
/// Gets an enumerable containing Service&lt;T&gt;s that are required for this Service to initialize without blocking. /// Gets an enumerable containing <see cref="Service{T}"/>s that are required for this Service to initialize
/// without blocking.
/// </summary> /// </summary>
/// <returns>List of dependency services.</returns> /// <returns>List of dependency services.</returns>
[UsedImplicitly] [UsedImplicitly]
public static List<Type> GetDependencyServices() public static List<Type> GetDependencyServices()
{ {
if (dependencyServices is not null)
return dependencyServices;
var res = new List<Type>(); var res = new List<Type>();
ServiceManager.Log.Verbose("Service<{0}>: Getting dependencies", typeof(T).Name); ServiceManager.Log.Verbose("Service<{0}>: Getting dependencies", typeof(T).Name);
@ -189,19 +216,42 @@ internal static class Service<T> where T : IServiceType
ServiceManager.Log.Verbose("Service<{0}>: => Dependency: {1}", typeof(T).Name, type.Name); ServiceManager.Log.Verbose("Service<{0}>: => Dependency: {1}", typeof(T).Name, type.Name);
} }
return res var deps = res
.Distinct() .Distinct()
.ToList(); .ToList();
if (typeof(T).GetCustomAttribute<ServiceManager.BlockingEarlyLoadedServiceAttribute>() is not null)
{
var offenders = deps.Where(
x => x.GetCustomAttribute<ServiceManager.ServiceAttribute>(true)!.Kind
is not ServiceManager.ServiceKind.BlockingEarlyLoadedService
and not ServiceManager.ServiceKind.ProvidedService)
.ToArray();
if (offenders.Any())
{
ServiceManager.Log.Error(
"{me} is a {bels}; it can only depend on {bels} and {ps}.\nOffending dependencies:\n{offenders}",
typeof(T),
nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute),
nameof(ServiceManager.BlockingEarlyLoadedServiceAttribute),
nameof(ServiceManager.ProvidedServiceAttribute),
string.Join("\n", offenders.Select(x => $"\t* {x.Name}")));
}
}
return dependencyServices = deps;
} }
[UsedImplicitly] /// <summary>
private static Task<T> StartLoader() /// Starts the service loader. Only to be called from <see cref="ServiceManager"/>.
/// </summary>
/// <returns>The loader task.</returns>
internal static Task<T> StartLoader()
{ {
if (instanceTcs.Task.IsCompleted) if (instanceTcs.Task.IsCompleted)
throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed."); throw new InvalidOperationException($"{typeof(T).Name} is already loaded or disposed.");
var attr = typeof(T).GetCustomAttribute<ServiceManager.Service>(true)?.GetType(); var attr = ServiceAttribute.GetType();
if (attr?.IsAssignableTo(typeof(ServiceManager.EarlyLoadedService)) != true) if (attr.IsAssignableTo(typeof(ServiceManager.EarlyLoadedServiceAttribute)) != true)
throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService"); throw new InvalidOperationException($"{typeof(T).Name} is not an EarlyLoadedService");
return Task.Run(Timings.AttachTimingHandle(async () => return Task.Run(Timings.AttachTimingHandle(async () =>
@ -212,6 +262,7 @@ internal static class Service<T> where T : IServiceType
var instance = await ConstructObject(); var instance = await ConstructObject();
instanceTcs.SetResult(instance); instanceTcs.SetResult(instance);
List<Task>? tasks = null;
foreach (var method in typeof(T).GetMethods( foreach (var method in typeof(T).GetMethods(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{ {
@ -221,9 +272,24 @@ internal static class Service<T> where T : IServiceType
ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name); ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name);
var args = await Task.WhenAll(method.GetParameters().Select( var args = await Task.WhenAll(method.GetParameters().Select(
x => ResolveServiceFromTypeAsync(x.ParameterType))); x => ResolveServiceFromTypeAsync(x.ParameterType)));
method.Invoke(instance, args); try
{
if (method.Invoke(instance, args) is Task task)
{
tasks ??= new();
tasks.Add(task);
}
}
catch (Exception e)
{
tasks ??= new();
tasks.Add(Task.FromException(e));
}
} }
if (tasks is not null)
await Task.WhenAll(tasks);
ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name);
return instance; return instance;
} }
@ -303,7 +369,19 @@ internal static class Service<T> where T : IServiceType
ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType))); ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType)));
using (Timings.Start($"{typeof(T).Name} Construct")) using (Timings.Start($"{typeof(T).Name} Construct"))
{ {
#if DEBUG
ServiceManager.CurrentConstructorServiceType.Value = typeof(Service<T>);
try
{
return (T)ctor.Invoke(args)!;
}
finally
{
ServiceManager.CurrentConstructorServiceType.Value = null;
}
#else
return (T)ctor.Invoke(args)!; return (T)ctor.Invoke(args)!;
#endif
} }
} }
@ -328,30 +406,43 @@ internal static class Service<T> where T : IServiceType
internal static class ServiceHelpers internal static class ServiceHelpers
{ {
/// <summary> /// <summary>
/// Get a list of dependencies for a service. Only accepts Service&lt;T&gt; types. /// Get a list of dependencies for a service. Only accepts <see cref="Service{T}"/> types.
/// These are returned as Service&lt;T&gt; types. /// These are returned as <see cref="Service{T}"/> types.
/// </summary> /// </summary>
/// <param name="serviceType">The dependencies for this service.</param> /// <param name="serviceType">The dependencies for this service.</param>
/// <returns>A list of dependencies.</returns> /// <returns>A list of dependencies.</returns>
public static List<Type> GetDependencies(Type serviceType) public static List<Type> GetDependencies(Type serviceType)
{ {
#if DEBUG
if (!serviceType.IsGenericType || serviceType.GetGenericTypeDefinition() != typeof(Service<>))
{
throw new ArgumentException(
$"Expected an instance of {nameof(Service<IServiceType>)}<>",
nameof(serviceType));
}
#endif
return (List<Type>)serviceType.InvokeMember( return (List<Type>)serviceType.InvokeMember(
"GetDependencyServices", nameof(Service<IServiceType>.GetDependencyServices),
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
null, null,
null, null,
null) ?? new List<Type>(); null) ?? new List<Type>();
} }
/// <summary> /// <summary>
/// Get the Service&lt;T&gt; type for a given service type. /// Get the <see cref="Service{T}"/> type for a given service type.
/// This will throw if the service type is not a valid service. /// This will throw if the service type is not a valid service.
/// </summary> /// </summary>
/// <param name="type">The type to obtain a Service&lt;T&gt; for.</param> /// <param name="type">The type to obtain a <see cref="Service{T}"/> for.</param>
/// <returns>The Service&lt;T&gt;.</returns> /// <returns>The <see cref="Service{T}"/>.</returns>
public static Type GetAsService(Type type) public static Type GetAsService(Type type)
{ {
return typeof(Service<>) #if DEBUG
.MakeGenericType(type); if (!type.IsAssignableTo(typeof(IServiceType)))
throw new ArgumentException($"Expected an instance of {nameof(IServiceType)}", nameof(type));
#endif
return typeof(Service<>).MakeGenericType(type);
} }
} }

View file

@ -21,7 +21,7 @@ namespace Dalamud.Storage;
/// <remarks> /// <remarks>
/// This is not an early-loaded service, as it is needed before they are initialized. /// This is not an early-loaded service, as it is needed before they are initialized.
/// </remarks> /// </remarks>
[ServiceManager.Service] [ServiceManager.ProvidedService]
public class ReliableFileStorage : IServiceType, IDisposable public class ReliableFileStorage : IServiceType, IDisposable
{ {
private static readonly ModuleLog Log = new("VFS"); private static readonly ModuleLog Log = new("VFS");