diff --git a/Dalamud.Injector/Dalamud.Injector.csproj b/Dalamud.Injector/Dalamud.Injector.csproj index 591dc948e..f494c3d6a 100644 --- a/Dalamud.Injector/Dalamud.Injector.csproj +++ b/Dalamud.Injector/Dalamud.Injector.csproj @@ -84,6 +84,7 @@ + diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 40bb48a85..79c3c0013 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -16,7 +16,7 @@ namespace Dalamud.Configuration.Internal /// Class containing Dalamud settings. /// [Serializable] - internal sealed class DalamudConfiguration + internal sealed class DalamudConfiguration : IServiceType { /// /// Currently used beta key for Dalamud staging builds. diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index dc1ff8da1..747e78e25 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -33,7 +33,7 @@ namespace Dalamud /// /// The main Dalamud class containing all subsystems. /// - internal sealed class Dalamud : IDisposable + internal sealed class Dalamud : IDisposable, IServiceType { #region Internals diff --git a/Dalamud/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs index 2fd47e759..904b245fd 100644 --- a/Dalamud/DalamudStartInfo.cs +++ b/Dalamud/DalamudStartInfo.cs @@ -10,7 +10,7 @@ namespace Dalamud /// Struct containing information needed to initialize Dalamud. /// [Serializable] - public record DalamudStartInfo + public record DalamudStartInfo : IServiceType { /// /// Initializes a new instance of the class. diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index b3d90a3a3..48cd0d325 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -27,7 +27,7 @@ namespace Dalamud.Data [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class DataManager : IDisposable + public sealed class DataManager : IDisposable, IServiceType { private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index 1468dbafb..53636a54e 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -29,7 +29,7 @@ namespace Dalamud.Game [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public class ChatHandlers + public class ChatHandlers : IServiceType { // private static readonly Dictionary UnicodeToDiscordEmojiDict = new() // { diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs index e8f4e9da3..dd735bd42 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.Aetherytes [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class AetheryteList + public sealed partial class AetheryteList : IServiceType { [ServiceManager.ServiceDependency] private readonly ClientState clientState = Service.Get(); diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index 85706de6d..e2f34204f 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -16,7 +16,7 @@ namespace Dalamud.Game.ClientState.Buddy [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class BuddyList + public sealed partial class BuddyList : IServiceType { private const uint InvalidObjectID = 0xE0000000; diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index bafb4cdf8..5bdd9c694 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -28,7 +28,7 @@ namespace Dalamud.Game.ClientState [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class ClientState : IDisposable + public sealed class ClientState : IDisposable, IServiceType { private readonly ClientStateAddressResolver address; private readonly Hook setupTerritoryTypeHook; @@ -128,16 +128,6 @@ namespace Dalamud.Game.ClientState /// public bool IsPvPExcludingDen { get; private set; } - /// - /// Enable this module. - /// - public void Enable() - { - Service.Get().Enable(); - Service.Get().Enable(); - this.setupTerritoryTypeHook.Enable(); - } - /// /// Dispose of managed and unmanaged resources. /// @@ -150,6 +140,12 @@ namespace Dalamud.Game.ClientState Service.Get().CfPop -= this.NetworkHandlersOnCfPop; } + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() + { + this.setupTerritoryTypeHook.Enable(); + } + private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) { this.TerritoryType = terriType; diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs index 0bfe44145..6cf0bbdd5 100644 --- a/Dalamud/Game/ClientState/Conditions/Condition.cs +++ b/Dalamud/Game/ClientState/Conditions/Condition.cs @@ -12,7 +12,7 @@ namespace Dalamud.Game.ClientState.Conditions [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class Condition + public sealed partial class Condition : IServiceType { /// /// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has. @@ -82,16 +82,14 @@ namespace Dalamud.Game.ClientState.Conditions return false; } - /// - /// Enables the hooks of the Condition class function. - /// - public void Enable() + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(Framework framework) { // Initialization for (var i = 0; i < MaxConditionEntries; i++) this.cache[i] = this[i]; - Service.Get().Update += this.FrameworkUpdate; + framework.Update += this.FrameworkUpdate; } private void FrameworkUpdate(Framework framework) diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index b1a2d9c22..93a0e60b9 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -14,7 +14,7 @@ namespace Dalamud.Game.ClientState.Fates [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class FateTable + public sealed partial class FateTable : IServiceType { private readonly ClientStateAddressResolver address; diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs index ce452bcfa..05462c6f7 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -16,7 +16,7 @@ namespace Dalamud.Game.ClientState.GamePad [PluginInterface] [InterfaceVersion("1.0.0")] [ServiceManager.BlockingEarlyLoadedService] - public unsafe class GamepadState : IDisposable + public unsafe class GamepadState : IDisposable, IServiceType { private readonly Hook gamepadPoll; @@ -168,10 +168,8 @@ namespace Dalamud.Game.ClientState.GamePad GC.SuppressFinalize(this); } - /// - /// Enables the hook of the GamepadPoll function. - /// - internal void Enable() + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() { this.gamepadPoll.Enable(); } diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index dba74ab67..aab5d4991 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.JobGauge [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public class JobGauges + public class JobGauges : IServiceType { private Dictionary cache = new(); diff --git a/Dalamud/Game/ClientState/Keys/KeyState.cs b/Dalamud/Game/ClientState/Keys/KeyState.cs index 90c3ad872..953f01f75 100644 --- a/Dalamud/Game/ClientState/Keys/KeyState.cs +++ b/Dalamud/Game/ClientState/Keys/KeyState.cs @@ -23,7 +23,7 @@ namespace Dalamud.Game.ClientState.Keys [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public class KeyState + public class KeyState : IServiceType { // The array is accessed in a way that this limit doesn't appear to exist // but there is other state data past this point, and keys beyond here aren't diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index 5500aa533..a5ae12aed 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -17,7 +17,7 @@ namespace Dalamud.Game.ClientState.Objects [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class ObjectTable + public sealed partial class ObjectTable : IServiceType { private const int ObjectTableLength = 424; diff --git a/Dalamud/Game/ClientState/Objects/TargetManager.cs b/Dalamud/Game/ClientState/Objects/TargetManager.cs index 881f77c54..54ed83546 100644 --- a/Dalamud/Game/ClientState/Objects/TargetManager.cs +++ b/Dalamud/Game/ClientState/Objects/TargetManager.cs @@ -12,7 +12,7 @@ namespace Dalamud.Game.ClientState.Objects [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed unsafe class TargetManager + public sealed unsafe class TargetManager : IServiceType { private readonly ClientStateAddressResolver address; diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index aee27160c..8f2f96eee 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.Party [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed unsafe partial class PartyList + public sealed unsafe partial class PartyList : IServiceType { private const int GroupLength = 8; private const int AllianceLength = 20; diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index 1b83cd3b1..0af2aede2 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -18,7 +18,7 @@ namespace Dalamud.Game.Command [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class CommandManager + public sealed class CommandManager : IServiceType { private readonly Dictionary commandMap = new(); private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled); diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index e9cdc18ac..70b4fd7e8 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -25,7 +25,7 @@ namespace Dalamud.Game [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class Framework : IDisposable + public sealed class Framework : IDisposable, IServiceType { private static Stopwatch statsStopwatch = new(); @@ -47,13 +47,6 @@ namespace Dalamud.Game this.updateHook = new Hook(this.Address.TickAddress, this.HandleFrameworkUpdate); this.freeHook = new Hook(this.Address.FreeAddress, this.HandleFrameworkFree); this.destroyHook = new Hook(this.Address.DestroyAddress, this.HandleFrameworkDestroy); - - gameGui.Enable(); - gameNetwork.Enable(); - - this.updateHook.Enable(); - this.freeHook.Enable(); - this.destroyHook.Enable(); } /// @@ -224,6 +217,14 @@ namespace Dalamud.Game statsStopwatch.Reset(); } + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() + { + this.updateHook.Enable(); + this.freeHook.Enable(); + this.destroyHook.Enable(); + } + private bool HandleFrameworkUpdate(IntPtr framework) { this.frameworkUpdateThread ??= Thread.CurrentThread; diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 1b798a6e5..96e7b9eaf 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -22,7 +22,7 @@ namespace Dalamud.Game.Gui [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class ChatGui : IDisposable + public sealed class ChatGui : IDisposable, IServiceType { private readonly ChatGuiAddressResolver address; @@ -123,16 +123,6 @@ namespace Dalamud.Game.Gui /// public byte LastLinkedItemFlags { get; private set; } - /// - /// Enables this module. - /// - public void Enable() - { - this.printMessageHook.Enable(); - this.populateItemLinkHook.Enable(); - this.interactableLinkClickedHook.Enable(); - } - /// /// Dispose of managed and unmanaged resources. /// @@ -280,6 +270,14 @@ namespace Dalamud.Game.Gui } } + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui, LibcFunction libcFunction) + { + this.printMessageHook.Enable(); + this.populateItemLinkHook.Enable(); + this.interactableLinkClickedHook.Enable(); + } + private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) { try diff --git a/Dalamud/Game/Gui/ContextMenus/ContextMenu.cs b/Dalamud/Game/Gui/ContextMenus/ContextMenu.cs index c8a8a8756..2e14e6402 100644 --- a/Dalamud/Game/Gui/ContextMenus/ContextMenu.cs +++ b/Dalamud/Game/Gui/ContextMenus/ContextMenu.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; - +using Dalamud.Configuration.Internal; using Dalamud.Game.Gui.ContextMenus.OldStructs; using Dalamud.Hooking; using Dalamud.IoC; @@ -25,7 +25,7 @@ namespace Dalamud.Game.Gui.ContextMenus [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class ContextMenu : IDisposable + public sealed class ContextMenu : IDisposable, IServiceType { private const int MaxContextMenuItemsPerContextMenu = 32; @@ -97,18 +97,6 @@ namespace Dalamud.Game.Gui.ContextMenus this.contextMenuOpeningHook.Disable(); } - /// - /// Enable this subsystem. - /// - internal void Enable() - { - this.contextMenuOpeningHook.Enable(); - this.contextMenuOpenedHook.Enable(); - this.contextMenuItemSelectedHook.Enable(); - this.subContextMenuOpeningHook.Enable(); - this.subContextMenuOpenedHook.Enable(); - } - private static unsafe bool IsInventoryContext(OldAgentContextInterface* agentContextInterface) { return agentContextInterface == AgentInventoryContext.Instance(); @@ -122,6 +110,19 @@ namespace Dalamud.Game.Gui.ContextMenus } } + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui) + { + if (!EnvironmentConfiguration.DalamudDoContextMenu) + return; + + this.contextMenuOpeningHook.Enable(); + this.contextMenuOpenedHook.Enable(); + this.contextMenuItemSelectedHook.Enable(); + this.subContextMenuOpeningHook.Enable(); + this.subContextMenuOpenedHook.Enable(); + } + private unsafe IntPtr ContextMenuOpeningDetour(IntPtr a1, IntPtr a2, IntPtr a3, uint a4, IntPtr a5, OldAgentContextInterface* agentContextInterface, IntPtr a7, ushort a8) { this.currentAgentContextInterface = agentContextInterface; diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index e4a75071c..6ff9dc83f 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -18,7 +18,7 @@ namespace Dalamud.Game.Gui.Dtr [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed unsafe class DtrBar : IDisposable + public sealed unsafe class DtrBar : IDisposable, IServiceType { private const uint BaseNodeId = 1000; diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index ba70e891e..736ad85db 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -17,7 +17,7 @@ namespace Dalamud.Game.Gui.FlyText [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class FlyTextGui : IDisposable + public sealed class FlyTextGui : IDisposable, IServiceType { /// /// The native function responsible for adding fly text to the UI. See . @@ -129,7 +129,10 @@ namespace Dalamud.Game.Gui.FlyText var strOffset = 28u; // Get the UI module and flytext addon pointers - var gameGui = Service.Get(); + var gameGui = Service.GetNullable(); + if (gameGui == null) + return; + var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule(); var flytext = gameGui.GetAddonByName("_FlyText", 1); @@ -174,10 +177,8 @@ namespace Dalamud.Game.Gui.FlyText } } - /// - /// Enables this module. - /// - internal void Enable() + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui) { this.createFlyTextHook.Enable(); } diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 53ab50255..b9f6f8e3b 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -28,7 +28,7 @@ namespace Dalamud.Game.Gui [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed unsafe class GameGui : IDisposable + public sealed unsafe class GameGui : IDisposable, IServiceType { private readonly GameGuiAddressResolver address; @@ -418,19 +418,9 @@ namespace Dalamud.Game.Gui /// The background music key. public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); - /// - /// Enables the hooks and submodules of this module. - /// - public void Enable() + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() { - Service.GetAsync().ContinueWith(x => x.Result.Enable()); - Service.GetAsync().ContinueWith(x => x.Result.Enable()); - Service.GetAsync().ContinueWith(x => x.Result.Enable()); - Service.GetAsync().ContinueWith(x => x.Result.Enable()); - - if (EnvironmentConfiguration.DalamudDoContextMenu) - Service.GetAsync().ContinueWith(x => x.Result.Enable()); - this.setGlobalBgmHook.Enable(); this.handleItemHoverHook.Enable(); this.handleItemOutHook.Enable(); diff --git a/Dalamud/Game/Gui/Internal/DalamudIME.cs b/Dalamud/Game/Gui/Internal/DalamudIME.cs index e0301b8b8..e16692d43 100644 --- a/Dalamud/Game/Gui/Internal/DalamudIME.cs +++ b/Dalamud/Game/Gui/Internal/DalamudIME.cs @@ -19,7 +19,7 @@ namespace Dalamud.Game.Gui.Internal /// This class handles IME for non-English users. /// [ServiceManager.EarlyLoadedService] - internal unsafe class DalamudIME : IDisposable + internal unsafe class DalamudIME : IDisposable, IServiceType { private static readonly ModuleLog Log = new("IME"); @@ -83,15 +83,13 @@ namespace Dalamud.Game.Gui.Internal return new Vector2(this.cursorPos->X, this.cursorPos->Y); } - /// - /// Enables the IME module. - /// - internal void Enable() + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene) { try { this.wndProcDelegate = this.WndProcDetour; - this.interfaceHandle = Service.Get().WindowHandlePtr; + this.interfaceHandle = interfaceManagerWithScene.Manager.WindowHandlePtr; this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate); this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr); diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs index 5b85588cc..406051a99 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs @@ -16,7 +16,7 @@ namespace Dalamud.Game.Gui.PartyFinder [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class PartyFinderGui : IDisposable + public sealed class PartyFinderGui : IDisposable, IServiceType { private readonly PartyFinderAddressResolver address; private readonly IntPtr memory; @@ -55,14 +55,6 @@ namespace Dalamud.Game.Gui.PartyFinder /// public event PartyFinderListingEventDelegate ReceiveListing; - /// - /// Enables this module. - /// - public void Enable() - { - this.receiveListingHook.Enable(); - } - /// /// Dispose of managed and unmanaged resources. /// @@ -80,6 +72,12 @@ namespace Dalamud.Game.Gui.PartyFinder } } + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui) + { + this.receiveListingHook.Enable(); + } + private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) { try diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs index 3cafffe32..8602be735 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game.Gui.Toast [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed partial class ToastGui : IDisposable + public sealed partial class ToastGui : IDisposable, IServiceType { private const uint QuestToastCheckmarkMagic = 60081; @@ -100,16 +100,6 @@ namespace Dalamud.Game.Gui.Toast #endregion - /// - /// Enables this module. - /// - public void Enable() - { - this.showNormalToastHook.Enable(); - this.showQuestToastHook.Enable(); - this.showErrorToastHook.Enable(); - } - /// /// Disposes of managed and unmanaged resources. /// @@ -153,6 +143,14 @@ namespace Dalamud.Game.Gui.Toast return terminated; } + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(GameGui gameGui) + { + this.showNormalToastHook.Enable(); + this.showQuestToastHook.Enable(); + this.showErrorToastHook.Enable(); + } + private SeString ParseString(IntPtr text) { var bytes = new List(); @@ -202,7 +200,9 @@ namespace Dalamud.Game.Gui.Toast { options ??= new ToastOptions(); - var manager = Service.Get().GetUIModule(); + var manager = Service.GetNullable()?.GetUIModule(); + if (manager == null) + return; // terminate the string var terminated = Terminate(bytes); @@ -211,7 +211,7 @@ namespace Dalamud.Game.Gui.Toast { fixed (byte* ptr = terminated) { - this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); + this.HandleNormalToastDetour(manager!.Value, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); } } } @@ -283,7 +283,9 @@ namespace Dalamud.Game.Gui.Toast { options ??= new QuestToastOptions(); - var manager = Service.Get().GetUIModule(); + var manager = Service.GetNullable()?.GetUIModule(); + if (manager == null) + return; // terminate the string var terminated = Terminate(bytes); @@ -295,7 +297,7 @@ namespace Dalamud.Game.Gui.Toast fixed (byte* ptr = terminated) { this.HandleQuestToastDetour( - manager, + manager!.Value, (int)options.Position, (IntPtr)ptr, ioc1, @@ -385,7 +387,9 @@ namespace Dalamud.Game.Gui.Toast private void ShowError(byte[] bytes) { - var manager = Service.Get().GetUIModule(); + var manager = Service.GetNullable()?.GetUIModule(); + if (manager == null) + return; // terminate the string var terminated = Terminate(bytes); @@ -394,7 +398,7 @@ namespace Dalamud.Game.Gui.Toast { fixed (byte* ptr = terminated) { - this.HandleErrorToastDetour(manager, (IntPtr)ptr, 0); + this.HandleErrorToastDetour(manager!.Value, (IntPtr)ptr, 0); } } } diff --git a/Dalamud/Game/Internal/AntiDebug.cs b/Dalamud/Game/Internal/AntiDebug.cs index 284732a26..99797f4df 100644 --- a/Dalamud/Game/Internal/AntiDebug.cs +++ b/Dalamud/Game/Internal/AntiDebug.cs @@ -9,7 +9,7 @@ namespace Dalamud.Game.Internal /// This class disables anti-debug functionality in the game client. /// [ServiceManager.EarlyLoadedService] - internal sealed partial class AntiDebug + internal sealed partial class AntiDebug : IServiceType { private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; private byte[] original; diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 33413ee96..575801b33 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -24,7 +24,7 @@ namespace Dalamud.Game.Internal /// This class implements in-game Dalamud options in the in-game System menu. /// [ServiceManager.EarlyLoadedService] - internal sealed unsafe partial class DalamudAtkTweaks + internal sealed unsafe partial class DalamudAtkTweaks : IServiceType { private readonly AtkValueChangeType atkValueChangeType; private readonly AtkValueSetString atkValueSetString; @@ -39,7 +39,7 @@ namespace Dalamud.Game.Internal private readonly string locDalamudSettings; [ServiceManager.ServiceConstructor] - private DalamudAtkTweaks(SigScanner sigScanner) + private DalamudAtkTweaks(SigScanner sigScanner, ContextMenu contextMenu) { var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); @@ -60,10 +60,7 @@ namespace Dalamud.Game.Internal this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins"); this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings"); - var contextMenu = Service.Get(); contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened; - - this.Enable(); } private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); @@ -76,10 +73,8 @@ namespace Dalamud.Game.Internal private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); - /// - /// Enables the . - /// - public void Enable() + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(DalamudInterface dalamudInterface) { this.hookAgentHudOpenSystemMenu.Enable(); this.hookUiModuleRequestMainCommand.Enable(); diff --git a/Dalamud/Game/Libc/LibcFunction.cs b/Dalamud/Game/Libc/LibcFunction.cs index b51277232..aa149a3ce 100644 --- a/Dalamud/Game/Libc/LibcFunction.cs +++ b/Dalamud/Game/Libc/LibcFunction.cs @@ -13,7 +13,7 @@ namespace Dalamud.Game.Libc [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class LibcFunction + public sealed class LibcFunction : IServiceType { private readonly LibcFunctionAddressResolver address; private readonly StdStringFromCStringDelegate stdStringCtorCString; diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index 3c22d3077..2f2903a8b 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game.Network [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public sealed class GameNetwork : IDisposable + public sealed class GameNetwork : IDisposable, IServiceType { private readonly GameNetworkAddressResolver address; private readonly Hook processZonePacketDownHook; @@ -59,15 +59,6 @@ namespace Dalamud.Game.Network /// public event OnNetworkMessageDelegate NetworkMessage; - /// - /// Enable this module. - /// - public void Enable() - { - this.processZonePacketDownHook.Enable(); - this.processZonePacketUpHook.Enable(); - } - /// /// Dispose of managed and unmanaged resources. /// @@ -98,6 +89,13 @@ namespace Dalamud.Game.Network } } + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction() + { + this.processZonePacketDownHook.Enable(); + this.processZonePacketUpHook.Enable(); + } + private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) { this.baseAddress = a; diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index e39c3b9af..e8380459f 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -22,7 +22,7 @@ namespace Dalamud.Game.Network.Internal /// This class handles network notifications and uploading market board data. /// [ServiceManager.EarlyLoadedService] - internal class NetworkHandlers + internal class NetworkHandlers : IServiceType { private readonly List marketBoardRequests = new(); diff --git a/Dalamud/Game/Network/Internal/WinSockHandlers.cs b/Dalamud/Game/Network/Internal/WinSockHandlers.cs index e2ab0c9bb..ed9d635a3 100644 --- a/Dalamud/Game/Network/Internal/WinSockHandlers.cs +++ b/Dalamud/Game/Network/Internal/WinSockHandlers.cs @@ -10,7 +10,7 @@ namespace Dalamud.Game.Network.Internal /// This class enables TCP optimizations in the game socket for better performance. /// [ServiceManager.EarlyLoadedService] - internal sealed class WinSockHandlers : IDisposable + internal sealed class WinSockHandlers : IDisposable, IServiceType { private Hook ws2SocketHook; diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 832066ee5..bab5c4e6d 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -19,7 +19,7 @@ namespace Dalamud.Game /// [PluginInterface] [InterfaceVersion("1.0")] - public class SigScanner : IDisposable + public class SigScanner : IDisposable, IServiceType { private readonly FileInfo? cacheFile; diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs index 85431e06c..02c187bad 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs @@ -14,7 +14,7 @@ namespace Dalamud.Game.Text.SeStringHandling [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] [Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] - public sealed class SeStringManager + public sealed class SeStringManager : IServiceType { [ServiceManager.ServiceConstructor] private SeStringManager() diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs index b9c0d270c..0a8ee331a 100644 --- a/Dalamud/Hooking/Internal/HookManager.cs +++ b/Dalamud/Hooking/Internal/HookManager.cs @@ -14,7 +14,7 @@ namespace Dalamud.Hooking.Internal /// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. /// [ServiceManager.EarlyLoadedService] - internal class HookManager : IDisposable + internal class HookManager : IDisposable, IServiceType { private static readonly ModuleLog Log = new("HM"); diff --git a/Dalamud/IServiceType.cs b/Dalamud/IServiceType.cs new file mode 100644 index 000000000..973795faf --- /dev/null +++ b/Dalamud/IServiceType.cs @@ -0,0 +1,8 @@ +namespace Dalamud; + +/// +/// Marker class for service types. +/// +public interface IServiceType +{ +} diff --git a/Dalamud/Interface/GameFonts/GameFontManager.cs b/Dalamud/Interface/GameFonts/GameFontManager.cs index ce4460257..28214d887 100644 --- a/Dalamud/Interface/GameFonts/GameFontManager.cs +++ b/Dalamud/Interface/GameFonts/GameFontManager.cs @@ -18,7 +18,7 @@ namespace Dalamud.Interface.GameFonts /// Loads game font for use in ImGui. /// [ServiceManager.EarlyLoadedService] - internal class GameFontManager : IDisposable + internal class GameFontManager : IDisposable, IServiceType { private static readonly string?[] FontNames = { diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index a9fc47995..11c8cb7f0 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -19,7 +19,7 @@ namespace Dalamud.Interface.Internal /// Class handling Dalamud core commands. /// [ServiceManager.EarlyLoadedService] - internal class DalamudCommands + internal class DalamudCommands : IServiceType { [ServiceManager.ServiceConstructor] private DalamudCommands(CommandManager commandManager) diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index d5a34d34e..e5d16289e 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -35,8 +35,8 @@ namespace Dalamud.Interface.Internal /// /// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. /// - [ServiceManager.AfterDrawingEarlyLoadedService] - internal class DalamudInterface : IDisposable + [ServiceManager.EarlyLoadedService] + internal class DalamudInterface : IDisposable, IServiceType { private static readonly ModuleLog Log = new("DUI"); @@ -77,8 +77,12 @@ namespace Dalamud.Interface.Internal private bool isImGuiDrawMetricsWindow = false; [ServiceManager.ServiceConstructor] - private DalamudInterface(Dalamud dalamud, DalamudConfiguration configuration, InterfaceManager interfaceManager) + private DalamudInterface( + Dalamud dalamud, + DalamudConfiguration configuration, + InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene) { + var interfaceManager = interfaceManagerWithScene.Manager; this.WindowSystem = new WindowSystem("DalamudCore"); this.changelogWindow = new ChangelogWindow() { IsOpen = false }; diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 1c4410efa..bda1efbb2 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -7,12 +7,10 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; -using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Game; using Dalamud.Game.ClientState.GamePad; using Dalamud.Game.ClientState.Keys; -using Dalamud.Game.Gui.Internal; using Dalamud.Game.Internal.DXGI; using Dalamud.Hooking; using Dalamud.Interface.GameFonts; @@ -45,7 +43,7 @@ namespace Dalamud.Interface.Internal /// This class manages interaction with the ImGui interface. /// [ServiceManager.BlockingEarlyLoadedService] - internal class InterfaceManager : IDisposable + internal class InterfaceManager : IDisposable, IServiceType { private const float MinimumFallbackFontSizePt = 9.6f; // Game's minimum AXIS font size private const float MinimumFallbackFontSizePx = MinimumFallbackFontSizePt * 4.0f / 3.0f; @@ -61,7 +59,6 @@ namespace Dalamud.Interface.Internal private readonly ManualResetEvent fontBuildSignal; private readonly SwapChainVtableResolver address; - private readonly TaskCompletionSource sceneInitializeTaskCompletionSource = new(); private RawDX11Scene? scene; private Hook? presentHook; @@ -71,7 +68,7 @@ namespace Dalamud.Interface.Internal // can't access imgui IO before first present call private bool lastWantCapture = false; private bool isRebuildingFonts = false; - + private bool isOverrideGameCursor = false; private bool isFallbackFontMode = false; [ServiceManager.ServiceConstructor] @@ -100,13 +97,6 @@ namespace Dalamud.Interface.Internal { Log.Error(e, "RTSS Free failed"); } - - Task.Run(async () => - { - var framework = await Service.GetAsync(); - var sigScanner = await Service.GetAsync(); - await framework.RunOnFrameworkThread(() => this.Enable(sigScanner)); - }); } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] @@ -160,11 +150,6 @@ namespace Dalamud.Interface.Internal /// public static ImFontPtr MonoFont { get; private set; } - /// - /// Gets a task that gets completed when scene gets initialized. - /// - public Task SceneInitializeTask => this.sceneInitializeTaskCompletionSource.Task; - /// /// Gets or sets the pointer to ImGui.IO(), when it was last used. /// @@ -178,15 +163,20 @@ namespace Dalamud.Interface.Internal /// /// Gets the address handle to the main process window. /// - public IntPtr WindowHandlePtr => this.scene.WindowHandlePtr; + public IntPtr WindowHandlePtr => this.scene?.WindowHandlePtr ?? IntPtr.Zero; /// /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. /// public bool OverrideGameCursor { - get => this.scene.UpdateCursor; - set => this.scene.UpdateCursor = value; + get => this.scene?.UpdateCursor ?? this.isOverrideGameCursor; + set + { + this.isOverrideGameCursor = value; + if (this.scene != null) + this.scene.UpdateCursor = value; + } } /// @@ -353,6 +343,12 @@ namespace Dalamud.Interface.Internal /// public void RebuildFonts() { + if (this.scene == null) + { + Log.Verbose("[FONT] RebuildFonts(): scene not ready, doing nothing"); + return; + } + Log.Verbose("[FONT] RebuildFonts() called"); // don't invoke this multiple times per frame, in case multiple plugins call it @@ -448,7 +444,7 @@ namespace Dalamud.Interface.Internal private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) { if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer) - return this.presentHook.Original(swapChain, syncInterval, presentFlags); + return this.presentHook!.Original(swapChain, syncInterval, presentFlags); if (this.scene == null) { @@ -460,7 +456,7 @@ namespace Dalamud.Interface.Internal } catch (DllNotFoundException ex) { - this.sceneInitializeTaskCompletionSource.SetException(ex); + Service.ProvideException(ex); Log.Error(ex, "Could not load ImGui dependencies."); var res = PInvoke.User32.MessageBox( @@ -501,6 +497,7 @@ namespace Dalamud.Interface.Internal Log.Error(ex, "Could not delete dalamudUI.ini"); } + this.scene.UpdateCursor = this.isOverrideGameCursor; this.scene.ImGuiIniPath = iniFileInfo.FullName; this.scene.OnBuildUI += this.Display; this.scene.OnNewInputFrame += this.OnNewInputFrame; @@ -566,7 +563,7 @@ namespace Dalamud.Interface.Internal Log.Information("[IM] Scene & ImGui setup OK!"); - Service.Get().Enable(); + Service.Get().RunOnFrameworkThread(() => Service.Provide(new(this))); } } @@ -576,17 +573,11 @@ namespace Dalamud.Interface.Internal this.RenderImGui(); - if (!this.SceneInitializeTask.IsCompleted) - this.sceneInitializeTaskCompletionSource.SetResult(); - return pRes; } this.RenderImGui(); - if (!this.SceneInitializeTask.IsCompleted) - this.sceneInitializeTaskCompletionSource.SetResult(); - return this.presentHook.Original(swapChain, syncInterval, presentFlags); } @@ -987,21 +978,20 @@ namespace Dalamud.Interface.Internal } } - private void Enable(SigScanner sigScanner) + [ServiceManager.CallWhenServicesReady] + private void ContinueConstruction(SigScanner sigScanner) { this.address.Setup(sigScanner); - this.setCursorHook = Hook.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); + this.setCursorHook = Hook.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true)!; this.presentHook = new Hook(this.address.Present, this.PresentDetour); this.resizeBuffersHook = new Hook(this.address.ResizeBuffers, this.ResizeBuffersDetour); - var setCursorAddress = this.setCursorHook?.Address ?? IntPtr.Zero; - Log.Verbose("===== S W A P C H A I N ====="); - Log.Verbose($"SetCursor address 0x{setCursorAddress.ToInt64():X}"); - Log.Verbose($"Present address 0x{this.presentHook.Address.ToInt64():X}"); - Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook.Address.ToInt64():X}"); + Log.Verbose($"SetCursor address 0x{this.setCursorHook!.Address.ToInt64():X}"); + Log.Verbose($"Present address 0x{this.presentHook!.Address.ToInt64():X}"); + Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook!.Address.ToInt64():X}"); - this.setCursorHook?.Enable(); + this.setCursorHook.Enable(); this.presentHook.Enable(); this.resizeBuffersHook.Enable(); @@ -1027,8 +1017,8 @@ namespace Dalamud.Interface.Internal private void Disable() { this.setCursorHook?.Disable(); - this.presentHook.Disable(); - this.resizeBuffersHook.Disable(); + this.presentHook?.Disable(); + this.resizeBuffersHook?.Disable(); } // This is intended to only be called as a handler attached to scene.OnNewRenderFrame @@ -1038,7 +1028,7 @@ namespace Dalamud.Interface.Internal this.SetupFonts(); Log.Verbose("[FONT] RebuildFontsInternal() detaching"); - this.scene.OnNewRenderFrame -= this.RebuildFontsInternal; + this.scene!.OnNewRenderFrame -= this.RebuildFontsInternal; Log.Verbose("[FONT] Calling InvalidateFonts"); try @@ -1086,11 +1076,11 @@ namespace Dalamud.Interface.Internal // We have to ensure we're working with the main swapchain, // as viewports might be resizing as well if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) - return this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); + return this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); this.scene?.OnPreResize(); - var ret = this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); + var ret = this.resizeBuffersHook!.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); if (ret.ToInt64() == 0x887A0001) { Log.Error("invalid call to resizeBuffers"); @@ -1106,7 +1096,7 @@ namespace Dalamud.Interface.Internal if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) return IntPtr.Zero; - return this.setCursorHook.Original(hCursor); + return this.setCursorHook!.Original(hCursor); } private void OnNewInputFrame() @@ -1194,6 +1184,26 @@ namespace Dalamud.Interface.Internal Service.Get().Draw(); } + /// + /// Represents an instance of InstanceManager with scene ready for use. + /// + public class InterfaceManagerWithScene : IServiceType + { + /// + /// Initializes a new instance of the class. + /// + /// An instance of . + internal InterfaceManagerWithScene(InterfaceManager interfaceManager) + { + this.Manager = interfaceManager; + } + + /// + /// Associated InterfaceManager. + /// + public InterfaceManager Manager { get; init; } + } + /// /// Represents a glyph request. /// diff --git a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs index c68017caf..f1318a7fe 100644 --- a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs +++ b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs @@ -14,7 +14,7 @@ namespace Dalamud.Interface.Internal.Notifications /// Ported from https://github.com/patrickcjk/imgui-notify. /// [ServiceManager.EarlyLoadedService] - internal class NotificationManager + internal class NotificationManager : IServiceType { /// /// Value indicating the bottom-left X padding. diff --git a/Dalamud/Interface/TitleScreenMenu.cs b/Dalamud/Interface/TitleScreenMenu.cs index 8e00cea3b..f44b112d7 100644 --- a/Dalamud/Interface/TitleScreenMenu.cs +++ b/Dalamud/Interface/TitleScreenMenu.cs @@ -13,7 +13,7 @@ namespace Dalamud.Interface [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] - public class TitleScreenMenu + public class TitleScreenMenu : IServiceType { /// /// Gets the texture size needed for title screen menu logos. diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 753e81b4e..0119d1a78 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -109,12 +109,12 @@ namespace Dalamud.Interface /// /// Gets the game's active Direct3D device. /// - public Device Device => Service.Get().Device; + public Device Device => Service.Get().Manager.Device!; /// /// Gets the game's main window handle. /// - public IntPtr WindowHandlePtr => Service.Get().WindowHandlePtr; + public IntPtr WindowHandlePtr => Service.Get().Manager.WindowHandlePtr; /// /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden. @@ -215,7 +215,7 @@ namespace Dalamud.Interface /// The full filepath to the image. /// A object wrapping the created image. Use inside ImGui.Image(). public TextureWrap LoadImage(string filePath) - => Service.Get().LoadImage(filePath); + => Service.Get().Manager.LoadImage(filePath); /// /// Loads an image from a byte stream, such as a png downloaded into memory. @@ -223,7 +223,7 @@ namespace Dalamud.Interface /// A byte array containing the raw image data. /// A object wrapping the created image. Use inside ImGui.Image(). public TextureWrap LoadImage(byte[] imageData) - => Service.Get().LoadImage(imageData); + => Service.Get().Manager.LoadImage(imageData); /// /// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use . @@ -234,7 +234,7 @@ namespace Dalamud.Interface /// The number of channels (bytes per pixel) of the image contained in . This should usually be 4. /// A object wrapping the created image. Use inside ImGui.Image(). public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) - => Service.Get().LoadImageRaw(imageData, width, height, numChannels); + => Service.Get().Manager.LoadImageRaw(imageData, width, height, numChannels); /// /// Gets a game font. diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index 5369f3c67..9edd1479c 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -12,7 +12,7 @@ namespace Dalamud.IoC.Internal /// /// A simple singleton-only IOC container that provides (optional) version-based dependency resolution. /// - internal class ServiceContainer : IServiceProvider + internal class ServiceContainer : IServiceProvider, IServiceType { private static readonly ModuleLog Log = new("SERVICECONTAINER"); diff --git a/Dalamud/Localization.cs b/Dalamud/Localization.cs index 0db7088dc..a050b71a0 100644 --- a/Dalamud/Localization.cs +++ b/Dalamud/Localization.cs @@ -14,7 +14,7 @@ namespace Dalamud /// Class handling localization. /// [ServiceManager.EarlyLoadedService] - public class Localization + public class Localization : IServiceType { /// /// Array of language codes which have a valid translation in Dalamud. diff --git a/Dalamud/Logging/Internal/TaskTracker.cs b/Dalamud/Logging/Internal/TaskTracker.cs index 354decc55..ca7576398 100644 --- a/Dalamud/Logging/Internal/TaskTracker.cs +++ b/Dalamud/Logging/Internal/TaskTracker.cs @@ -13,7 +13,7 @@ namespace Dalamud.Logging.Internal /// Class responsible for tracking asynchronous tasks. /// [ServiceManager.EarlyLoadedService] - internal class TaskTracker : IDisposable + internal class TaskTracker : IDisposable, IServiceType { private static readonly ModuleLog Log = new("TT"); private static readonly List TrackedTasksInternal = new(); diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index d88e82236..9f803f79a 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -31,7 +31,7 @@ namespace Dalamud.Plugin.Internal; /// Class responsible for loading and unloading plugins. /// [ServiceManager.EarlyLoadedService] -internal partial class PluginManager : IDisposable +internal partial class PluginManager : IDisposable, IServiceType { /// /// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded. @@ -386,12 +386,8 @@ internal partial class PluginManager : IDisposable // Load plugins that want to be loaded during Framework.Tick, when drawing facilities are available loadTasks.Add( - Service + Service .GetAsync() - .ContinueWith( - x => x.Result.SceneInitializeTask, - TaskContinuationOptions.RunContinuationsAsynchronously) - .Unwrap() .ContinueWith( _ => Service.Get().RunOnTick( () => LoadPluginsSync( diff --git a/Dalamud/Plugin/Internal/StartupPluginLoader.cs b/Dalamud/Plugin/Internal/StartupPluginLoader.cs index 8a9063747..a95e25ace 100644 --- a/Dalamud/Plugin/Internal/StartupPluginLoader.cs +++ b/Dalamud/Plugin/Internal/StartupPluginLoader.cs @@ -10,7 +10,7 @@ namespace Dalamud.Plugin.Internal; /// Class responsible for loading plugins on startup. /// [ServiceManager.BlockingEarlyLoadedService] -public class StartupPluginLoader +public class StartupPluginLoader : IServiceType { private static readonly ModuleLog Log = new("SPL"); diff --git a/Dalamud/Plugin/Ipc/Internal/CallGate.cs b/Dalamud/Plugin/Ipc/Internal/CallGate.cs index 5d283672c..2a857b2d0 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGate.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGate.cs @@ -6,7 +6,7 @@ namespace Dalamud.Plugin.Ipc.Internal /// This class facilitates inter-plugin communication. /// [ServiceManager.EarlyLoadedService] - internal class CallGate + internal class CallGate : IServiceType { private readonly Dictionary gates = new(); diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs index 5dc6d08f1..80dd01a90 100644 --- a/Dalamud/ServiceManager.cs +++ b/Dalamud/ServiceManager.cs @@ -6,7 +6,6 @@ using System.Reflection; using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Game; -using Dalamud.Interface.Internal; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; using Dalamud.Utility.Timing; @@ -67,7 +66,6 @@ namespace Dalamud var earlyLoadingServices = new HashSet(); var blockingEarlyLoadingServices = new HashSet(); - var afterDrawingEarlyLoadedServices = new HashSet(); var dependencyServicesMap = new Dictionary>(); var getAsyncTaskMap = new Dictionary(); @@ -90,10 +88,6 @@ namespace Dalamud getAsyncTaskMap[serviceType] = getTask; blockingEarlyLoadingServices.Add(serviceType); } - else if (attr.IsAssignableTo(typeof(AfterDrawingEarlyLoadedService))) - { - afterDrawingEarlyLoadedServices.Add(serviceType); - } else { earlyLoadingServices.Add(serviceType); @@ -126,55 +120,44 @@ namespace Dalamud try { - for (var i = 0; i < 2; i++) + var tasks = new List(); + var servicesToLoad = new HashSet(); + servicesToLoad.UnionWith(earlyLoadingServices); + servicesToLoad.UnionWith(blockingEarlyLoadingServices); + + while (servicesToLoad.Any()) { - var tasks = new List(); - var servicesToLoad = new HashSet(); - if (i == 0) + foreach (var serviceType in servicesToLoad) { - servicesToLoad.UnionWith(earlyLoadingServices); - servicesToLoad.UnionWith(blockingEarlyLoadingServices); + if (dependencyServicesMap[serviceType].Any( + x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false)) + continue; + + tasks.Add((Task)service.MakeGenericType(serviceType).InvokeMember( + "StartLoader", + BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, + null, + null, + null)); + servicesToLoad.Remove(serviceType); + } + + if (!tasks.Any()) + throw new InvalidOperationException("Unresolvable dependency cycle detected"); + + if (servicesToLoad.Any()) + { + await Task.WhenAny(tasks); + var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray(); + if (faultedTasks.Any()) + throw new AggregateException(faultedTasks); } else { - servicesToLoad.UnionWith(afterDrawingEarlyLoadedServices); - await (await Service.GetAsync()).SceneInitializeTask; + await Task.WhenAll(tasks); } - while (servicesToLoad.Any()) - { - foreach (var serviceType in servicesToLoad) - { - if (dependencyServicesMap[serviceType].Any( - x => getAsyncTaskMap.GetValueOrDefault(x)?.IsCompleted == false)) - continue; - - tasks.Add((Task)service.MakeGenericType(serviceType).InvokeMember( - "StartLoader", - BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, - null, - null, - null)); - servicesToLoad.Remove(serviceType); - } - - if (!tasks.Any()) - throw new InvalidOperationException("Unresolvable dependency cycle detected"); - - if (servicesToLoad.Any()) - { - await Task.WhenAny(tasks); - var faultedTasks = tasks.Where(x => x.IsFaulted).Select(x => (Exception)x.Exception!).ToArray(); - if (faultedTasks.Any()) - throw new AggregateException(faultedTasks); - } - else - { - await Task.WhenAll(tasks); - } - - tasks.RemoveAll(x => x.IsCompleted); - } + tasks.RemoveAll(x => x.IsCompleted); } } catch (Exception e) @@ -236,11 +219,11 @@ namespace Dalamud } /// - /// Indicates that the class is a service, and will be instantiated automatically on startup, - /// when drawing becomes available. + /// Indicates that the method should be called when the services given in the constructor are ready. /// - [AttributeUsage(AttributeTargets.Class)] - public class AfterDrawingEarlyLoadedService : EarlyLoadedService + [AttributeUsage(AttributeTargets.Method)] + [MeansImplicitUse] + public class CallWhenServicesReady : Attribute { } } diff --git a/Dalamud/Service{T}.cs b/Dalamud/Service{T}.cs index e8fd0f32f..f813ef8dd 100644 --- a/Dalamud/Service{T}.cs +++ b/Dalamud/Service{T}.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Threading; using System.Threading.Tasks; using Dalamud.IoC; using Dalamud.IoC.Internal; @@ -18,7 +17,7 @@ namespace Dalamud /// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI. /// /// The class you want to store in the service locator. - internal static class Service + internal static class Service where T : IServiceType { // ReSharper disable once StaticMemberInGenericType private static readonly TaskCompletionSource InstanceTcs = new(); @@ -51,10 +50,23 @@ namespace Dalamud ServiceManager.Log.Debug("Service<{0}>: Begin construction", typeof(T).Name); try { - var x = await ConstructObject(); + var instance = await ConstructObject(); + InstanceTcs.SetResult(instance); + + foreach (var method in typeof(T).GetMethods( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + if (method.GetCustomAttribute(true) == null) + continue; + + ServiceManager.Log.Debug("Service<{0}>: Calling {1}", typeof(T).Name, method.Name); + var args = await Task.WhenAll(method.GetParameters().Select( + x => ResolveServiceFromTypeAsync(x.ParameterType))); + method.Invoke(instance, args); + } + ServiceManager.Log.Debug("Service<{0}>: Construction complete", typeof(T).Name); - InstanceTcs.SetResult(x); - return x; + return instance; } catch (Exception e) { @@ -71,10 +83,20 @@ namespace Dalamud /// Object to set. public static void Provide(T obj) { - InstanceTcs!.SetResult(obj); + InstanceTcs.SetResult(obj); ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name); } + /// + /// Sets the service load state to failure. + /// + /// The exception. + public static void ProvideException(Exception exception) + { + ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name); + InstanceTcs.SetException(exception); + } + /// /// Pull the instance out of the service locator, waiting if necessary. /// @@ -103,6 +125,7 @@ namespace Dalamud /// Gets an enumerable containing Service<T>s that are required for this Service to initialize without blocking. /// /// List of dependency services. + [UsedImplicitly] public static List GetDependencyServices() { var res = new List(); @@ -119,7 +142,7 @@ namespace Dalamud .ToList(); } - private static async Task GetServiceObjectConstructArgument(Type type) + private static async Task ResolveServiceFromTypeAsync(Type type) { var task = (Task)typeof(Service<>) .MakeGenericType(type) @@ -151,7 +174,7 @@ namespace Dalamud { var ctor = GetServiceConstructor(); var args = await Task.WhenAll( - ctor.GetParameters().Select(x => GetServiceObjectConstructArgument(x.ParameterType))); + ctor.GetParameters().Select(x => ResolveServiceFromTypeAsync(x.ParameterType))); using (Timings.Start($"{typeof(T).Name} Construct")) { return (T)ctor.Invoke(args)!;