diff --git a/Dalamud.CorePlugin/PluginImpl.cs b/Dalamud.CorePlugin/PluginImpl.cs index cb9b4368a..7c9adc6a8 100644 --- a/Dalamud.CorePlugin/PluginImpl.cs +++ b/Dalamud.CorePlugin/PluginImpl.cs @@ -97,8 +97,6 @@ namespace Dalamud.CorePlugin this.Interface.UiBuilder.Draw -= this.OnDraw; this.windowSystem.RemoveAllWindows(); - - this.Interface.ExplicitDispose(); } /// diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 85a9507c9..70ed5dfde 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -26,7 +26,7 @@ namespace Dalamud.Configuration.Internal; #pragma warning disable SA1015 [InherentDependency] // We must still have this when unloading #pragma warning restore SA1015 -internal sealed class DalamudConfiguration : IServiceType, IDisposable +internal sealed class DalamudConfiguration : IInternalDisposableService { private static readonly JsonSerializerSettings SerializerSettings = new() { @@ -502,7 +502,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { // Make sure that we save, if a save is queued while we are shutting down this.Update(); diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 8c858ce7c..f9d2aff3c 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Dalamud.Common; using Dalamud.Configuration.Internal; using Dalamud.Game; -using Dalamud.Interface.Internal; using Dalamud.Plugin.Internal; using Dalamud.Storage; using Dalamud.Utility; @@ -187,27 +186,6 @@ internal sealed class Dalamud : IServiceType this.unloadSignal.WaitOne(); } - /// - /// Dispose subsystems related to plugin handling. - /// - public void DisposePlugins() - { - // this must be done before unloading interface manager, in order to do rebuild - // the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game - // will not receive any windows messages - Service.GetNullable()?.Dispose(); - - // this must be done before unloading plugins, or it can cause a race condition - // due to rendering happening on another thread, where a plugin might receive - // a render call after it has been disposed, which can crash if it attempts to - // use any resources that it freed in its own Dispose method - Service.GetNullable()?.Dispose(); - - Service.GetNullable()?.Dispose(); - - Service.GetNullable()?.Dispose(); - } - /// /// Replace the current exception handler with the default one. /// diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index b08c6ffe7..da93f57c4 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -27,7 +27,7 @@ namespace Dalamud.Data; #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal sealed class DataManager : IDisposable, IServiceType, IDataManager +internal sealed class DataManager : IInternalDisposableService, IDataManager { private readonly Thread luminaResourceThread; private readonly CancellationTokenSource luminaCancellationTokenSource; @@ -158,7 +158,7 @@ internal sealed class DataManager : IDisposable, IServiceType, IDataManager #endregion /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.luminaCancellationTokenSource.Cancel(); } diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index d0f9e8845..1ad3ad8a9 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -138,7 +138,9 @@ public sealed class EntryPoint SerilogEventSink.Instance.LogLine += SerilogOnLogLine; // Load configuration first to get some early persistent state, like log level +#pragma warning disable CS0618 // Type or member is obsolete var fs = new ReliableFileStorage(Path.GetDirectoryName(info.ConfigurationPath)!); +#pragma warning restore CS0618 // Type or member is obsolete var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs); // Set the appropriate logging level from the configuration diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs index 8ee09bed8..a9b9ef5fa 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManager.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs @@ -19,7 +19,7 @@ namespace Dalamud.Game.Addon.Events; /// [InterfaceVersion("1.0")] [ServiceManager.EarlyLoadedService] -internal unsafe class AddonEventManager : IDisposable, IServiceType +internal unsafe class AddonEventManager : IInternalDisposableService { /// /// PluginName for Dalamud Internal use. @@ -62,7 +62,7 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType private delegate nint UpdateCursorDelegate(RaptureAtkModule* module); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.onUpdateCursor.Dispose(); @@ -204,7 +204,7 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddonEventManager +internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddonEventManager { [ServiceManager.ServiceDependency] private readonly AddonEventManager eventManagerService = Service.Get(); @@ -225,7 +225,7 @@ internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddon } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { // if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared. if (this.isForcingCursor) diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index 37f12ce3a..eefb3b5e9 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -19,7 +19,7 @@ namespace Dalamud.Game.Addon.Lifecycle; /// [InterfaceVersion("1.0")] [ServiceManager.EarlyLoadedService] -internal unsafe class AddonLifecycle : IDisposable, IServiceType +internal unsafe class AddonLifecycle : IInternalDisposableService { private static readonly ModuleLog Log = new("AddonLifecycle"); @@ -89,7 +89,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType internal List EventListeners { get; } = new(); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.onAddonSetupHook.Dispose(); this.onAddonSetup2Hook.Dispose(); @@ -383,7 +383,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLifecycle +internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLifecycle { [ServiceManager.ServiceDependency] private readonly AddonLifecycle addonLifecycleService = Service.Get(); @@ -391,7 +391,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif private readonly List eventListeners = new(); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { foreach (var listener in this.eventListeners) { diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index d387c2e2d..bd4259f5a 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -23,7 +23,7 @@ namespace Dalamud.Game.ClientState; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed class ClientState : IDisposable, IServiceType, IClientState +internal sealed class ClientState : IInternalDisposableService, IClientState { private static readonly ModuleLog Log = new("ClientState"); @@ -115,7 +115,7 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState /// /// Dispose of managed and unmanaged resources. /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.setupTerritoryTypeHook.Dispose(); this.framework.Update -= this.FrameworkOnOnUpdateEvent; @@ -196,7 +196,7 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class ClientStatePluginScoped : IDisposable, IServiceType, IClientState +internal class ClientStatePluginScoped : IInternalDisposableService, IClientState { [ServiceManager.ServiceDependency] private readonly ClientState clientStateService = Service.Get(); @@ -257,7 +257,7 @@ internal class ClientStatePluginScoped : IDisposable, IServiceType, IClientState public bool IsGPosing => this.clientStateService.IsGPosing; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward; this.clientStateService.Login -= this.LoginForward; diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs index a298b1502..dc8b28494 100644 --- a/Dalamud/Game/ClientState/Conditions/Condition.cs +++ b/Dalamud/Game/ClientState/Conditions/Condition.cs @@ -10,7 +10,7 @@ namespace Dalamud.Game.ClientState.Conditions; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed partial class Condition : IServiceType, ICondition +internal sealed partial class Condition : IInternalDisposableService, 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. @@ -22,6 +22,8 @@ internal sealed partial class Condition : IServiceType, ICondition private readonly bool[] cache = new bool[MaxConditionEntries]; + private bool isDisposed; + [ServiceManager.ServiceConstructor] private Condition(ClientState clientState) { @@ -35,6 +37,9 @@ internal sealed partial class Condition : IServiceType, ICondition this.framework.Update += this.FrameworkUpdate; } + /// Finalizes an instance of the class. + ~Condition() => this.Dispose(false); + /// public event ICondition.ConditionChangeDelegate? ConditionChange; @@ -60,6 +65,9 @@ internal sealed partial class Condition : IServiceType, ICondition public bool this[ConditionFlag flag] => this[(int)flag]; + /// + void IInternalDisposableService.DisposeService() => this.Dispose(true); + /// public bool Any() { @@ -89,6 +97,19 @@ internal sealed partial class Condition : IServiceType, ICondition return false; } + private void Dispose(bool disposing) + { + if (this.isDisposed) + return; + + if (disposing) + { + this.framework.Update -= this.FrameworkUpdate; + } + + this.isDisposed = true; + } + private void FrameworkUpdate(IFramework unused) { for (var i = 0; i < MaxConditionEntries; i++) @@ -112,44 +133,6 @@ internal sealed partial class Condition : IServiceType, ICondition } } -/// -/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. -/// -internal sealed partial class Condition : IDisposable -{ - private bool isDisposed; - - /// - /// Finalizes an instance of the class. - /// - ~Condition() - { - this.Dispose(false); - } - - /// - /// Disposes this instance, alongside its hooks. - /// - void IDisposable.Dispose() - { - GC.SuppressFinalize(this); - this.Dispose(true); - } - - private void Dispose(bool disposing) - { - if (this.isDisposed) - return; - - if (disposing) - { - this.framework.Update -= this.FrameworkUpdate; - } - - this.isDisposed = true; - } -} - /// /// Plugin-scoped version of a Condition service. /// @@ -159,7 +142,7 @@ internal sealed partial class Condition : IDisposable #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class ConditionPluginScoped : IDisposable, IServiceType, ICondition +internal class ConditionPluginScoped : IInternalDisposableService, ICondition { [ServiceManager.ServiceDependency] private readonly Condition conditionService = Service.Get(); @@ -185,7 +168,7 @@ internal class ConditionPluginScoped : IDisposable, IServiceType, ICondition public bool this[int flag] => this.conditionService[flag]; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.conditionService.ConditionChange -= this.ConditionChangedForward; diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs index 40e632113..a0e16f0e2 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -21,7 +21,7 @@ namespace Dalamud.Game.ClientState.GamePad; #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState +internal unsafe class GamepadState : IInternalDisposableService, IGamepadState { private readonly Hook? gamepadPoll; @@ -109,7 +109,7 @@ internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState /// /// Disposes this instance, alongside its hooks. /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.Dispose(true); GC.SuppressFinalize(this); diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index 6b67f1892..7dcca763b 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -19,7 +19,7 @@ namespace Dalamud.Game.Command; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed class CommandManager : IServiceType, IDisposable, ICommandManager +internal sealed class CommandManager : IInternalDisposableService, ICommandManager { private static readonly ModuleLog Log = new("Command"); @@ -130,7 +130,7 @@ internal sealed class CommandManager : IServiceType, IDisposable, ICommandManage } /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled; } @@ -170,7 +170,7 @@ internal sealed class CommandManager : IServiceType, IDisposable, ICommandManage #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class CommandManagerPluginScoped : IDisposable, IServiceType, ICommandManager +internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager { private static readonly ModuleLog Log = new("Command"); @@ -193,7 +193,7 @@ internal class CommandManagerPluginScoped : IDisposable, IServiceType, ICommandM public ReadOnlyDictionary Commands => this.commandManagerService.Commands; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { foreach (var command in this.pluginRegisteredCommands) { diff --git a/Dalamud/Game/Config/GameConfig.cs b/Dalamud/Game/Config/GameConfig.cs index 162df9417..a021025b1 100644 --- a/Dalamud/Game/Config/GameConfig.cs +++ b/Dalamud/Game/Config/GameConfig.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game.Config; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable +internal sealed class GameConfig : IInternalDisposableService, IGameConfig { private readonly TaskCompletionSource tcsInitialization = new(); private readonly TaskCompletionSource tcsSystem = new(); @@ -195,7 +195,7 @@ internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable public void Set(UiControlOption option, string value) => this.UiControl.Set(option.GetName(), value); /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { var ode = new ObjectDisposedException(nameof(GameConfig)); this.tcsInitialization.SetExceptionIfIncomplete(ode); @@ -248,7 +248,7 @@ internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class GameConfigPluginScoped : IDisposable, IServiceType, IGameConfig +internal class GameConfigPluginScoped : IInternalDisposableService, IGameConfig { [ServiceManager.ServiceDependency] private readonly GameConfig gameConfigService = Service.Get(); @@ -295,7 +295,7 @@ internal class GameConfigPluginScoped : IDisposable, IServiceType, IGameConfig public GameConfigSection UiControl => this.gameConfigService.UiControl; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.gameConfigService.Changed -= this.ConfigChangedForward; this.initializationTask.ContinueWith( diff --git a/Dalamud/Game/DutyState/DutyState.cs b/Dalamud/Game/DutyState/DutyState.cs index c4bda0d19..e2e4aef15 100644 --- a/Dalamud/Game/DutyState/DutyState.cs +++ b/Dalamud/Game/DutyState/DutyState.cs @@ -13,7 +13,7 @@ namespace Dalamud.Game.DutyState; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal unsafe class DutyState : IDisposable, IServiceType, IDutyState +internal unsafe class DutyState : IInternalDisposableService, IDutyState { private readonly DutyStateAddressResolver address; private readonly Hook contentDirectorNetworkMessageHook; @@ -62,7 +62,7 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState private bool CompletedThisTerritory { get; set; } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.contentDirectorNetworkMessageHook.Dispose(); this.framework.Update -= this.FrameworkOnUpdateEvent; @@ -168,7 +168,7 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class DutyStatePluginScoped : IDisposable, IServiceType, IDutyState +internal class DutyStatePluginScoped : IInternalDisposableService, IDutyState { [ServiceManager.ServiceDependency] private readonly DutyState dutyStateService = Service.Get(); @@ -200,7 +200,7 @@ internal class DutyStatePluginScoped : IDisposable, IServiceType, IDutyState public bool IsDutyStarted => this.dutyStateService.IsDutyStarted; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.dutyStateService.DutyStarted -= this.DutyStartedForward; this.dutyStateService.DutyWiped -= this.DutyWipedForward; diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index 6520ca5c8..252a02031 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -23,7 +23,7 @@ namespace Dalamud.Game; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed class Framework : IDisposable, IServiceType, IFramework +internal sealed class Framework : IInternalDisposableService, IFramework { private static readonly ModuleLog Log = new("Framework"); @@ -274,7 +274,7 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework /// /// Dispose of managed and unmanaged resources. /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.RunOnFrameworkThread(() => { @@ -469,7 +469,7 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class FrameworkPluginScoped : IDisposable, IServiceType, IFramework +internal class FrameworkPluginScoped : IInternalDisposableService, IFramework { [ServiceManager.ServiceDependency] private readonly Framework frameworkService = Service.Get(); @@ -504,7 +504,7 @@ internal class FrameworkPluginScoped : IDisposable, IServiceType, IFramework public bool IsFrameworkUnloading => this.frameworkService.IsFrameworkUnloading; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.frameworkService.Update -= this.OnUpdateForward; diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 02b52ee56..e0b90b382 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -29,7 +29,7 @@ namespace Dalamud.Game.Gui; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui +internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui { private static readonly ModuleLog Log = new("ChatGui"); @@ -109,7 +109,7 @@ internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui /// /// Dispose of managed and unmanaged resources. /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.printMessageHook.Dispose(); this.populateItemLinkHook.Dispose(); @@ -409,7 +409,7 @@ internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class ChatGuiPluginScoped : IDisposable, IServiceType, IChatGui +internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui { [ServiceManager.ServiceDependency] private readonly ChatGui chatGuiService = Service.Get(); @@ -447,7 +447,7 @@ internal class ChatGuiPluginScoped : IDisposable, IServiceType, IChatGui public IReadOnlyDictionary<(string PluginName, uint CommandId), Action> RegisteredLinkHandlers => this.chatGuiService.RegisteredLinkHandlers; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.chatGuiService.ChatMessage -= this.OnMessageForward; this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward; diff --git a/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs b/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs index 65c9b2760..f136d017a 100644 --- a/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs +++ b/Dalamud/Game/Gui/ContextMenu/ContextMenu.cs @@ -28,7 +28,7 @@ namespace Dalamud.Game.Gui.ContextMenu; /// [InterfaceVersion("1.0")] [ServiceManager.EarlyLoadedService] -internal sealed unsafe class ContextMenu : IDisposable, IServiceType, IContextMenu +internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextMenu { private static readonly ModuleLog Log = new("ContextMenu"); @@ -77,7 +77,7 @@ internal sealed unsafe class ContextMenu : IDisposable, IServiceType, IContextMe private IReadOnlyList? SubmenuItems { get; set; } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { var manager = RaptureAtkUnitManager.Instance(); var menu = manager->GetAddonByName("ContextMenu"); @@ -496,7 +496,7 @@ original: #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class ContextMenuPluginScoped : IDisposable, IServiceType, IContextMenu +internal class ContextMenuPluginScoped : IInternalDisposableService, IContextMenu { [ServiceManager.ServiceDependency] private readonly ContextMenu parentService = Service.Get(); @@ -514,7 +514,7 @@ internal class ContextMenuPluginScoped : IDisposable, IServiceType, IContextMenu private object MenuItemsLock { get; } = new(); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.parentService.OnMenuOpened -= this.OnMenuOpenedForward; diff --git a/Dalamud/Game/Gui/Dtr/DtrBar.cs b/Dalamud/Game/Gui/Dtr/DtrBar.cs index 993bb951f..dbf6fba3c 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBar.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBar.cs @@ -22,7 +22,7 @@ namespace Dalamud.Game.Gui.Dtr; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar +internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar { private const uint BaseNodeId = 1000; @@ -101,7 +101,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar } /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.addonLifecycle.UnregisterListener(this.dtrPostDrawListener); this.addonLifecycle.UnregisterListener(this.dtrPostRequestedUpdateListener); @@ -493,7 +493,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class DtrBarPluginScoped : IDisposable, IServiceType, IDtrBar +internal class DtrBarPluginScoped : IInternalDisposableService, IDtrBar { [ServiceManager.ServiceDependency] private readonly DtrBar dtrBarService = Service.Get(); @@ -501,7 +501,7 @@ internal class DtrBarPluginScoped : IDisposable, IServiceType, IDtrBar private readonly Dictionary pluginEntries = new(); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { foreach (var entry in this.pluginEntries) { diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index 2383b4e53..9310529e4 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -16,7 +16,7 @@ namespace Dalamud.Game.Gui.FlyText; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui +internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui { /// /// The native function responsible for adding fly text to the UI. See . @@ -78,7 +78,7 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui /// /// Disposes of managed and unmanaged resources. /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.createFlyTextHook.Dispose(); } @@ -277,7 +277,7 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class FlyTextGuiPluginScoped : IDisposable, IServiceType, IFlyTextGui +internal class FlyTextGuiPluginScoped : IInternalDisposableService, IFlyTextGui { [ServiceManager.ServiceDependency] private readonly FlyTextGui flyTextGuiService = Service.Get(); @@ -294,7 +294,7 @@ internal class FlyTextGuiPluginScoped : IDisposable, IServiceType, IFlyTextGui public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.flyTextGuiService.FlyTextCreated -= this.FlyTextCreatedForward; diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index a97e19a0a..9272aa824 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -27,7 +27,7 @@ namespace Dalamud.Game.Gui; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui +internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui { private static readonly ModuleLog Log = new("GameGui"); @@ -344,7 +344,7 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui /// /// Disables the hooks and submodules of this module. /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.setGlobalBgmHook.Dispose(); this.handleItemHoverHook.Dispose(); @@ -520,7 +520,7 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class GameGuiPluginScoped : IDisposable, IServiceType, IGameGui +internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui { [ServiceManager.ServiceDependency] private readonly GameGui gameGuiService = Service.Get(); @@ -558,7 +558,7 @@ internal class GameGuiPluginScoped : IDisposable, IServiceType, IGameGui public HoveredAction HoveredAction => this.gameGuiService.HoveredAction; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.gameGuiService.UiHideToggled -= this.UiHideToggledForward; this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward; diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs index 4a8332d24..f19fe3b0a 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game.Gui.PartyFinder; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGui +internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui { private readonly PartyFinderAddressResolver address; private readonly IntPtr memory; @@ -47,7 +47,7 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu /// /// Dispose of managed and unmanaged resources. /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.receiveListingHook.Dispose(); @@ -131,7 +131,7 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class PartyFinderGuiPluginScoped : IDisposable, IServiceType, IPartyFinderGui +internal class PartyFinderGuiPluginScoped : IInternalDisposableService, IPartyFinderGui { [ServiceManager.ServiceDependency] private readonly PartyFinderGui partyFinderGuiService = Service.Get(); @@ -148,7 +148,7 @@ internal class PartyFinderGuiPluginScoped : IDisposable, IServiceType, IPartyFin public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.partyFinderGuiService.ReceiveListing -= this.ReceiveListingForward; diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs index 7491b7f13..2cf327007 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -14,7 +14,7 @@ namespace Dalamud.Game.Gui.Toast; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui +internal sealed partial class ToastGui : IInternalDisposableService, IToastGui { private const uint QuestToastCheckmarkMagic = 60081; @@ -73,7 +73,7 @@ internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui /// /// Disposes of managed and unmanaged resources. /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.showNormalToastHook.Dispose(); this.showQuestToastHook.Dispose(); @@ -383,7 +383,7 @@ internal sealed partial class ToastGui #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class ToastGuiPluginScoped : IDisposable, IServiceType, IToastGui +internal class ToastGuiPluginScoped : IInternalDisposableService, IToastGui { [ServiceManager.ServiceDependency] private readonly ToastGui toastGuiService = Service.Get(); @@ -408,7 +408,7 @@ internal class ToastGuiPluginScoped : IDisposable, IServiceType, IToastGui public event IToastGui.OnErrorToastDelegate? ErrorToast; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.toastGuiService.Toast -= this.ToastForward; this.toastGuiService.QuestToast -= this.QuestToastForward; diff --git a/Dalamud/Game/Internal/AntiDebug.cs b/Dalamud/Game/Internal/AntiDebug.cs index 2f4ec28c0..5ab024012 100644 --- a/Dalamud/Game/Internal/AntiDebug.cs +++ b/Dalamud/Game/Internal/AntiDebug.cs @@ -12,7 +12,7 @@ namespace Dalamud.Game.Internal; /// This class disables anti-debug functionality in the game client. /// [ServiceManager.EarlyLoadedService] -internal sealed partial class AntiDebug : IServiceType +internal sealed class AntiDebug : IInternalDisposableService { private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; private byte[] original; @@ -43,16 +43,25 @@ internal sealed partial class AntiDebug : IServiceType } } + /// Finalizes an instance of the class. + ~AntiDebug() => this.Disable(); + /// /// Gets a value indicating whether the anti-debugging is enabled. /// public bool IsEnabled { get; private set; } = false; + /// + void IInternalDisposableService.DisposeService() => this.Disable(); + /// /// Enables the anti-debugging by overwriting code in memory. /// public void Enable() { + if (this.IsEnabled) + return; + this.original = new byte[this.nop.Length]; if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) { @@ -73,6 +82,9 @@ internal sealed partial class AntiDebug : IServiceType /// public void Disable() { + if (!this.IsEnabled) + return; + if (this.debugCheckAddress != IntPtr.Zero && this.original != null) { Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); @@ -86,45 +98,3 @@ internal sealed partial class AntiDebug : IServiceType this.IsEnabled = false; } } - -/// -/// Implementing IDisposable. -/// -internal sealed partial class AntiDebug : IDisposable -{ - private bool disposed = false; - - /// - /// Finalizes an instance of the class. - /// - ~AntiDebug() => this.Dispose(false); - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of managed and unmanaged resources. - /// - /// If this was disposed through calling Dispose() or from being finalized. - private void Dispose(bool disposing) - { - if (this.disposed) - return; - - if (disposing) - { - // If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded. - // If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the - // check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it. - this.Disable(); - } - - this.disposed = true; - } -} diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 30fab6b1b..9f9328de1 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -20,7 +20,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 : IServiceType +internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService { private readonly AtkValueChangeType atkValueChangeType; private readonly AtkValueSetString atkValueSetString; @@ -40,6 +40,8 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType private readonly string locDalamudPlugins; private readonly string locDalamudSettings; + private bool disposed = false; + [ServiceManager.ServiceConstructor] private DalamudAtkTweaks(TargetSigScanner sigScanner) { @@ -69,6 +71,9 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); } + /// Finalizes an instance of the class. + ~DalamudAtkTweaks() => this.Dispose(false); + private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type); @@ -79,6 +84,26 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); + /// + void IInternalDisposableService.DisposeService() => this.Dispose(true); + + private void Dispose(bool disposing) + { + if (this.disposed) + return; + + if (disposing) + { + this.hookAgentHudOpenSystemMenu.Dispose(); + this.hookUiModuleRequestMainCommand.Dispose(); + this.hookAtkUnitBaseReceiveGlobalEvent.Dispose(); + + // this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened; + } + + this.disposed = true; + } + /* private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args) { @@ -229,45 +254,3 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType } } } - -/// -/// Implements IDisposable. -/// -internal sealed partial class DalamudAtkTweaks : IDisposable -{ - private bool disposed = false; - - /// - /// Finalizes an instance of the class. - /// - ~DalamudAtkTweaks() => this.Dispose(false); - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - private void Dispose(bool disposing) - { - if (this.disposed) - return; - - if (disposing) - { - this.hookAgentHudOpenSystemMenu.Dispose(); - this.hookUiModuleRequestMainCommand.Dispose(); - this.hookAtkUnitBaseReceiveGlobalEvent.Dispose(); - - // this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened; - } - - this.disposed = true; - } -} diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs index 1c7f3e3bf..3e3dbc685 100644 --- a/Dalamud/Game/Inventory/GameInventory.cs +++ b/Dalamud/Game/Inventory/GameInventory.cs @@ -19,7 +19,7 @@ namespace Dalamud.Game.Inventory; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal class GameInventory : IDisposable, IServiceType +internal class GameInventory : IInternalDisposableService { private readonly List subscribersPendingChange = new(); private readonly List subscribers = new(); @@ -61,7 +61,7 @@ internal class GameInventory : IDisposable, IServiceType private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { lock (this.subscribersPendingChange) { @@ -351,7 +351,7 @@ internal class GameInventory : IDisposable, IServiceType #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInventory +internal class GameInventoryPluginScoped : IInternalDisposableService, IGameInventory { private static readonly ModuleLog Log = new(nameof(GameInventoryPluginScoped)); @@ -406,7 +406,7 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven public event IGameInventory.InventoryChangedDelegate? ItemMergedExplicit; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.gameInventoryService.Unsubscribe(this); diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index 4099f228e..954612af7 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game.Network; /// [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork +internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork { private readonly GameNetworkAddressResolver address; private readonly Hook processZonePacketDownHook; @@ -59,7 +59,7 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage; /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.processZonePacketDownHook.Dispose(); this.processZonePacketUpHook.Dispose(); @@ -145,7 +145,7 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class GameNetworkPluginScoped : IDisposable, IServiceType, IGameNetwork +internal class GameNetworkPluginScoped : IInternalDisposableService, IGameNetwork { [ServiceManager.ServiceDependency] private readonly GameNetwork gameNetworkService = Service.Get(); @@ -162,7 +162,7 @@ internal class GameNetworkPluginScoped : IDisposable, IServiceType, IGameNetwork public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.gameNetworkService.NetworkMessage -= this.NetworkMessageForward; diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 8d5ec1344..2a46af3d3 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -26,7 +26,7 @@ namespace Dalamud.Game.Network.Internal; /// This class handles network notifications and uploading market board data. /// [ServiceManager.BlockingEarlyLoadedService] -internal unsafe class NetworkHandlers : IDisposable, IServiceType +internal unsafe class NetworkHandlers : IInternalDisposableService { private readonly IMarketBoardUploader uploader; @@ -213,7 +213,7 @@ internal unsafe class NetworkHandlers : IDisposable, IServiceType /// /// Disposes of managed and unmanaged resources. /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.disposing = true; this.Dispose(this.disposing); diff --git a/Dalamud/Game/Network/Internal/WinSockHandlers.cs b/Dalamud/Game/Network/Internal/WinSockHandlers.cs index 8439389ff..619c458c4 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, IServiceType +internal sealed class WinSockHandlers : IInternalDisposableService { private Hook ws2SocketHook; @@ -27,7 +27,7 @@ internal sealed class WinSockHandlers : IDisposable, IServiceType /// /// Disposes of managed and unmanaged resources. /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.ws2SocketHook?.Dispose(); } diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index fe2d9083e..5e49052ae 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -104,6 +104,10 @@ public class SigScanner : IDisposable, ISigScanner /// public ProcessModule Module { get; } + /// Gets or sets a value indicating whether this instance of is meant to be a + /// Dalamud service. + private protected bool IsService { get; set; } + private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize; /// @@ -309,13 +313,11 @@ public class SigScanner : IDisposable, ISigScanner } } - /// - /// Free the memory of the copied module search area on object disposal, if applicable. - /// + /// public void Dispose() { - this.Save(); - Marshal.FreeHGlobal(this.moduleCopyPtr); + if (!this.IsService) + this.DisposeCore(); } /// @@ -337,6 +339,15 @@ public class SigScanner : IDisposable, ISigScanner } } + /// + /// Free the memory of the copied module search area on object disposal, if applicable. + /// + private protected void DisposeCore() + { + this.Save(); + Marshal.FreeHGlobal(this.moduleCopyPtr); + } + /// /// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location. /// diff --git a/Dalamud/Game/TargetSigScanner.cs b/Dalamud/Game/TargetSigScanner.cs index 35c82562e..e169ea904 100644 --- a/Dalamud/Game/TargetSigScanner.cs +++ b/Dalamud/Game/TargetSigScanner.cs @@ -15,7 +15,7 @@ namespace Dalamud.Game; #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class TargetSigScanner : SigScanner, IServiceType +internal class TargetSigScanner : SigScanner, IPublicDisposableService { /// /// Initializes a new instance of the class. @@ -26,4 +26,14 @@ internal class TargetSigScanner : SigScanner, IServiceType : base(Process.GetCurrentProcess().MainModule!, doCopy, cacheFile) { } + + /// + void IInternalDisposableService.DisposeService() + { + if (this.IsService) + this.DisposeCore(); + } + + /// + void IPublicDisposableService.MarkDisposeOnlyFromService() => this.IsService = true; } diff --git a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs index 9958385b9..1138d4e07 100644 --- a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs +++ b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs @@ -21,7 +21,7 @@ namespace Dalamud.Hooking.Internal; #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class GameInteropProviderPluginScoped : IGameInteropProvider, IServiceType, IDisposable +internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternalDisposableService { private readonly LocalPlugin plugin; private readonly SigScanner scanner; @@ -83,7 +83,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IServiceT => this.HookFromAddress(this.scanner.ScanText(signature), detour, backend); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { var notDisposed = this.trackedHooks.Where(x => !x.IsDisposed).ToArray(); if (notDisposed.Length != 0) diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs index 9c288a276..c8cdf3a46 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, IServiceType +internal class HookManager : IInternalDisposableService { /// /// Logger shared with . @@ -74,7 +74,7 @@ internal class HookManager : IDisposable, IServiceType } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { RevertHooks(); TrackedHooks.Clear(); diff --git a/Dalamud/Hooking/WndProcHook/WndProcHookManager.cs b/Dalamud/Hooking/WndProcHook/WndProcHookManager.cs index 91020f898..a2253eb23 100644 --- a/Dalamud/Hooking/WndProcHook/WndProcHookManager.cs +++ b/Dalamud/Hooking/WndProcHook/WndProcHookManager.cs @@ -15,7 +15,7 @@ namespace Dalamud.Hooking.WndProcHook; /// Manages WndProc hooks for game main window and extra ImGui viewport windows. /// [ServiceManager.BlockingEarlyLoadedService] -internal sealed class WndProcHookManager : IServiceType, IDisposable +internal sealed class WndProcHookManager : IInternalDisposableService { private static readonly ModuleLog Log = new(nameof(WndProcHookManager)); @@ -56,7 +56,7 @@ internal sealed class WndProcHookManager : IServiceType, IDisposable public event WndProcEventDelegate? PostWndProc; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { if (this.dispatchMessageWHook.IsDisposed) return; diff --git a/Dalamud/IServiceType.cs b/Dalamud/IServiceType.cs index 973795faf..3a5dde880 100644 --- a/Dalamud/IServiceType.cs +++ b/Dalamud/IServiceType.cs @@ -6,3 +6,20 @@ public interface IServiceType { } + +/// , but for . +/// Use this to prevent services from accidentally being disposed by plugins or using clauses. +internal interface IInternalDisposableService : IServiceType +{ + /// Disposes the service. + void DisposeService(); +} + +/// An which happens to be public and needs to expose +/// . +internal interface IPublicDisposableService : IInternalDisposableService, IDisposable +{ + /// Marks that only should respond, + /// while suppressing . + void MarkDisposeOnlyFromService(); +} diff --git a/Dalamud/Interface/DragDrop/DragDropManager.cs b/Dalamud/Interface/DragDrop/DragDropManager.cs index 151ef28a0..adc0ebff7 100644 --- a/Dalamud/Interface/DragDrop/DragDropManager.cs +++ b/Dalamud/Interface/DragDrop/DragDropManager.cs @@ -19,7 +19,7 @@ namespace Dalamud.Interface.DragDrop; #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType +internal partial class DragDropManager : IInternalDisposableService, IDragDropManager { private nint windowHandlePtr = nint.Zero; @@ -56,6 +56,9 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService /// Gets the list of directory paths currently being dragged from an external application over any FFXIV-related viewport or stored from the last drop. public IReadOnlyList Directories { get; private set; } = Array.Empty(); + /// + void IInternalDisposableService.DisposeService() => this.Disable(); + /// Enable external drag and drop. public void Enable() { @@ -99,10 +102,6 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService this.ServiceAvailable = false; } - /// - public void Dispose() - => this.Disable(); - /// public void CreateImGuiSource(string label, Func validityCheck, Func tooltipBuilder) { diff --git a/Dalamud/Interface/ImGuiFontChooserDialog/SingleFontChooserDialog.cs b/Dalamud/Interface/ImGuiFontChooserDialog/SingleFontChooserDialog.cs index 9420fe42c..7636f22b6 100644 --- a/Dalamud/Interface/ImGuiFontChooserDialog/SingleFontChooserDialog.cs +++ b/Dalamud/Interface/ImGuiFontChooserDialog/SingleFontChooserDialog.cs @@ -93,7 +93,7 @@ public sealed class SingleFontChooserDialog : IDisposable /// Initializes a new instance of the class. /// A new instance of created using /// as its auto-rebuild mode. - /// The passed instance of will be disposed after use. If you pass an atlas + /// The passed instance of will be disposed after use. If you pass an atlas /// that is already being used, then all the font handles under the passed atlas will be invalidated upon disposing /// this font chooser. Consider using for automatic /// handling of font atlas derived from a , or even for automatic diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index caf014885..64040011e 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -34,7 +34,7 @@ namespace Dalamud.Interface.Internal; /// This class handles CJK IME. /// [ServiceManager.EarlyLoadedService] -internal sealed unsafe class DalamudIme : IDisposable, IServiceType +internal sealed unsafe class DalamudIme : IInternalDisposableService { private const int CImGuiStbTextCreateUndoOffset = 0xB57A0; private const int CImGuiStbTextUndoOffset = 0xB59C0; @@ -200,7 +200,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType this.candidateStrings.Count != 0 || this.ShowPartialConversion || this.inputModeIcon != default; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.interfaceManager.Draw -= this.Draw; this.ReleaseUnmanagedResources(); diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 1a07cd6ae..ec18fbb69 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -46,7 +46,7 @@ namespace Dalamud.Interface.Internal; /// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. /// [ServiceManager.EarlyLoadedService] -internal class DalamudInterface : IDisposable, IServiceType +internal class DalamudInterface : IInternalDisposableService { private const float CreditsDarkeningMaxAlpha = 0.8f; @@ -209,7 +209,7 @@ internal class DalamudInterface : IDisposable, IServiceType } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.interfaceManager.Draw -= this.OnDraw; diff --git a/Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs b/Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs index bbf665405..9fa21a31b 100644 --- a/Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs +++ b/Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs @@ -35,7 +35,7 @@ namespace Dalamud.Interface.Internal; /// /// [ServiceManager.EarlyLoadedService] -internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDisposable +internal sealed unsafe class ImGuiClipboardFunctionProvider : IInternalDisposableService { private static readonly ModuleLog Log = new(nameof(ImGuiClipboardFunctionProvider)); private readonly nint clipboardUserDataOriginal; @@ -75,7 +75,7 @@ internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDis } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { if (!this.clipboardUserData.IsAllocated) return; diff --git a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs index f2d6ed244..139dd96e2 100644 --- a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs +++ b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs @@ -24,7 +24,7 @@ namespace Dalamud.Interface.Internal; /// Change push_texture_id to only have one condition. /// [ServiceManager.EarlyLoadedService] -internal sealed unsafe class ImGuiDrawListFixProvider : IServiceType, IDisposable +internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableService { private const int CImGuiImDrawListAddPolyLineOffset = 0x589B0; private const int CImGuiImDrawListAddRectFilled = 0x59FD0; @@ -69,7 +69,7 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IServiceType, IDisposabl ImDrawFlags flags); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.hookImDrawListAddPolyline.Dispose(); this.hookImDrawListAddRectFilled.Dispose(); diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 48ad653d2..be14b882b 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using Dalamud.Configuration.Internal; @@ -51,7 +52,7 @@ namespace Dalamud.Interface.Internal; /// This class manages interaction with the ImGui interface. /// [ServiceManager.BlockingEarlyLoadedService] -internal class InterfaceManager : IDisposable, IServiceType +internal class InterfaceManager : IInternalDisposableService { /// /// The default font size, in points. @@ -69,10 +70,13 @@ internal class InterfaceManager : IDisposable, IServiceType [ServiceManager.ServiceDependency] private readonly WndProcHookManager wndProcHookManager = Service.Get(); + [ServiceManager.ServiceDependency] + private readonly Framework framework = Service.Get(); + private readonly SwapChainVtableResolver address = new(); - private readonly Hook setCursorHook; private RawDX11Scene? scene; + private Hook? setCursorHook; private Hook? presentHook; private Hook? resizeBuffersHook; @@ -87,8 +91,6 @@ internal class InterfaceManager : IDisposable, IServiceType [ServiceManager.ServiceConstructor] private InterfaceManager() { - this.setCursorHook = Hook.FromImport( - null, "user32.dll", "SetCursor", 0, this.SetCursorDetour); } [UnmanagedFunctionPointer(CallingConvention.ThisCall)] @@ -233,25 +235,45 @@ internal class InterfaceManager : IDisposable, IServiceType /// /// Dispose of managed and unmanaged resources. /// - public void Dispose() + void IInternalDisposableService.DisposeService() { - if (Service.GetNullable() is { } framework) - framework.RunOnFrameworkThread(Disposer).Wait(); - else - Disposer(); + // Unload hooks from the framework thread if possible. + // We're currently off the framework thread, as this function can only be called from + // ServiceManager.UnloadAllServices, which is called from EntryPoint.RunThread. + // The functions being unhooked are mostly called from the main thread, so unhooking from the main thread when + // possible would avoid any chance of unhooking a function that currently is being called. + // If unloading is initiated from "Unload Dalamud" /xldev menu, then the framework would still be running, as + // Framework.Destroy has never been called and thus Framework.IsFrameworkUnloading cannot be true, and this + // function will actually run the destroy from the framework thread. + // Otherwise, as Framework.IsFrameworkUnloading should have been set, this code should run immediately. + this.framework.RunOnFrameworkThread(ClearHooks).Wait(); + + // Below this point, hooks are guaranteed to be no longer called. + + // A font resource lock outlives the parent handle and the owner atlas. It should be disposed. + Interlocked.Exchange(ref this.defaultFontResourceLock, null)?.Dispose(); + + // Font handles become invalid after disposing the atlas, but just to be safe. + this.DefaultFontHandle?.Dispose(); + this.DefaultFontHandle = null; + + this.MonoFontHandle?.Dispose(); + this.MonoFontHandle = null; + + this.IconFontHandle?.Dispose(); + this.IconFontHandle = null; + + Interlocked.Exchange(ref this.dalamudAtlas, null)?.Dispose(); + Interlocked.Exchange(ref this.scene, null)?.Dispose(); - this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc; - this.defaultFontResourceLock?.Dispose(); // lock outlives handle and atlas - this.defaultFontResourceLock = null; - this.dalamudAtlas?.Dispose(); - this.scene?.Dispose(); return; - void Disposer() + void ClearHooks() { - this.setCursorHook.Dispose(); - this.presentHook?.Dispose(); - this.resizeBuffersHook?.Dispose(); + this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc; + Interlocked.Exchange(ref this.setCursorHook, null)?.Dispose(); + Interlocked.Exchange(ref this.presentHook, null)?.Dispose(); + Interlocked.Exchange(ref this.resizeBuffersHook, null)?.Dispose(); } } @@ -693,7 +715,6 @@ internal class InterfaceManager : IDisposable, IServiceType "InterfaceManager accepts event registration and stuff even when the game window is not ready.")] private void ContinueConstruction( TargetSigScanner sigScanner, - Framework framework, FontAtlasFactory fontAtlasFactory) { this.dalamudAtlas = fontAtlasFactory @@ -731,7 +752,7 @@ internal class InterfaceManager : IDisposable, IServiceType this.DefaultFontHandle.ImFontChanged += (_, font) => { var fontLocked = font.NewRef(); - Service.Get().RunOnFrameworkThread( + this.framework.RunOnFrameworkThread( () => { // Update the ImGui default font. @@ -765,6 +786,7 @@ internal class InterfaceManager : IDisposable, IServiceType Log.Error(ex, "Could not enable immersive mode"); } + this.setCursorHook = Hook.FromImport(null, "user32.dll", "SetCursor", 0, this.SetCursorDetour); this.presentHook = Hook.FromAddress(this.address.Present, this.PresentDetour); this.resizeBuffersHook = Hook.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour); @@ -808,7 +830,7 @@ internal class InterfaceManager : IDisposable, IServiceType if (this.lastWantCapture && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) return IntPtr.Zero; - return this.setCursorHook.IsDisposed + return this.setCursorHook?.IsDisposed is not false ? User32.SetCursor(new(hCursor, false)).DangerousGetHandle() : this.setCursorHook.Original(hCursor); } diff --git a/Dalamud/Interface/Internal/TextureManager.cs b/Dalamud/Interface/Internal/TextureManager.cs index 9f90ea1ad..74ce91e5e 100644 --- a/Dalamud/Interface/Internal/TextureManager.cs +++ b/Dalamud/Interface/Internal/TextureManager.cs @@ -27,7 +27,7 @@ namespace Dalamud.Interface.Internal; [ResolveVia] [ResolveVia] #pragma warning restore SA1015 -internal class TextureManager : IDisposable, IServiceType, ITextureProvider, ITextureSubstitutionProvider +internal class TextureManager : IInternalDisposableService, ITextureProvider, ITextureSubstitutionProvider { private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; private const string HighResolutionIconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}_hr1.tex"; @@ -268,7 +268,7 @@ internal class TextureManager : IDisposable, IServiceType, ITextureProvider, ITe } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.fallbackTextureWrap?.Dispose(); this.framework.Update -= this.FrameworkOnUpdate; diff --git a/Dalamud/Interface/Internal/Windows/Data/GameInventoryTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/GameInventoryTestWidget.cs index c19f56654..5cede00cf 100644 --- a/Dalamud/Interface/Internal/Windows/Data/GameInventoryTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/GameInventoryTestWidget.cs @@ -74,7 +74,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget this.standardEnabled = false; if (!this.rawEnabled) { - this.scoped.Dispose(); + ((IInternalDisposableService)this.scoped).DisposeService(); this.scoped = null; } } @@ -105,7 +105,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget this.rawEnabled = false; if (!this.standardEnabled) { - this.scoped.Dispose(); + ((IInternalDisposableService)this.scoped).DisposeService(); this.scoped = null; } } @@ -135,7 +135,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget { if (ImGui.Button("Disable##all-disable")) { - this.scoped?.Dispose(); + ((IInternalDisposableService)this.scoped)?.DisposeService(); this.scoped = null; this.standardEnabled = this.rawEnabled = false; } diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index 29adbb3e5..97744b1a7 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -21,7 +21,7 @@ namespace Dalamud.Interface.Internal.Windows; /// A cache for plugin icons and images. /// [ServiceManager.EarlyLoadedService] -internal class PluginImageCache : IDisposable, IServiceType +internal class PluginImageCache : IInternalDisposableService { /// /// Maximum plugin image width. @@ -136,7 +136,7 @@ internal class PluginImageCache : IDisposable, IServiceType this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall, this.EmptyTexture); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.cancelToken.Cancel(); this.downloadQueue.CompleteAdding(); diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs index 883fcbbfc..b3d330075 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.Implementation.cs @@ -204,12 +204,12 @@ internal sealed partial class FontAtlasFactory { while (this.IsBuildInProgress) await Task.Delay(100); - this.Garbage.Dispose(); + this.Clear(); }); } else { - this.Garbage.Dispose(); + this.Clear(); } return newRefCount; @@ -227,6 +227,20 @@ internal sealed partial class FontAtlasFactory var axisSubstance = this.Substances.OfType().Single(); return new(factory, this, axisSubstance, isAsync) { BuildStep = FontAtlasBuildStep.PreBuild }; } + + public void Clear() + { + try + { + this.Garbage.Dispose(); + } + catch (Exception e) + { + Log.Error( + e, + $"Disposing {nameof(FontAtlasBuiltData)} of {this.Owner?.Name ?? "???"}."); + } + } } private class DalamudFontAtlas : IFontAtlas, DisposeSafety.IDisposeCallback @@ -547,13 +561,13 @@ internal sealed partial class FontAtlasFactory { if (this.buildIndex != rebuildIndex) { - data.ExplicitDisposeIgnoreExceptions(); + data.Release(); return; } var prevBuiltData = this.builtData; this.builtData = data; - prevBuiltData.ExplicitDisposeIgnoreExceptions(); + prevBuiltData?.Release(); this.buildTask = EmptyTask; fontsAndLocks.EnsureCapacity(data.Substances.Sum(x => x.RelevantHandles.Count)); diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs index 3e0fd1394..7fa41487a 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontAtlasFactory.cs @@ -31,7 +31,7 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals; /// [ServiceManager.BlockingEarlyLoadedService] internal sealed partial class FontAtlasFactory - : IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable + : IInternalDisposableService, GamePrebakedFontHandle.IGameFontTextureProvider { private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new(); private readonly CancellationTokenSource cancellationTokenSource = new(); @@ -161,7 +161,7 @@ internal sealed partial class FontAtlasFactory this.dalamudAssetManager.IsStreamImmediatelyAvailable(DalamudAsset.LodestoneGameSymbol); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.cancellationTokenSource.Cancel(); this.scopedFinalizer.Dispose(); diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/FontHandle.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/FontHandle.cs index 15e2803da..0e26145f0 100644 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/FontHandle.cs +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/FontHandle.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using Dalamud.Interface.Internal; @@ -291,11 +292,15 @@ internal abstract class FontHandle : IFontHandle { if (disposing) { + if (Interlocked.Exchange(ref this.manager, null) is not { } managerToDisassociate) + return; + if (this.pushedFonts.Count > 0) Log.Warning($"{nameof(IFontHandle)}.{nameof(IDisposable.Dispose)}: fonts were still in a stack."); - this.Manager.FreeFontHandle(this); - this.manager = null; + + managerToDisassociate.FreeFontHandle(this); this.Disposed?.InvokeSafely(); + this.Disposed = null; this.ImFontChanged = null; } } diff --git a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs index 1f9a5bc76..6fbc0b4f3 100644 --- a/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs +++ b/Dalamud/Interface/TitleScreenMenu/TitleScreenMenu.cs @@ -193,7 +193,7 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class TitleScreenMenuPluginScoped : IDisposable, IServiceType, ITitleScreenMenu +internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleScreenMenu { [ServiceManager.ServiceDependency] private readonly TitleScreenMenu titleScreenMenuService = Service.Get(); @@ -204,7 +204,7 @@ internal class TitleScreenMenuPluginScoped : IDisposable, IServiceType, ITitleSc public IReadOnlyList? Entries => this.titleScreenMenuService.Entries; /// - public void Dispose() + void IInternalDisposableService.DisposeService() { foreach (var entry in this.pluginEntries) { diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 2053d9354..03132a530 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -17,6 +17,7 @@ using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas.Internals; +using Dalamud.Plugin; using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -605,6 +606,10 @@ public sealed class UiBuilder : IDisposable } } + /// Clean up resources allocated by this instance of . + /// Dalamud internal use only. + internal void DisposeInternal() => this.scopedFinalizer.Dispose(); + /// /// Open the registered configuration UI, if it exists. /// diff --git a/Dalamud/IoC/Internal/ServiceScope.cs b/Dalamud/IoC/Internal/ServiceScope.cs index 01c18a8b2..9fcf1af3c 100644 --- a/Dalamud/IoC/Internal/ServiceScope.cs +++ b/Dalamud/IoC/Internal/ServiceScope.cs @@ -96,6 +96,17 @@ internal class ServiceScopeImpl : IServiceScope /// public void Dispose() { - foreach (var createdObject in this.scopeCreatedObjects.OfType()) createdObject.Dispose(); + foreach (var createdObject in this.scopeCreatedObjects) + { + switch (createdObject) + { + case IInternalDisposableService d: + d.DisposeService(); + break; + case IDisposable d: + d.Dispose(); + break; + } + } } } diff --git a/Dalamud/Logging/Internal/TaskTracker.cs b/Dalamud/Logging/Internal/TaskTracker.cs index b65f0efa7..9ecabe6c7 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, IServiceType +internal class TaskTracker : IInternalDisposableService { private static readonly ModuleLog Log = new("TT"); private static readonly List TrackedTasksInternal = new(); @@ -119,7 +119,7 @@ internal class TaskTracker : IDisposable, IServiceType } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.scheduleAndStartHook?.Dispose(); diff --git a/Dalamud/Logging/ScopedPluginLogService.cs b/Dalamud/Logging/ScopedPluginLogService.cs index 924b4885d..0c044f2c2 100644 --- a/Dalamud/Logging/ScopedPluginLogService.cs +++ b/Dalamud/Logging/ScopedPluginLogService.cs @@ -17,7 +17,7 @@ namespace Dalamud.Logging; #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal class ScopedPluginLogService : IServiceType, IPluginLog, IDisposable +internal class ScopedPluginLogService : IServiceType, IPluginLog { private readonly LocalPlugin localPlugin; @@ -53,12 +53,6 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog, IDisposable /// public ILogger Logger { get; } - /// - public void Dispose() - { - GC.SuppressFinalize(this); - } - /// public void Fatal(string messageTemplate, params object[] values) => this.Write(LogEventLevel.Fatal, null, messageTemplate, values); diff --git a/Dalamud/Networking/Http/HappyHttpClient.cs b/Dalamud/Networking/Http/HappyHttpClient.cs index 4379a698f..23c6e3899 100644 --- a/Dalamud/Networking/Http/HappyHttpClient.cs +++ b/Dalamud/Networking/Http/HappyHttpClient.cs @@ -12,7 +12,7 @@ namespace Dalamud.Networking.Http; /// awareness. /// [ServiceManager.BlockingEarlyLoadedService] -internal class HappyHttpClient : IDisposable, IServiceType +internal class HappyHttpClient : IInternalDisposableService { /// /// Initializes a new instance of the class. @@ -58,7 +58,7 @@ internal class HappyHttpClient : IDisposable, IServiceType public HappyEyeballsCallback SharedHappyEyeballsCallback { get; } /// - void IDisposable.Dispose() + void IInternalDisposableService.DisposeService() { this.SharedHttpClient.Dispose(); this.SharedHappyEyeballsCallback.Dispose(); diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index 5e103ecbe..135cf89ea 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -452,26 +452,28 @@ public sealed class DalamudPluginInterface : IDisposable #endregion - /// - /// Unregister your plugin and dispose all references. - /// + /// void IDisposable.Dispose() { - this.UiBuilder.ExplicitDispose(); - Service.Get().RemoveChatLinkHandler(this.plugin.InternalName); - Service.Get().LocalizationChanged -= this.OnLocalizationChanged; - Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; } - /// - /// Obsolete implicit dispose implementation. Should not be used. - /// - [Obsolete("Do not dispose \"DalamudPluginInterface\".", true)] + /// This function will do nothing. Dalamud will dispose this object on plugin unload. + [Obsolete("This function will do nothing. Dalamud will dispose this object on plugin unload.", true)] public void Dispose() { // ignored } + /// Unregister the plugin and dispose all references. + /// Dalamud internal use only. + internal void DisposeInternal() + { + Service.Get().RemoveChatLinkHandler(this.plugin.InternalName); + Service.Get().LocalizationChanged -= this.OnLocalizationChanged; + Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; + this.UiBuilder.DisposeInternal(); + } + /// /// Dispatch the active plugins changed event. /// diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 6bdf73036..b815ac036 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -55,7 +55,7 @@ namespace Dalamud.Plugin.Internal; [InherentDependency] #pragma warning restore SA1015 -internal partial class PluginManager : IDisposable, IServiceType +internal partial class PluginManager : IInternalDisposableService { /// /// Default time to wait between plugin unload and plugin assembly unload. @@ -370,7 +370,7 @@ internal partial class PluginManager : IDisposable, IServiceType } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { var disposablePlugins = this.installedPluginsList.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray(); @@ -410,7 +410,16 @@ internal partial class PluginManager : IDisposable, IServiceType // Now that we've waited enough, dispose the whole plugin. // Since plugins should have been unloaded above, this should be done quickly. foreach (var plugin in disposablePlugins) - plugin.ExplicitDisposeIgnoreExceptions($"Error disposing {plugin.Name}", Log); + { + try + { + plugin.Dispose(); + } + catch (Exception e) + { + Log.Error(e, $"Error disposing {plugin.Name}"); + } + } } this.assemblyLocationMonoHook?.Dispose(); diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs b/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs index 7001e4d7b..eebb87aaa 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfileCommandHandler.cs @@ -16,7 +16,7 @@ namespace Dalamud.Plugin.Internal.Profiles; /// Service responsible for profile-related chat commands. /// [ServiceManager.EarlyLoadedService] -internal class ProfileCommandHandler : IServiceType, IDisposable +internal class ProfileCommandHandler : IInternalDisposableService { private readonly CommandManager cmd; private readonly ProfileManager profileManager; @@ -69,7 +69,7 @@ internal class ProfileCommandHandler : IServiceType, IDisposable } /// - public void Dispose() + void IInternalDisposableService.DisposeService() { this.cmd.RemoveHandler("/xlenablecollection"); this.cmd.RemoveHandler("/xldisablecollection"); diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index 0f65bafb2..911bc436d 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -240,7 +240,7 @@ internal class LocalPlugin : IDisposable this.instance = null; } - this.DalamudInterface?.ExplicitDispose(); + this.DalamudInterface?.DisposeInternal(); this.DalamudInterface = null; this.ServiceScope?.Dispose(); @@ -426,7 +426,7 @@ internal class LocalPlugin : IDisposable if (this.instance == null) { this.State = PluginState.LoadError; - this.DalamudInterface.ExplicitDispose(); + this.DalamudInterface.DisposeInternal(); Log.Error( $"Error while loading {this.Name}, failed to bind and call the plugin constructor"); return; @@ -499,7 +499,7 @@ internal class LocalPlugin : IDisposable this.instance = null; - this.DalamudInterface?.ExplicitDispose(); + this.DalamudInterface?.DisposeInternal(); this.DalamudInterface = null; this.ServiceScope?.Dispose(); diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs index acd7c2b6f..845a65d6e 100644 --- a/Dalamud/ServiceManager.cs +++ b/Dalamud/ServiceManager.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -175,7 +176,8 @@ internal static class ServiceManager foreach (var serviceType in GetConcreteServiceTypes()) { var serviceKind = serviceType.GetServiceKind(); - Debug.Assert(serviceKind != ServiceKind.None, $"Service<{serviceType.FullName}> did not specify a kind"); + + CheckServiceTypeContracts(serviceType); // Let IoC know about the interfaces this service implements serviceContainer.RegisterInterfaces(serviceType); @@ -514,6 +516,44 @@ internal static class ServiceManager return ServiceKind.ProvidedService; } + /// Validate service type contracts, and throws exceptions accordingly. + /// An instance of that is supposed to be a service type. + /// Does nothing on non-debug builds. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckServiceTypeContracts(Type serviceType) + { +#if DEBUG + try + { + if (!serviceType.IsAssignableTo(typeof(IServiceType))) + throw new InvalidOperationException($"Non-{nameof(IServiceType)} passed."); + if (serviceType.GetServiceKind() == ServiceKind.None) + throw new InvalidOperationException("Service type is not specified."); + + var isServiceDisposable = + serviceType.IsAssignableTo(typeof(IInternalDisposableService)); + var isAnyDisposable = + isServiceDisposable + || serviceType.IsAssignableTo(typeof(IDisposable)) + || serviceType.IsAssignableTo(typeof(IAsyncDisposable)); + if (isAnyDisposable && !isServiceDisposable) + { + throw new InvalidOperationException( + $"A service must be an {nameof(IInternalDisposableService)} without specifying " + + $"{nameof(IDisposable)} nor {nameof(IAsyncDisposable)} if it is purely meant to be a service, " + + $"or an {nameof(IPublicDisposableService)} if it also is allowed to be constructed not as a " + + $"service to be used elsewhere and has to offer {nameof(IDisposable)} or " + + $"{nameof(IAsyncDisposable)}. See {nameof(ReliableFileStorage)} for an example of " + + $"{nameof(IPublicDisposableService)}."); + } + } + catch (Exception e) + { + throw new InvalidOperationException($"{serviceType.Name}: {e.Message}"); + } +#endif + } + /// /// Indicates that this constructor will be called for early initialization. /// diff --git a/Dalamud/Service{T}.cs b/Dalamud/Service{T}.cs index 08f592826..ed03749d5 100644 --- a/Dalamud/Service{T}.cs +++ b/Dalamud/Service{T}.cs @@ -65,6 +65,12 @@ internal static class Service where T : IServiceType None, } + /// Does nothing. + /// Used to invoke the static ctor. + public static void Nop() + { + } + /// /// Sets the type in the service locator to the given object. /// @@ -72,6 +78,8 @@ internal static class Service where T : IServiceType public static void Provide(T obj) { ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name); + if (obj is IPublicDisposableService pds) + pds.MarkDisposeOnlyFromService(); instanceTcs.SetResult(obj); } @@ -297,23 +305,26 @@ internal static class Service where T : IServiceType if (!instanceTcs.Task.IsCompletedSuccessfully) return; - var instance = instanceTcs.Task.Result; - if (instance is IDisposable disposable) + switch (instanceTcs.Task.Result) { - ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name); - try - { - disposable.Dispose(); - ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name); - } - catch (Exception e) - { - ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name); - } - } - else - { - ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name); + case IInternalDisposableService d: + ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name); + try + { + d.DisposeService(); + ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name); + } + catch (Exception e) + { + ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name); + } + + break; + + default: + ServiceManager.CheckServiceTypeContracts(typeof(T)); + ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name); + break; } instanceTcs = new TaskCompletionSource(); diff --git a/Dalamud/Storage/Assets/DalamudAssetManager.cs b/Dalamud/Storage/Assets/DalamudAssetManager.cs index 68be78352..4f53460fb 100644 --- a/Dalamud/Storage/Assets/DalamudAssetManager.cs +++ b/Dalamud/Storage/Assets/DalamudAssetManager.cs @@ -27,7 +27,7 @@ namespace Dalamud.Storage.Assets; #pragma warning disable SA1015 [ResolveVia] #pragma warning restore SA1015 -internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudAssetManager +internal sealed class DalamudAssetManager : IInternalDisposableService, IDalamudAssetManager { private const int DownloadAttemptCount = 10; private const int RenameAttemptCount = 10; @@ -67,7 +67,13 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA .Where(x => x.GetAttribute()?.Required is true) .Select(this.CreateStreamAsync) .Select(x => x.ToContentDisposedTask())) - .ContinueWith(_ => loadTimings.Dispose()), + .ContinueWith( + r => + { + loadTimings.Dispose(); + return r; + }) + .Unwrap(), "Prevent Dalamud from loading more stuff, until we've ensured that all required assets are available."); Task.WhenAll( @@ -83,7 +89,7 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA public IDalamudTextureWrap Empty4X4 => this.GetDalamudTextureWrap(DalamudAsset.Empty4X4); /// - public void Dispose() + void IInternalDisposableService.DisposeService() { lock (this.syncRoot) { diff --git a/Dalamud/Storage/ReliableFileStorage.cs b/Dalamud/Storage/ReliableFileStorage.cs index a013e95b5..eab93269e 100644 --- a/Dalamud/Storage/ReliableFileStorage.cs +++ b/Dalamud/Storage/ReliableFileStorage.cs @@ -22,17 +22,22 @@ namespace Dalamud.Storage; /// This is not an early-loaded service, as it is needed before they are initialized. /// [ServiceManager.ProvidedService] -public class ReliableFileStorage : IServiceType, IDisposable +[Api10ToDo("Make internal and IInternalDisposableService, and remove #pragma guard from the caller.")] +public class ReliableFileStorage : IPublicDisposableService { private static readonly ModuleLog Log = new("VFS"); private readonly object syncRoot = new(); + private SQLiteConnection? db; + private bool isService; /// /// Initializes a new instance of the class. /// /// Path to the VFS. + [Obsolete("Dalamud internal use only.", false)] + [Api10ToDo("Make internal, and remove #pragma guard from the caller.")] public ReliableFileStorage(string vfsDbPath) { var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db"); @@ -60,7 +65,7 @@ public class ReliableFileStorage : IServiceType, IDisposable } } } - + /// /// Check if a file exists. /// This will return true if the file does not exist on the filesystem, but in the transparent backup. @@ -288,9 +293,20 @@ public class ReliableFileStorage : IServiceType, IDisposable /// public void Dispose() { - this.db?.Dispose(); + if (!this.isService) + this.DisposeCore(); } + /// + void IInternalDisposableService.DisposeService() + { + if (this.isService) + this.DisposeCore(); + } + + /// + void IPublicDisposableService.MarkDisposeOnlyFromService() => this.isService = true; + /// /// Replace possible non-portable parts of a path with portable versions. /// @@ -312,6 +328,8 @@ public class ReliableFileStorage : IServiceType, IDisposable this.db.CreateTable(); } + private void DisposeCore() => this.db?.Dispose(); + private class DbFile { [PrimaryKey] diff --git a/Dalamud/Utility/DisposeSafety.cs b/Dalamud/Utility/DisposeSafety.cs index 8ac891e0a..64d31048f 100644 --- a/Dalamud/Utility/DisposeSafety.cs +++ b/Dalamud/Utility/DisposeSafety.cs @@ -70,7 +70,16 @@ public static class DisposeSafety r => { if (!r.IsCompletedSuccessfully) - return ignoreAllExceptions ? Task.CompletedTask : r; + { + if (ignoreAllExceptions) + { + _ = r.Exception; + return Task.CompletedTask; + } + + return r; + } + try { r.Result.Dispose(); diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 65196b3ee..43355ac2c 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -19,7 +19,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; -using Dalamud.Logging.Internal; using ImGuiNET; using Lumina.Excel.GeneratedSheets; using Serilog; @@ -638,42 +637,6 @@ public static class Util if (!Windows.Win32.PInvoke.MoveFileEx(tempPath, path, MOVE_FILE_FLAGS.MOVEFILE_REPLACE_EXISTING | MOVE_FILE_FLAGS.MOVEFILE_WRITE_THROUGH)) throw new Win32Exception(); } - - /// - /// Dispose this object. - /// - /// The object to dispose. - /// The type of object to dispose. - internal static void ExplicitDispose(this T obj) where T : IDisposable - { - obj.Dispose(); - } - - /// - /// Dispose this object. - /// - /// The object to dispose. - /// Log message to print, if specified and an error occurs. - /// Module logger, if any. - /// The type of object to dispose. - internal static void ExplicitDisposeIgnoreExceptions( - this T obj, string? logMessage = null, ModuleLog? moduleLog = null) where T : IDisposable - { - try - { - obj.Dispose(); - } - catch (Exception e) - { - if (logMessage == null) - return; - - if (moduleLog != null) - moduleLog.Error(e, logMessage); - else - Log.Error(e, logMessage); - } - } /// /// Gets a random, inoffensive, human-friendly string.