Merge branch 'net5'

This commit is contained in:
goaaats 2022-06-26 18:50:56 +02:00
commit d29400437e
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
52 changed files with 274 additions and 273 deletions

View file

@ -84,6 +84,7 @@
<!-- This prevents us from having to include Dalamud itself as a dependency -->
<!-- If the files move just update the paths here -->
<Compile Include="..\Dalamud\ClientLanguage.cs" Link="Included\%(Filename)%(Extension)" />
<Compile Include="..\Dalamud\IServiceType.cs" Link="Included\%(Filename)%(Extension)" />
<Compile Include="..\Dalamud\DalamudStartInfo.cs" Link="Included\%(Filename)%(Extension)" />
<Compile Include="..\Dalamud\Game\GameVersion.cs" Link="Included\Game\%(Filename)%(Extension)" />
<Compile Include="..\Dalamud\Game\GameVersionConverter.cs" Link="Included\Game\%(Filename)%(Extension)" />

View file

@ -16,7 +16,7 @@ namespace Dalamud.Configuration.Internal
/// Class containing Dalamud settings.
/// </summary>
[Serializable]
internal sealed class DalamudConfiguration
internal sealed class DalamudConfiguration : IServiceType
{
/// <summary>
/// Currently used beta key for Dalamud staging builds.

View file

@ -33,7 +33,7 @@ namespace Dalamud
/// <summary>
/// The main Dalamud class containing all subsystems.
/// </summary>
internal sealed class Dalamud : IDisposable
internal sealed class Dalamud : IDisposable, IServiceType
{
#region Internals

View file

@ -10,7 +10,7 @@ namespace Dalamud
/// Struct containing information needed to initialize Dalamud.
/// </summary>
[Serializable]
public record DalamudStartInfo
public record DalamudStartInfo : IServiceType
{
/// <summary>
/// Initializes a new instance of the <see cref="DalamudStartInfo"/> class.

View file

@ -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";

View file

@ -29,7 +29,7 @@ namespace Dalamud.Game
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public class ChatHandlers
public class ChatHandlers : IServiceType
{
// private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new()
// {

View file

@ -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<ClientState>.Get();

View file

@ -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;

View file

@ -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<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
@ -128,16 +128,6 @@ namespace Dalamud.Game.ClientState
/// </summary>
public bool IsPvPExcludingDen { get; private set; }
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
Service<Conditions.Condition>.Get().Enable();
Service<GamepadState>.Get().Enable();
this.setupTerritoryTypeHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
@ -150,6 +140,12 @@ namespace Dalamud.Game.ClientState
Service<NetworkHandlers>.Get().CfPop -= this.NetworkHandlersOnCfPop;
}
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
this.setupTerritoryTypeHook.Enable();
}
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
{
this.TerritoryType = terriType;

View file

@ -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
{
/// <summary>
/// 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;
}
/// <summary>
/// Enables the hooks of the Condition class function.
/// </summary>
public void Enable()
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(Framework framework)
{
// Initialization
for (var i = 0; i < MaxConditionEntries; i++)
this.cache[i] = this[i];
Service<Framework>.Get().Update += this.FrameworkUpdate;
framework.Update += this.FrameworkUpdate;
}
private void FrameworkUpdate(Framework framework)

View file

@ -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;

View file

@ -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<ControllerPoll> gamepadPoll;
@ -168,10 +168,8 @@ namespace Dalamud.Game.ClientState.GamePad
GC.SuppressFinalize(this);
}
/// <summary>
/// Enables the hook of the GamepadPoll function.
/// </summary>
internal void Enable()
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
this.gamepadPoll.Enable();
}

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.ClientState.JobGauge
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public class JobGauges
public class JobGauges : IServiceType
{
private Dictionary<Type, JobGaugeBase> cache = new();

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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<string, CommandInfo> commandMap = new();
private readonly Regex commandRegexEn = new(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);

View file

@ -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<OnUpdateDetour>(this.Address.TickAddress, this.HandleFrameworkUpdate);
this.freeHook = new Hook<OnDestroyDetour>(this.Address.FreeAddress, this.HandleFrameworkFree);
this.destroyHook = new Hook<OnRealDestroyDelegate>(this.Address.DestroyAddress, this.HandleFrameworkDestroy);
gameGui.Enable();
gameNetwork.Enable();
this.updateHook.Enable();
this.freeHook.Enable();
this.destroyHook.Enable();
}
/// <summary>
@ -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;

View file

@ -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
/// </summary>
public byte LastLinkedItemFlags { get; private set; }
/// <summary>
/// Enables this module.
/// </summary>
public void Enable()
{
this.printMessageHook.Enable();
this.populateItemLinkHook.Enable();
this.interactableLinkClickedHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
@ -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

View file

@ -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();
}
/// <summary>
/// Enable this subsystem.
/// </summary>
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;

View file

@ -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;

View file

@ -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
{
/// <summary>
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
@ -129,7 +129,10 @@ namespace Dalamud.Game.Gui.FlyText
var strOffset = 28u;
// Get the UI module and flytext addon pointers
var gameGui = Service<GameGui>.Get();
var gameGui = Service<GameGui>.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
}
}
/// <summary>
/// Enables this module.
/// </summary>
internal void Enable()
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(GameGui gameGui)
{
this.createFlyTextHook.Enable();
}

View file

@ -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
/// <param name="bgmKey">The background music key.</param>
public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
/// <summary>
/// Enables the hooks and submodules of this module.
/// </summary>
public void Enable()
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction()
{
Service<ChatGui>.GetAsync().ContinueWith(x => x.Result.Enable());
Service<ToastGui>.GetAsync().ContinueWith(x => x.Result.Enable());
Service<FlyTextGui>.GetAsync().ContinueWith(x => x.Result.Enable());
Service<PartyFinderGui>.GetAsync().ContinueWith(x => x.Result.Enable());
if (EnvironmentConfiguration.DalamudDoContextMenu)
Service<ContextMenu>.GetAsync().ContinueWith(x => x.Result.Enable());
this.setGlobalBgmHook.Enable();
this.handleItemHoverHook.Enable();
this.handleItemOutHook.Enable();

View file

@ -19,7 +19,7 @@ namespace Dalamud.Game.Gui.Internal
/// This class handles IME for non-English users.
/// </summary>
[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);
}
/// <summary>
/// Enables the IME module.
/// </summary>
internal void Enable()
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
{
try
{
this.wndProcDelegate = this.WndProcDetour;
this.interfaceHandle = Service<InterfaceManager>.Get().WindowHandlePtr;
this.interfaceHandle = interfaceManagerWithScene.Manager.WindowHandlePtr;
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);

View file

@ -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
/// </summary>
public event PartyFinderListingEventDelegate ReceiveListing;
/// <summary>
/// Enables this module.
/// </summary>
public void Enable()
{
this.receiveListingHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
@ -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

View file

@ -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
/// <summary>
/// Enables this module.
/// </summary>
public void Enable()
{
this.showNormalToastHook.Enable();
this.showQuestToastHook.Enable();
this.showErrorToastHook.Enable();
}
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
@ -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<byte>();
@ -202,7 +200,9 @@ namespace Dalamud.Game.Gui.Toast
{
options ??= new ToastOptions();
var manager = Service<GameGui>.Get().GetUIModule();
var manager = Service<GameGui>.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<GameGui>.Get().GetUIModule();
var manager = Service<GameGui>.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<GameGui>.Get().GetUIModule();
var manager = Service<GameGui>.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);
}
}
}

View file

@ -9,7 +9,7 @@ namespace Dalamud.Game.Internal
/// This class disables anti-debug functionality in the game client.
/// </summary>
[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;

View file

@ -24,7 +24,7 @@ namespace Dalamud.Game.Internal
/// This class implements in-game Dalamud options in the in-game System menu.
/// </summary>
[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<ContextMenu>.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);
/// <summary>
/// Enables the <see cref="DalamudAtkTweaks"/>.
/// </summary>
public void Enable()
[ServiceManager.CallWhenServicesReady]
private void ContinueConstruction(DalamudInterface dalamudInterface)
{
this.hookAgentHudOpenSystemMenu.Enable();
this.hookUiModuleRequestMainCommand.Enable();

View file

@ -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;

View file

@ -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<ProcessZonePacketDownDelegate> processZonePacketDownHook;
@ -59,15 +59,6 @@ namespace Dalamud.Game.Network
/// </summary>
public event OnNetworkMessageDelegate NetworkMessage;
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
@ -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;

View file

@ -22,7 +22,7 @@ namespace Dalamud.Game.Network.Internal
/// This class handles network notifications and uploading market board data.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class NetworkHandlers
internal class NetworkHandlers : IServiceType
{
private readonly List<MarketBoardItemRequest> marketBoardRequests = new();

View file

@ -10,7 +10,7 @@ namespace Dalamud.Game.Network.Internal
/// This class enables TCP optimizations in the game socket for better performance.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal sealed class WinSockHandlers : IDisposable
internal sealed class WinSockHandlers : IDisposable, IServiceType
{
private Hook<SocketDelegate> ws2SocketHook;

View file

@ -19,7 +19,7 @@ namespace Dalamud.Game
/// </summary>
[PluginInterface]
[InterfaceVersion("1.0")]
public class SigScanner : IDisposable
public class SigScanner : IDisposable, IServiceType
{
private readonly FileInfo? cacheFile;

View file

@ -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()

View file

@ -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.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class HookManager : IDisposable
internal class HookManager : IDisposable, IServiceType
{
private static readonly ModuleLog Log = new("HM");

8
Dalamud/IServiceType.cs Normal file
View file

@ -0,0 +1,8 @@
namespace Dalamud;
/// <summary>
/// Marker class for service types.
/// </summary>
public interface IServiceType
{
}

View file

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

View file

@ -19,7 +19,7 @@ namespace Dalamud.Interface.Internal
/// Class handling Dalamud core commands.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class DalamudCommands
internal class DalamudCommands : IServiceType
{
[ServiceManager.ServiceConstructor]
private DalamudCommands(CommandManager commandManager)

View file

@ -35,8 +35,8 @@ namespace Dalamud.Interface.Internal
/// <summary>
/// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping.
/// </summary>
[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 };

View file

@ -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.
/// </summary>
[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<PresentDelegate>? 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<Framework>.GetAsync();
var sigScanner = await Service<SigScanner>.GetAsync();
await framework.RunOnFrameworkThread(() => this.Enable(sigScanner));
});
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@ -160,11 +150,6 @@ namespace Dalamud.Interface.Internal
/// </summary>
public static ImFontPtr MonoFont { get; private set; }
/// <summary>
/// Gets a task that gets completed when scene gets initialized.
/// </summary>
public Task SceneInitializeTask => this.sceneInitializeTaskCompletionSource.Task;
/// <summary>
/// Gets or sets the pointer to ImGui.IO(), when it was last used.
/// </summary>
@ -178,15 +163,20 @@ namespace Dalamud.Interface.Internal
/// <summary>
/// Gets the address handle to the main process window.
/// </summary>
public IntPtr WindowHandlePtr => this.scene.WindowHandlePtr;
public IntPtr WindowHandlePtr => this.scene?.WindowHandlePtr ?? IntPtr.Zero;
/// <summary>
/// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor.
/// </summary>
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;
}
}
/// <summary>
@ -353,6 +343,12 @@ namespace Dalamud.Interface.Internal
/// </summary>
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<InterfaceManagerWithScene>.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<DalamudIME>.Get().Enable();
Service<Framework>.Get().RunOnFrameworkThread(() => Service<InterfaceManagerWithScene>.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<SetCursorDelegate>.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true);
this.setCursorHook = Hook<SetCursorDelegate>.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true)!;
this.presentHook = new Hook<PresentDelegate>(this.address.Present, this.PresentDetour);
this.resizeBuffersHook = new Hook<ResizeBuffersDelegate>(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<NotificationManager>.Get().Draw();
}
/// <summary>
/// Represents an instance of InstanceManager with scene ready for use.
/// </summary>
public class InterfaceManagerWithScene : IServiceType
{
/// <summary>
/// Initializes a new instance of the <see cref="InterfaceManagerWithScene"/> class.
/// </summary>
/// <param name="interfaceManager">An instance of <see cref="InterfaceManager"/>.</param>
internal InterfaceManagerWithScene(InterfaceManager interfaceManager)
{
this.Manager = interfaceManager;
}
/// <summary>
/// Associated InterfaceManager.
/// </summary>
public InterfaceManager Manager { get; init; }
}
/// <summary>
/// Represents a glyph request.
/// </summary>

View file

@ -14,7 +14,7 @@ namespace Dalamud.Interface.Internal.Notifications
/// Ported from https://github.com/patrickcjk/imgui-notify.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class NotificationManager
internal class NotificationManager : IServiceType
{
/// <summary>
/// Value indicating the bottom-left X padding.

View file

@ -13,7 +13,7 @@ namespace Dalamud.Interface
[PluginInterface]
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
public class TitleScreenMenu
public class TitleScreenMenu : IServiceType
{
/// <summary>
/// Gets the texture size needed for title screen menu logos.

View file

@ -109,12 +109,12 @@ namespace Dalamud.Interface
/// <summary>
/// Gets the game's active Direct3D device.
/// </summary>
public Device Device => Service<InterfaceManager>.Get().Device;
public Device Device => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.Device!;
/// <summary>
/// Gets the game's main window handle.
/// </summary>
public IntPtr WindowHandlePtr => Service<InterfaceManager>.Get().WindowHandlePtr;
public IntPtr WindowHandlePtr => Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.WindowHandlePtr;
/// <summary>
/// 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
/// <param name="filePath">The full filepath to the image.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
public TextureWrap LoadImage(string filePath)
=> Service<InterfaceManager>.Get().LoadImage(filePath);
=> Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImage(filePath);
/// <summary>
/// Loads an image from a byte stream, such as a png downloaded into memory.
@ -223,7 +223,7 @@ namespace Dalamud.Interface
/// <param name="imageData">A byte array containing the raw image data.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
public TextureWrap LoadImage(byte[] imageData)
=> Service<InterfaceManager>.Get().LoadImage(imageData);
=> Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImage(imageData);
/// <summary>
/// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use <see cref="LoadImage(byte[])"/>.
@ -234,7 +234,7 @@ namespace Dalamud.Interface
/// <param name="numChannels">The number of channels (bytes per pixel) of the image contained in <paramref name="imageData"/>. This should usually be 4.</param>
/// <returns>A <see cref="TextureWrap"/> object wrapping the created image. Use <see cref="TextureWrap.ImGuiHandle"/> inside ImGui.Image().</returns>
public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels)
=> Service<InterfaceManager>.Get().LoadImageRaw(imageData, width, height, numChannels);
=> Service<InterfaceManager.InterfaceManagerWithScene>.Get().Manager.LoadImageRaw(imageData, width, height, numChannels);
/// <summary>
/// Gets a game font.

View file

@ -12,7 +12,7 @@ namespace Dalamud.IoC.Internal
/// <summary>
/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution.
/// </summary>
internal class ServiceContainer : IServiceProvider
internal class ServiceContainer : IServiceProvider, IServiceType
{
private static readonly ModuleLog Log = new("SERVICECONTAINER");

View file

@ -14,7 +14,7 @@ namespace Dalamud
/// Class handling localization.
/// </summary>
[ServiceManager.EarlyLoadedService]
public class Localization
public class Localization : IServiceType
{
/// <summary>
/// Array of language codes which have a valid translation in Dalamud.

View file

@ -13,7 +13,7 @@ namespace Dalamud.Logging.Internal
/// Class responsible for tracking asynchronous tasks.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class TaskTracker : IDisposable
internal class TaskTracker : IDisposable, IServiceType
{
private static readonly ModuleLog Log = new("TT");
private static readonly List<TaskInfo> TrackedTasksInternal = new();

View file

@ -31,7 +31,7 @@ namespace Dalamud.Plugin.Internal;
/// Class responsible for loading and unloading plugins.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal partial class PluginManager : IDisposable
internal partial class PluginManager : IDisposable, IServiceType
{
/// <summary>
/// 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<InterfaceManager>
Service<InterfaceManager.InterfaceManagerWithScene>
.GetAsync()
.ContinueWith(
x => x.Result.SceneInitializeTask,
TaskContinuationOptions.RunContinuationsAsynchronously)
.Unwrap()
.ContinueWith(
_ => Service<Framework>.Get().RunOnTick(
() => LoadPluginsSync(

View file

@ -10,7 +10,7 @@ namespace Dalamud.Plugin.Internal;
/// Class responsible for loading plugins on startup.
/// </summary>
[ServiceManager.BlockingEarlyLoadedService]
public class StartupPluginLoader
public class StartupPluginLoader : IServiceType
{
private static readonly ModuleLog Log = new("SPL");

View file

@ -6,7 +6,7 @@ namespace Dalamud.Plugin.Ipc.Internal
/// This class facilitates inter-plugin communication.
/// </summary>
[ServiceManager.EarlyLoadedService]
internal class CallGate
internal class CallGate : IServiceType
{
private readonly Dictionary<string, CallGateChannel> gates = new();

View file

@ -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<Type>();
var blockingEarlyLoadingServices = new HashSet<Type>();
var afterDrawingEarlyLoadedServices = new HashSet<Type>();
var dependencyServicesMap = new Dictionary<Type, List<Type>>();
var getAsyncTaskMap = new Dictionary<Type, Task>();
@ -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);
@ -125,21 +119,11 @@ namespace Dalamud
}).ConfigureAwait(false);
try
{
for (var i = 0; i < 2; i++)
{
var tasks = new List<Task>();
var servicesToLoad = new HashSet<Type>();
if (i == 0)
{
servicesToLoad.UnionWith(earlyLoadingServices);
servicesToLoad.UnionWith(blockingEarlyLoadingServices);
}
else
{
servicesToLoad.UnionWith(afterDrawingEarlyLoadedServices);
await (await Service<InterfaceManager>.GetAsync()).SceneInitializeTask;
}
while (servicesToLoad.Any())
{
@ -176,7 +160,6 @@ namespace Dalamud
tasks.RemoveAll(x => x.IsCompleted);
}
}
}
catch (Exception e)
{
Log.Error(e, "Failed resolving services");
@ -236,11 +219,11 @@ namespace Dalamud
}
/// <summary>
/// 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.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class AfterDrawingEarlyLoadedService : EarlyLoadedService
[AttributeUsage(AttributeTargets.Method)]
[MeansImplicitUse]
public class CallWhenServicesReady : Attribute
{
}
}

View file

@ -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.
/// </remarks>
/// <typeparam name="T">The class you want to store in the service locator.</typeparam>
internal static class Service<T>
internal static class Service<T> where T : IServiceType
{
// ReSharper disable once StaticMemberInGenericType
private static readonly TaskCompletionSource<T> 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<ServiceManager.CallWhenServicesReady>(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
/// <param name="obj">Object to set.</param>
public static void Provide(T obj)
{
InstanceTcs!.SetResult(obj);
InstanceTcs.SetResult(obj);
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
}
/// <summary>
/// Sets the service load state to failure.
/// </summary>
/// <param name="exception">The exception.</param>
public static void ProvideException(Exception exception)
{
ServiceManager.Log.Error(exception, "Service<{0}>: Error", typeof(T).Name);
InstanceTcs.SetException(exception);
}
/// <summary>
/// Pull the instance out of the service locator, waiting if necessary.
/// </summary>
@ -103,6 +125,7 @@ namespace Dalamud
/// Gets an enumerable containing Service&lt;T&gt;s that are required for this Service to initialize without blocking.
/// </summary>
/// <returns>List of dependency services.</returns>
[UsedImplicitly]
public static List<Type> GetDependencyServices()
{
var res = new List<Type>();
@ -119,7 +142,7 @@ namespace Dalamud
.ToList();
}
private static async Task<object?> GetServiceObjectConstructArgument(Type type)
private static async Task<object?> 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)!;