diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs
index d8f3427ef..23f3b1a6d 100644
--- a/Dalamud/Game/Addon/Events/AddonEventManager.cs
+++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs
@@ -57,6 +57,8 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
+
+ this.onUpdateCursor.Enable();
}
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
@@ -149,12 +151,6 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.onUpdateCursor.Enable();
- }
-
///
/// When an addon finalizes, check it for any registered events, and unregister them.
///
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
index 08a2d59ef..3528de562 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
@@ -58,6 +58,14 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
this.onAddonUpdateHook = new CallHook(this.address.AddonUpdate, this.OnAddonUpdate);
this.onAddonRefreshHook = Hook.FromAddress(this.address.AddonOnRefresh, this.OnAddonRefresh);
this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
+
+ this.onAddonSetupHook.Enable();
+ this.onAddonSetup2Hook.Enable();
+ this.onAddonFinalizeHook.Enable();
+ this.onAddonDrawHook.Enable();
+ this.onAddonUpdateHook.Enable();
+ this.onAddonRefreshHook.Enable();
+ this.onAddonRequestedUpdateHook.Enable();
}
private delegate void AddonSetupDelegate(AtkUnitBase* addon, uint valueCount, AtkValue* values);
@@ -181,18 +189,6 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.onAddonSetupHook.Enable();
- this.onAddonSetup2Hook.Enable();
- this.onAddonFinalizeHook.Enable();
- this.onAddonDrawHook.Enable();
- this.onAddonUpdateHook.Enable();
- this.onAddonRefreshHook.Enable();
- this.onAddonRequestedUpdateHook.Enable();
- }
-
private void RegisterReceiveEventHook(AtkUnitBase* addon)
{
// Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener.
diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs
index 3b3f65128..d387c2e2d 100644
--- a/Dalamud/Game/ClientState/ClientState.cs
+++ b/Dalamud/Game/ClientState/ClientState.cs
@@ -58,6 +58,8 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState
this.framework.Update += this.FrameworkOnOnUpdateEvent;
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
+
+ this.setupTerritoryTypeHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -120,12 +122,6 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState
this.networkHandlers.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 2db47ea4d..a298b1502 100644
--- a/Dalamud/Game/ClientState/Conditions/Condition.cs
+++ b/Dalamud/Game/ClientState/Conditions/Condition.cs
@@ -16,6 +16,9 @@ internal sealed partial class Condition : IServiceType, ICondition
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
///
internal const int MaxConditionEntries = 104;
+
+ [ServiceManager.ServiceDependency]
+ private readonly Framework framework = Service.Get();
private readonly bool[] cache = new bool[MaxConditionEntries];
@@ -24,6 +27,12 @@ internal sealed partial class Condition : IServiceType, ICondition
{
var resolver = clientState.AddressResolver;
this.Address = resolver.ConditionFlags;
+
+ // Initialization
+ for (var i = 0; i < MaxConditionEntries; i++)
+ this.cache[i] = this[i];
+
+ this.framework.Update += this.FrameworkUpdate;
}
///
@@ -80,17 +89,7 @@ internal sealed partial class Condition : IServiceType, ICondition
return false;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(Framework framework)
- {
- // Initialization
- for (var i = 0; i < MaxConditionEntries; i++)
- this.cache[i] = this[i];
-
- framework.Update += this.FrameworkUpdate;
- }
-
- private void FrameworkUpdate(IFramework framework)
+ private void FrameworkUpdate(IFramework unused)
{
for (var i = 0; i < MaxConditionEntries; i++)
{
@@ -144,7 +143,7 @@ internal sealed partial class Condition : IDisposable
if (disposing)
{
- Service.Get().Update -= this.FrameworkUpdate;
+ this.framework.Update -= this.FrameworkUpdate;
}
this.isDisposed = true;
diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs
index b03db6df2..40e632113 100644
--- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs
+++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs
@@ -38,6 +38,7 @@ internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
var resolver = clientState.AddressResolver;
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
this.gamepadPoll = Hook.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
+ this.gamepadPoll?.Enable();
}
private delegate int ControllerPoll(IntPtr controllerInput);
@@ -114,12 +115,6 @@ internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
GC.SuppressFinalize(this);
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.gamepadPoll?.Enable();
- }
-
private int GamepadPollDetour(IntPtr gamepadInput)
{
var original = this.gamepadPoll!.Original(gamepadInput);
diff --git a/Dalamud/Game/DutyState/DutyState.cs b/Dalamud/Game/DutyState/DutyState.cs
index 66356033b..c4bda0d19 100644
--- a/Dalamud/Game/DutyState/DutyState.cs
+++ b/Dalamud/Game/DutyState/DutyState.cs
@@ -37,6 +37,8 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
this.framework.Update += this.FrameworkOnUpdateEvent;
this.clientState.TerritoryChanged += this.TerritoryOnChangedEvent;
+
+ this.contentDirectorNetworkMessageHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -67,12 +69,6 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
this.clientState.TerritoryChanged -= this.TerritoryOnChangedEvent;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.contentDirectorNetworkMessageHook.Enable();
- }
-
private byte ContentDirectorNetworkMessageDetour(IntPtr a1, IntPtr a2, ushort* a3)
{
var category = *a3;
diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs
index 6db9f7312..ce34f2c06 100644
--- a/Dalamud/Game/Framework.cs
+++ b/Dalamud/Game/Framework.cs
@@ -58,6 +58,9 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
this.updateHook = Hook.FromAddress(this.addressResolver.TickAddress, this.HandleFrameworkUpdate);
this.destroyHook = Hook.FromAddress(this.addressResolver.DestroyAddress, this.HandleFrameworkDestroy);
+
+ this.updateHook.Enable();
+ this.destroyHook.Enable();
}
///
@@ -330,13 +333,6 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.updateHook.Enable();
- this.destroyHook.Enable();
- }
-
private void RunPendingTickTasks()
{
if (this.runOnNextTickTaskList.Count == 0 && this.runOnNextTickTaskList2.Count == 0)
diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs
index 50c5b2908..8f2a617cf 100644
--- a/Dalamud/Game/Gui/ChatGui.cs
+++ b/Dalamud/Game/Gui/ChatGui.cs
@@ -50,6 +50,10 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
this.printMessageHook = Hook.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour);
this.populateItemLinkHook = Hook.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
this.interactableLinkClickedHook = Hook.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
+
+ this.printMessageHook.Enable();
+ this.populateItemLinkHook.Enable();
+ this.interactableLinkClickedHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -182,14 +186,6 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
this.dalamudLinkHandlers.Remove((pluginName, commandId));
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.printMessageHook.Enable();
- this.populateItemLinkHook.Enable();
- this.interactableLinkClickedHook.Enable();
- }
-
private void PrintTagged(string message, XivChatType channel, string? tag, ushort? color)
{
var builder = new SeStringBuilder();
diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs
index 36056883e..2383b4e53 100644
--- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs
+++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs
@@ -36,6 +36,8 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText);
this.createFlyTextHook = Hook.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
+
+ this.createFlyTextHook.Enable();
}
///
@@ -143,12 +145,6 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
return terminated;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(GameGui gameGui)
- {
- this.createFlyTextHook.Enable();
- }
-
private IntPtr CreateFlyTextDetour(
IntPtr addonFlyText,
FlyTextKind kind,
diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs
index a1a17436e..a97e19a0a 100644
--- a/Dalamud/Game/Gui/GameGui.cs
+++ b/Dalamud/Game/Gui/GameGui.cs
@@ -75,6 +75,15 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
this.toggleUiHideHook = Hook.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour);
this.utf8StringFromSequenceHook = Hook.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour);
+
+ this.setGlobalBgmHook.Enable();
+ this.handleItemHoverHook.Enable();
+ this.handleItemOutHook.Enable();
+ this.handleImmHook.Enable();
+ this.toggleUiHideHook.Enable();
+ this.handleActionHoverHook.Enable();
+ this.handleActionOutHook.Enable();
+ this.utf8StringFromSequenceHook.Enable();
}
// Marshaled delegates
@@ -376,19 +385,6 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
this.GameUiHidden = false;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.setGlobalBgmHook.Enable();
- this.handleItemHoverHook.Enable();
- this.handleItemOutHook.Enable();
- this.handleImmHook.Enable();
- this.toggleUiHideHook.Enable();
- this.handleActionHoverHook.Enable();
- this.handleActionOutHook.Enable();
- this.utf8StringFromSequenceHook.Enable();
- }
-
private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6)
{
var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
diff --git a/Dalamud/Game/Gui/Internal/DalamudIME.cs b/Dalamud/Game/Gui/Internal/DalamudIME.cs
index 37c072806..a9f6991ae 100644
--- a/Dalamud/Game/Gui/Internal/DalamudIME.cs
+++ b/Dalamud/Game/Gui/Internal/DalamudIME.cs
@@ -253,7 +253,7 @@ internal unsafe class DalamudIME : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady]
+ [ServiceManager.CallWhenServicesReady("Effectively waiting for cimgui.dll to become available.")]
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
{
try
diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs
index 61c0f62e4..4a8332d24 100644
--- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs
+++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs
@@ -35,6 +35,7 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
this.receiveListingHook = Hook.FromAddress(this.address.ReceiveListing, this.HandleReceiveListingDetour);
+ this.receiveListingHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -60,12 +61,6 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu
}
}
- [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 362edb3be..7491b7f13 100644
--- a/Dalamud/Game/Gui/Toast/ToastGui.cs
+++ b/Dalamud/Game/Gui/Toast/ToastGui.cs
@@ -41,6 +41,10 @@ internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui
this.showNormalToastHook = Hook.FromAddress(this.address.ShowNormalToast, this.HandleNormalToastDetour);
this.showQuestToastHook = Hook.FromAddress(this.address.ShowQuestToast, this.HandleQuestToastDetour);
this.showErrorToastHook = Hook.FromAddress(this.address.ShowErrorToast, this.HandleErrorToastDetour);
+
+ this.showNormalToastHook.Enable();
+ this.showQuestToastHook.Enable();
+ this.showErrorToastHook.Enable();
}
#region Marshal delegates
@@ -109,14 +113,6 @@ internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui
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();
diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs
index 0013dca4d..4eb605a76 100644
--- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs
+++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs
@@ -63,6 +63,10 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
// this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
+
+ this.hookAgentHudOpenSystemMenu.Enable();
+ this.hookUiModuleRequestMainCommand.Enable();
+ this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
}
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
@@ -75,14 +79,6 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5);
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(DalamudInterface dalamudInterface)
- {
- this.hookAgentHudOpenSystemMenu.Enable();
- this.hookUiModuleRequestMainCommand.Enable();
- this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
- }
-
/*
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
{
diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs
index 9ea3e491e..4099f228e 100644
--- a/Dalamud/Game/Network/GameNetwork.cs
+++ b/Dalamud/Game/Network/GameNetwork.cs
@@ -44,6 +44,9 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
this.processZonePacketDownHook = Hook.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour);
this.processZonePacketUpHook = Hook.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
+
+ this.processZonePacketDownHook.Enable();
+ this.processZonePacketUpHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -62,13 +65,6 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
this.processZonePacketUpHook.Dispose();
}
- [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/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 52e849c0e..1b12fd853 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -1063,14 +1063,10 @@ internal class InterfaceManager : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(
- TargetSigScanner sigScanner,
- DalamudAssetManager dalamudAssetManager,
- DalamudConfiguration configuration)
+ [ServiceManager.CallWhenServicesReady(
+ "InterfaceManager accepts event registration and stuff even when the game window is not ready.")]
+ private void ContinueConstruction(TargetSigScanner sigScanner, DalamudConfiguration configuration)
{
- dalamudAssetManager.WaitForAllRequiredAssets().Wait();
-
this.address.Setup(sigScanner);
this.framework.RunOnFrameworkThread(() =>
{
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ServicesWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ServicesWidget.cs
index 49f3c1b90..22b53cdaa 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ServicesWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ServicesWidget.cs
@@ -1,4 +1,6 @@
-using System.Linq;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
@@ -13,6 +15,13 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
///
internal class ServicesWidget : IDataWindowWidget
{
+ private readonly Dictionary nodeRects = new();
+ private readonly HashSet selectedNodes = new();
+ private readonly HashSet tempRelatedNodes = new();
+
+ private bool includeUnloadDependencies;
+ private List>? dependencyNodes;
+
///
public string[]? CommandShortcuts { get; init; } = { "services" };
@@ -33,27 +42,294 @@ internal class ServicesWidget : IDataWindowWidget
{
var container = Service.Get();
- foreach (var instance in container.Instances)
+ if (ImGui.CollapsingHeader("Dependencies"))
{
- var hasInterface = container.InterfaceToTypeMap.Values.Any(x => x == instance.Key);
- var isPublic = instance.Key.IsPublic;
+ if (ImGui.Button("Clear selection"))
+ this.selectedNodes.Clear();
- ImGui.BulletText($"{instance.Key.FullName} ({instance.Key.GetServiceKind()})");
-
- using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !hasInterface))
+ ImGui.SameLine();
+ switch (this.includeUnloadDependencies)
{
- ImGui.Text(hasInterface
- ? $"\t => Provided via interface: {container.InterfaceToTypeMap.First(x => x.Value == instance.Key).Key.FullName}"
- : "\t => NO INTERFACE!!!");
+ case true when ImGui.Button("Show load-time dependencies"):
+ this.includeUnloadDependencies = false;
+ this.dependencyNodes = null;
+ break;
+ case false when ImGui.Button("Show unload-time dependencies"):
+ this.includeUnloadDependencies = true;
+ this.dependencyNodes = null;
+ break;
}
- if (isPublic)
+ this.dependencyNodes ??= ServiceDependencyNode.CreateTreeByLevel(this.includeUnloadDependencies);
+ var cellPad = ImGui.CalcTextSize("WW");
+ var margin = ImGui.CalcTextSize("W\nW\nW");
+ var rowHeight = cellPad.Y * 3;
+ var width = ImGui.GetContentRegionAvail().X;
+ if (ImGui.BeginChild(
+ "dependency-graph",
+ new(width, (this.dependencyNodes.Count * (rowHeight + margin.Y)) + cellPad.Y),
+ false,
+ ImGuiWindowFlags.HorizontalScrollbar))
{
- using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
- ImGui.Text("\t => PUBLIC!!!");
+ const uint rectBaseBorderColor = 0xFFFFFFFF;
+ const uint rectHoverFillColor = 0xFF404040;
+ const uint rectHoverRelatedFillColor = 0xFF802020;
+ const uint rectSelectedFillColor = 0xFF20A020;
+ const uint rectSelectedRelatedFillColor = 0xFF204020;
+ const uint lineBaseColor = 0xFF808080;
+ const uint lineHoverColor = 0xFFFF8080;
+ const uint lineHoverNotColor = 0xFF404040;
+ const uint lineSelectedColor = 0xFF80FF00;
+ const uint lineInvalidColor = 0xFFFF0000;
+
+ ServiceDependencyNode? hoveredNode = null;
+
+ var pos = ImGui.GetCursorScreenPos();
+ var dl = ImGui.GetWindowDrawList();
+ var mouse = ImGui.GetMousePos();
+ var maxRowWidth = 0f;
+
+ // 1. Layout
+ for (var level = 0; level < this.dependencyNodes.Count; level++)
+ {
+ var levelNodes = this.dependencyNodes[level];
+
+ var rowWidth = 0f;
+ foreach (var node in levelNodes)
+ rowWidth += ImGui.CalcTextSize(node.TypeName).X + cellPad.X + margin.X;
+
+ var off = cellPad / 2;
+ if (rowWidth < width)
+ off.X += ImGui.GetScrollX() + ((width - rowWidth) / 2);
+ else if (rowWidth - ImGui.GetScrollX() < width)
+ off.X += width - (rowWidth - ImGui.GetScrollX());
+ off.Y = (rowHeight + margin.Y) * level;
+
+ foreach (var node in levelNodes)
+ {
+ var textSize = ImGui.CalcTextSize(node.TypeName);
+ var cellSize = textSize + cellPad;
+
+ var rc = new Vector4(pos + off, pos.X + off.X + cellSize.X, pos.Y + off.Y + cellSize.Y);
+ this.nodeRects[node] = rc;
+ if (rc.X <= mouse.X && mouse.X < rc.Z && rc.Y <= mouse.Y && mouse.Y < rc.W)
+ {
+ hoveredNode = node;
+ if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
+ {
+ if (this.selectedNodes.Contains(node.Type))
+ this.selectedNodes.Remove(node.Type);
+ else
+ this.selectedNodes.Add(node.Type);
+ }
+ }
+
+ off.X += cellSize.X + margin.X;
+ }
+
+ maxRowWidth = Math.Max(maxRowWidth, rowWidth);
+ }
+
+ // 2. Draw non-hovered lines
+ foreach (var levelNodes in this.dependencyNodes)
+ {
+ foreach (var node in levelNodes)
+ {
+ var rect = this.nodeRects[node];
+ var point1 = new Vector2((rect.X + rect.Z) / 2, rect.Y);
+
+ foreach (var parent in node.InvalidParents)
+ {
+ rect = this.nodeRects[parent];
+ var point2 = new Vector2((rect.X + rect.Z) / 2, rect.W);
+ if (node == hoveredNode || parent == hoveredNode)
+ continue;
+
+ dl.AddLine(point1, point2, lineInvalidColor, 2f * ImGuiHelpers.GlobalScale);
+ }
+
+ foreach (var parent in node.Parents)
+ {
+ rect = this.nodeRects[parent];
+ var point2 = new Vector2((rect.X + rect.Z) / 2, rect.W);
+ if (node == hoveredNode || parent == hoveredNode)
+ continue;
+
+ var isSelected = this.selectedNodes.Contains(node.Type) ||
+ this.selectedNodes.Contains(parent.Type);
+ dl.AddLine(
+ point1,
+ point2,
+ isSelected
+ ? lineSelectedColor
+ : hoveredNode is not null
+ ? lineHoverNotColor
+ : lineBaseColor);
+ }
+ }
+ }
+
+ // 3. Draw boxes
+ foreach (var levelNodes in this.dependencyNodes)
+ {
+ foreach (var node in levelNodes)
+ {
+ var textSize = ImGui.CalcTextSize(node.TypeName);
+ var cellSize = textSize + cellPad;
+
+ var rc = this.nodeRects[node];
+ if (hoveredNode == node)
+ dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectHoverFillColor);
+ else if (this.selectedNodes.Contains(node.Type))
+ dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectSelectedFillColor);
+ else if (node.Relatives.Any(x => this.selectedNodes.Contains(x.Type)))
+ dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectSelectedRelatedFillColor);
+ else if (hoveredNode?.Relatives.Select(x => x.Type).Contains(node.Type) is true)
+ dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectHoverRelatedFillColor);
+
+ dl.AddRect(new(rc.X, rc.Y), new(rc.Z, rc.W), rectBaseBorderColor);
+ ImGui.SetCursorPos((new Vector2(rc.X, rc.Y) - pos) + ((cellSize - textSize) / 2));
+ ImGui.TextUnformatted(node.TypeName);
+ }
+ }
+
+ // 4. Draw hovered lines
+ if (hoveredNode is not null)
+ {
+ foreach (var levelNodes in this.dependencyNodes)
+ {
+ foreach (var node in levelNodes)
+ {
+ var rect = this.nodeRects[node];
+ var point1 = new Vector2((rect.X + rect.Z) / 2, rect.Y);
+ foreach (var parent in node.Parents)
+ {
+ if (node == hoveredNode || parent == hoveredNode)
+ {
+ rect = this.nodeRects[parent];
+ var point2 = new Vector2((rect.X + rect.Z) / 2, rect.W);
+ dl.AddLine(
+ point1,
+ point2,
+ lineHoverColor,
+ 2 * ImGuiHelpers.GlobalScale);
+ }
+ }
+ }
+ }
+ }
+
+ ImGui.SetCursorPos(default);
+ ImGui.Dummy(new(maxRowWidth, this.dependencyNodes.Count * rowHeight));
+ ImGui.EndChild();
}
-
- ImGuiHelpers.ScaledDummy(2);
+ }
+
+ if (ImGui.CollapsingHeader("Plugin-facing Services"))
+ {
+ foreach (var instance in container.Instances)
+ {
+ var hasInterface = container.InterfaceToTypeMap.Values.Any(x => x == instance.Key);
+ var isPublic = instance.Key.IsPublic;
+
+ ImGui.BulletText($"{instance.Key.FullName} ({instance.Key.GetServiceKind()})");
+
+ using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !hasInterface))
+ {
+ ImGui.Text(
+ hasInterface
+ ? $"\t => Provided via interface: {container.InterfaceToTypeMap.First(x => x.Value == instance.Key).Key.FullName}"
+ : "\t => NO INTERFACE!!!");
+ }
+
+ if (isPublic)
+ {
+ using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
+ ImGui.Text("\t => PUBLIC!!!");
+ }
+
+ ImGuiHelpers.ScaledDummy(2);
+ }
+ }
+ }
+
+ private class ServiceDependencyNode
+ {
+ private readonly List parents = new();
+ private readonly List children = new();
+ private readonly List invalidParents = new();
+
+ private ServiceDependencyNode(Type t) => this.Type = t;
+
+ public Type Type { get; }
+
+ public string TypeName => this.Type.Name;
+
+ public IReadOnlyList Parents => this.parents;
+
+ public IReadOnlyList Children => this.children;
+
+ public IReadOnlyList InvalidParents => this.invalidParents;
+
+ public IEnumerable Relatives =>
+ this.parents.Concat(this.children).Concat(this.invalidParents);
+
+ public int Level { get; private set; }
+
+ public static List CreateTree(bool includeUnloadDependencies)
+ {
+ var nodes = new Dictionary();
+ foreach (var t in ServiceManager.GetConcreteServiceTypes())
+ nodes.Add(typeof(Service<>).MakeGenericType(t), new(t));
+ foreach (var t in ServiceManager.GetConcreteServiceTypes())
+ {
+ var st = typeof(Service<>).MakeGenericType(t);
+ var node = nodes[st];
+ foreach (var depType in ServiceHelpers.GetDependencies(st, includeUnloadDependencies))
+ {
+ var depServiceType = typeof(Service<>).MakeGenericType(depType);
+ var depNode = nodes[depServiceType];
+ if (node.IsAncestorOf(depType))
+ {
+ node.invalidParents.Add(depNode);
+ }
+ else
+ {
+ depNode.UpdateNodeLevel(1);
+ node.UpdateNodeLevel(depNode.Level + 1);
+ node.parents.Add(depNode);
+ depNode.children.Add(node);
+ }
+ }
+ }
+
+ return nodes.Values.OrderBy(x => x.Level).ThenBy(x => x.Type.Name).ToList();
+ }
+
+ public static List> CreateTreeByLevel(bool includeUnloadDependencies)
+ {
+ var res = new List>();
+ foreach (var n in CreateTree(includeUnloadDependencies))
+ {
+ while (res.Count <= n.Level)
+ res.Add(new());
+ res[n.Level].Add(n);
+ }
+
+ return res;
+ }
+
+ private bool IsAncestorOf(Type type) =>
+ this.children.Any(x => x.Type == type) || this.children.Any(x => x.IsAncestorOf(type));
+
+ private void UpdateNodeLevel(int newLevel)
+ {
+ if (this.Level >= newLevel)
+ return;
+
+ this.Level = newLevel;
+ foreach (var c in this.children)
+ c.UpdateNodeLevel(newLevel + 1);
}
}
}
diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs
index 363d01f26..0ef3d49f8 100644
--- a/Dalamud/Plugin/Internal/PluginManager.cs
+++ b/Dalamud/Plugin/Internal/PluginManager.cs
@@ -21,6 +21,7 @@ using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
+using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Networking.Http;
@@ -29,6 +30,7 @@ using Dalamud.Plugin.Internal.Profiles;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Internal.Types.Manifest;
using Dalamud.Plugin.Ipc.Internal;
+using Dalamud.Support;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
using Newtonsoft.Json;
@@ -93,7 +95,9 @@ internal partial class PluginManager : IDisposable, IServiceType
}
[ServiceManager.ServiceConstructor]
- private PluginManager()
+ private PluginManager(
+ ServiceManager.RegisterStartupBlockerDelegate registerStartupBlocker,
+ ServiceManager.RegisterUnloadAfterDelegate registerUnloadAfter)
{
this.pluginDirectory = new DirectoryInfo(this.dalamud.StartInfo.PluginDirectory!);
@@ -142,6 +146,14 @@ internal partial class PluginManager : IDisposable, IServiceType
this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient);
this.ApplyPatches();
+
+ registerStartupBlocker(
+ Task.Run(this.LoadAndStartLoadSyncPlugins),
+ "Waiting for plugins that asked to be loaded before the game.");
+
+ registerUnloadAfter(
+ ResolvePossiblePluginDependencyServices(),
+ "See the attached comment for the called function.");
}
///
@@ -1201,6 +1213,49 @@ internal partial class PluginManager : IDisposable, IServiceType
/// The calling plugin, or null.
public LocalPlugin? FindCallingPlugin() => this.FindCallingPlugin(new StackTrace());
+ ///
+ /// Resolves the services that a plugin may have a dependency on.
+ /// This is required, as the lifetime of a plugin cannot be longer than PluginManager,
+ /// and we want to ensure that dependency services to be kept alive at least until all the plugins, and thus
+ /// PluginManager to be gone.
+ ///
+ /// The dependency services.
+ private static IEnumerable ResolvePossiblePluginDependencyServices()
+ {
+ foreach (var serviceType in ServiceManager.GetConcreteServiceTypes())
+ {
+ if (serviceType == typeof(PluginManager))
+ continue;
+
+ // Scoped plugin services lifetime is tied to their scopes. They go away when LocalPlugin goes away.
+ // Nonetheless, their direct dependencies must be considered.
+ if (serviceType.GetServiceKind() == ServiceManager.ServiceKind.ScopedService)
+ {
+ var typeAsServiceT = ServiceHelpers.GetAsService(serviceType);
+ var dependencies = ServiceHelpers.GetDependencies(typeAsServiceT, false);
+ ServiceManager.Log.Verbose("Found dependencies of scoped plugin service {Type} ({Cnt})", serviceType.FullName!, dependencies!.Count);
+
+ foreach (var scopedDep in dependencies)
+ {
+ if (scopedDep == typeof(PluginManager))
+ throw new Exception("Scoped plugin services cannot depend on PluginManager.");
+
+ ServiceManager.Log.Verbose("PluginManager MUST depend on {Type} via {BaseType}", scopedDep.FullName!, serviceType.FullName!);
+ yield return scopedDep;
+ }
+
+ continue;
+ }
+
+ var pluginInterfaceAttribute = serviceType.GetCustomAttribute(true);
+ if (pluginInterfaceAttribute == null)
+ continue;
+
+ ServiceManager.Log.Verbose("PluginManager MUST depend on {Type}", serviceType.FullName!);
+ yield return serviceType;
+ }
+ }
+
private async Task DownloadPluginAsync(RemotePluginManifest repoManifest, bool useTesting)
{
var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall;
@@ -1590,6 +1645,38 @@ internal partial class PluginManager : IDisposable, IServiceType
}
}
+ private void LoadAndStartLoadSyncPlugins()
+ {
+ try
+ {
+ using (Timings.Start("PM Load Plugin Repos"))
+ {
+ _ = this.SetPluginReposFromConfigAsync(false);
+ this.OnInstalledPluginsChanged += () => Task.Run(Troubleshooting.LogTroubleshooting);
+
+ Log.Information("[T3] PM repos OK!");
+ }
+
+ using (Timings.Start("PM Cleanup Plugins"))
+ {
+ this.CleanupPlugins();
+ Log.Information("[T3] PMC OK!");
+ }
+
+ using (Timings.Start("PM Load Sync Plugins"))
+ {
+ this.LoadAllPlugins().Wait();
+ Log.Information("[T3] PML OK!");
+ }
+
+ _ = Task.Run(Troubleshooting.LogTroubleshooting);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Plugin load failed");
+ }
+ }
+
private static class Locs
{
public static string DalamudPluginUpdateSuccessful(string name, Version version) => Loc.Localize("DalamudPluginUpdateSuccessful", " 》 {0} updated to v{1}.").Format(name, version);
diff --git a/Dalamud/Plugin/Internal/StartupPluginLoader.cs b/Dalamud/Plugin/Internal/StartupPluginLoader.cs
deleted file mode 100644
index 4f68d39fc..000000000
--- a/Dalamud/Plugin/Internal/StartupPluginLoader.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-using Dalamud.Logging.Internal;
-using Dalamud.Support;
-using Dalamud.Utility.Timing;
-
-namespace Dalamud.Plugin.Internal;
-
-///
-/// Class responsible for loading plugins on startup.
-///
-[ServiceManager.BlockingEarlyLoadedService]
-public class StartupPluginLoader : IServiceType
-{
- private static readonly ModuleLog Log = new("SPL");
-
- [ServiceManager.ServiceConstructor]
- private StartupPluginLoader(PluginManager pluginManager)
- {
- try
- {
- using (Timings.Start("PM Load Plugin Repos"))
- {
- _ = pluginManager.SetPluginReposFromConfigAsync(false);
- pluginManager.OnInstalledPluginsChanged += () => Task.Run(Troubleshooting.LogTroubleshooting);
-
- Log.Information("[T3] PM repos OK!");
- }
-
- using (Timings.Start("PM Cleanup Plugins"))
- {
- pluginManager.CleanupPlugins();
- Log.Information("[T3] PMC OK!");
- }
-
- using (Timings.Start("PM Load Sync Plugins"))
- {
- pluginManager.LoadAllPlugins().Wait();
- Log.Information("[T3] PML OK!");
- }
-
- Task.Run(Troubleshooting.LogTroubleshooting);
- }
- catch (Exception ex)
- {
- Log.Error(ex, "Plugin load failed");
- }
- }
-}
diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs
index 21c08ce72..3ff7cde76 100644
--- a/Dalamud/ServiceManager.cs
+++ b/Dalamud/ServiceManager.cs
@@ -11,6 +11,7 @@ using Dalamud.Game;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Storage;
+using Dalamud.Utility;
using Dalamud.Utility.Timing;
using JetBrains.Annotations;
@@ -21,7 +22,7 @@ namespace Dalamud;
// - Visualize/output .dot or imgui thing
///
-/// Class to initialize Service<T>s.
+/// Class to initialize .
///
internal static class ServiceManager
{
@@ -43,6 +44,26 @@ internal static class ServiceManager
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
private static ManualResetEvent unloadResetEvent = new(false);
+
+ ///
+ /// Delegate for registering startup blocker task.
+ /// Do not use this delegate outside the constructor.
+ ///
+ /// The blocker task.
+ /// The justification for using this feature.
+ [InjectableType]
+ public delegate void RegisterStartupBlockerDelegate(Task t, string justification);
+
+ ///
+ /// Delegate for registering services that should be unloaded before self.
+ /// Intended for use with . If you think you need to use this outside
+ /// of that, consider having a discussion first.
+ /// Do not use this delegate outside the constructor.
+ ///
+ /// Services that should be unloaded first.
+ /// The justification for using this feature.
+ [InjectableType]
+ public delegate void RegisterUnloadAfterDelegate(IEnumerable unloadAfter, string justification);
///
/// Kinds of services.
@@ -125,6 +146,15 @@ internal static class ServiceManager
#endif
}
+ ///
+ /// Gets the concrete types of services, i.e. the non-abstract non-interface types.
+ ///
+ /// The enumerable of service types, that may be enumerated only once per call.
+ public static IEnumerable GetConcreteServiceTypes() =>
+ Assembly.GetExecutingAssembly()
+ .GetTypes()
+ .Where(x => x.IsAssignableTo(typeof(IServiceType)) && !x.IsInterface && !x.IsAbstract);
+
///
/// Kicks off construction of services that can handle early loading.
///
@@ -141,7 +171,7 @@ internal static class ServiceManager
var serviceContainer = Service.Get();
- foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsAssignableTo(typeof(IServiceType)) && !x.IsInterface && !x.IsAbstract))
+ foreach (var serviceType in GetConcreteServiceTypes())
{
var serviceKind = serviceType.GetServiceKind();
Debug.Assert(serviceKind != ServiceKind.None, $"Service<{serviceType.FullName}> did not specify a kind");
@@ -157,7 +187,7 @@ internal static class ServiceManager
var getTask = (Task)genericWrappedServiceType
.InvokeMember(
- "GetAsync",
+ nameof(Service.GetAsync),
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
null,
null,
@@ -184,17 +214,42 @@ internal static class ServiceManager
}
var typeAsServiceT = ServiceHelpers.GetAsService(serviceType);
- dependencyServicesMap[serviceType] = ServiceHelpers.GetDependencies(typeAsServiceT)
+ dependencyServicesMap[serviceType] = ServiceHelpers.GetDependencies(typeAsServiceT, false)
.Select(x => typeof(Service<>).MakeGenericType(x))
.ToList();
}
+ var blockerTasks = new List();
_ = Task.Run(async () =>
{
try
{
- var whenBlockingComplete = Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
- while (await Task.WhenAny(whenBlockingComplete, Task.Delay(120000)) != whenBlockingComplete)
+ // Wait for all blocking constructors to complete first.
+ await WaitWithTimeoutConsent(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
+
+ // All the BlockingEarlyLoadedService constructors have been run,
+ // and blockerTasks now will not change. Now wait for them.
+ // Note that ServiceManager.CallWhenServicesReady does not get to register a blocker.
+ await WaitWithTimeoutConsent(blockerTasks);
+
+ BlockingServicesLoadedTaskCompletionSource.SetResult();
+ Timings.Event("BlockingServices Initialized");
+ }
+ catch (Exception e)
+ {
+ BlockingServicesLoadedTaskCompletionSource.SetException(e);
+ }
+
+ return;
+
+ async Task WaitWithTimeoutConsent(IEnumerable tasksEnumerable)
+ {
+ var tasks = tasksEnumerable.AsReadOnlyCollection();
+ if (tasks.Count == 0)
+ return;
+
+ var aggregatedTask = Task.WhenAll(tasks);
+ while (await Task.WhenAny(aggregatedTask, Task.Delay(120000)) != aggregatedTask)
{
if (NativeFunctions.MessageBoxW(
IntPtr.Zero,
@@ -208,13 +263,6 @@ internal static class ServiceManager
"and the user chose to continue without Dalamud.");
}
}
-
- BlockingServicesLoadedTaskCompletionSource.SetResult();
- Timings.Event("BlockingServices Initialized");
- }
- catch (Exception e)
- {
- BlockingServicesLoadedTaskCompletionSource.SetException(e);
}
}).ConfigureAwait(false);
@@ -249,6 +297,25 @@ internal static class ServiceManager
if (!hasDeps)
continue;
+ // This object will be used in a task. Each task must receive a new object.
+ var startLoaderArgs = new List
/// The object.
- [UsedImplicitly]
public static Task GetAsync() => instanceTcs.Task;
///
@@ -141,11 +140,15 @@ internal static class Service where T : IServiceType
///
/// Gets an enumerable containing s that are required for this Service to initialize
/// without blocking.
+ /// These are NOT returned as types; raw types will be returned.
///
+ /// Whether to include the unload dependencies.
/// List of dependency services.
- [UsedImplicitly]
- public static List GetDependencyServices()
+ public static IReadOnlyCollection GetDependencyServices(bool includeUnloadDependencies)
{
+ if (includeUnloadDependencies && dependencyServicesForUnload is not null)
+ return dependencyServicesForUnload;
+
if (dependencyServices is not null)
return dependencyServices;
@@ -158,7 +161,8 @@ internal static class Service where T : IServiceType
{
res.AddRange(ctor
.GetParameters()
- .Select(x => x.ParameterType));
+ .Select(x => x.ParameterType)
+ .Where(x => x.GetServiceKind() != ServiceManager.ServiceKind.None));
}
res.AddRange(typeof(T)
@@ -171,50 +175,8 @@ internal static class Service where T : IServiceType
.OfType()
.Select(x => x.GetType().GetGenericArguments().First()));
- // HACK: PluginManager needs to depend on ALL plugin exposed services
- if (typeof(T) == typeof(PluginManager))
- {
- foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
- {
- if (!serviceType.IsAssignableTo(typeof(IServiceType)))
- continue;
-
- if (serviceType == typeof(PluginManager))
- continue;
-
- // Scoped plugin services lifetime is tied to their scopes. They go away when LocalPlugin goes away.
- // Nonetheless, their direct dependencies must be considered.
- if (serviceType.GetServiceKind() == ServiceManager.ServiceKind.ScopedService)
- {
- var typeAsServiceT = ServiceHelpers.GetAsService(serviceType);
- var dependencies = ServiceHelpers.GetDependencies(typeAsServiceT);
- ServiceManager.Log.Verbose("Found dependencies of scoped plugin service {Type} ({Cnt})", serviceType.FullName!, dependencies!.Count);
-
- foreach (var scopedDep in dependencies)
- {
- if (scopedDep == typeof(PluginManager))
- throw new Exception("Scoped plugin services cannot depend on PluginManager.");
-
- ServiceManager.Log.Verbose("PluginManager MUST depend on {Type} via {BaseType}", scopedDep.FullName!, serviceType.FullName!);
- res.Add(scopedDep);
- }
-
- continue;
- }
-
- var pluginInterfaceAttribute = serviceType.GetCustomAttribute(true);
- if (pluginInterfaceAttribute == null)
- continue;
-
- ServiceManager.Log.Verbose("PluginManager MUST depend on {Type}", serviceType.FullName!);
- res.Add(serviceType);
- }
- }
-
foreach (var type in res)
- {
ServiceManager.Log.Verbose("Service<{0}>: => Dependency: {1}", typeof(T).Name, type.Name);
- }
var deps = res
.Distinct()
@@ -244,8 +206,9 @@ internal static class Service where T : IServiceType
///
/// Starts the service loader. Only to be called from .
///
+ /// Additional objects available to constructors.
/// The loader task.
- internal static Task StartLoader()
+ internal static Task StartLoader(IReadOnlyCollection