Add IInternal/PublicDisposableService (#1696)

* Add IInternal/PublicDisposableService

Plugins are exposed interfaces that are not inherited from
`IDisposable`, but services implementing plugin interfaces often
implement `IDisposable`. Some plugins may try to call
`IDisposable.Dispose` on everything provided, and it also is possible to
use `using` clause too eagerly while working on Dalamud itself, such as
writing `using var smth = await Service<SomeService>.GetAsync();`. Such
behaviors often lead to a difficult-to-debug errors, and making those
services either not an `IDisposable` or making `IDisposable.Dispose` do
nothing if the object has been loaded would prevent such errors. As
`ServiceManager` must be the only class dealing with construction and
disposal of services, `IInternalDisposableService` has been added to
limit who can dispose the object. `IPublicDisposableService` also has
been added to classes that can be constructed and accessed directly by
plugins; for those, `Dispose` will be ignored if the instance is a
service instance, and only `DisposeService` will respond.

In addition, `DalamudPluginInterface` and `UiBuilder` also have been
changed so that their `IDisposable.Dispose` no longer respond, and
instead, internal functions have been added to only allow disposal from
Dalamud.

* Cleanup

* Postmerge fixes

* More explanation on RunOnFrameworkThread(ClearHooks)

* Mark ReliableFileStorage public ctor obsolete

---------

Co-authored-by: goat <16760685+goaaats@users.noreply.github.com>
This commit is contained in:
srkizer 2024-03-17 00:58:05 +09:00 committed by GitHub
parent dcec076ca7
commit 87b9edb448
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 441 additions and 381 deletions

View file

@ -97,8 +97,6 @@ namespace Dalamud.CorePlugin
this.Interface.UiBuilder.Draw -= this.OnDraw; this.Interface.UiBuilder.Draw -= this.OnDraw;
this.windowSystem.RemoveAllWindows(); this.windowSystem.RemoveAllWindows();
this.Interface.ExplicitDispose();
} }
/// <summary> /// <summary>

View file

@ -26,7 +26,7 @@ namespace Dalamud.Configuration.Internal;
#pragma warning disable SA1015 #pragma warning disable SA1015
[InherentDependency<ReliableFileStorage>] // We must still have this when unloading [InherentDependency<ReliableFileStorage>] // We must still have this when unloading
#pragma warning restore SA1015 #pragma warning restore SA1015
internal sealed class DalamudConfiguration : IServiceType, IDisposable internal sealed class DalamudConfiguration : IInternalDisposableService
{ {
private static readonly JsonSerializerSettings SerializerSettings = new() private static readonly JsonSerializerSettings SerializerSettings = new()
{ {
@ -502,7 +502,7 @@ internal sealed class DalamudConfiguration : IServiceType, IDisposable
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
// Make sure that we save, if a save is queued while we are shutting down // Make sure that we save, if a save is queued while we are shutting down
this.Update(); this.Update();

View file

@ -9,7 +9,6 @@ using System.Threading.Tasks;
using Dalamud.Common; using Dalamud.Common;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Interface.Internal;
using Dalamud.Plugin.Internal; using Dalamud.Plugin.Internal;
using Dalamud.Storage; using Dalamud.Storage;
using Dalamud.Utility; using Dalamud.Utility;
@ -187,27 +186,6 @@ internal sealed class Dalamud : IServiceType
this.unloadSignal.WaitOne(); this.unloadSignal.WaitOne();
} }
/// <summary>
/// Dispose subsystems related to plugin handling.
/// </summary>
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<DalamudIme>.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<InterfaceManager>.GetNullable()?.Dispose();
Service<DalamudInterface>.GetNullable()?.Dispose();
Service<PluginManager>.GetNullable()?.Dispose();
}
/// <summary> /// <summary>
/// Replace the current exception handler with the default one. /// Replace the current exception handler with the default one.
/// </summary> /// </summary>

View file

@ -27,7 +27,7 @@ namespace Dalamud.Data;
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDataManager>] [ResolveVia<IDataManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal sealed class DataManager : IDisposable, IServiceType, IDataManager internal sealed class DataManager : IInternalDisposableService, IDataManager
{ {
private readonly Thread luminaResourceThread; private readonly Thread luminaResourceThread;
private readonly CancellationTokenSource luminaCancellationTokenSource; private readonly CancellationTokenSource luminaCancellationTokenSource;
@ -158,7 +158,7 @@ internal sealed class DataManager : IDisposable, IServiceType, IDataManager
#endregion #endregion
/// <inheritdoc/> /// <inheritdoc/>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.luminaCancellationTokenSource.Cancel(); this.luminaCancellationTokenSource.Cancel();
} }

View file

@ -138,7 +138,9 @@ public sealed class EntryPoint
SerilogEventSink.Instance.LogLine += SerilogOnLogLine; SerilogEventSink.Instance.LogLine += SerilogOnLogLine;
// Load configuration first to get some early persistent state, like log level // 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)!); var fs = new ReliableFileStorage(Path.GetDirectoryName(info.ConfigurationPath)!);
#pragma warning restore CS0618 // Type or member is obsolete
var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs); var configuration = DalamudConfiguration.Load(info.ConfigurationPath!, fs);
// Set the appropriate logging level from the configuration // Set the appropriate logging level from the configuration

View file

@ -19,7 +19,7 @@ namespace Dalamud.Game.Addon.Events;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal unsafe class AddonEventManager : IDisposable, IServiceType internal unsafe class AddonEventManager : IInternalDisposableService
{ {
/// <summary> /// <summary>
/// PluginName for Dalamud Internal use. /// PluginName for Dalamud Internal use.
@ -62,7 +62,7 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module); private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.onUpdateCursor.Dispose(); this.onUpdateCursor.Dispose();
@ -204,7 +204,7 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IAddonEventManager>] [ResolveVia<IAddonEventManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddonEventManager internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddonEventManager
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly AddonEventManager eventManagerService = Service<AddonEventManager>.Get(); private readonly AddonEventManager eventManagerService = Service<AddonEventManager>.Get();
@ -225,7 +225,7 @@ internal class AddonEventManagerPluginScoped : IDisposable, IServiceType, IAddon
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
// if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared. // if multiple plugins force cursors and dispose without un-forcing them then all forces will be cleared.
if (this.isForcingCursor) if (this.isForcingCursor)

View file

@ -19,7 +19,7 @@ namespace Dalamud.Game.Addon.Lifecycle;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal unsafe class AddonLifecycle : IDisposable, IServiceType internal unsafe class AddonLifecycle : IInternalDisposableService
{ {
private static readonly ModuleLog Log = new("AddonLifecycle"); private static readonly ModuleLog Log = new("AddonLifecycle");
@ -89,7 +89,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
internal List<AddonLifecycleEventListener> EventListeners { get; } = new(); internal List<AddonLifecycleEventListener> EventListeners { get; } = new();
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.onAddonSetupHook.Dispose(); this.onAddonSetupHook.Dispose();
this.onAddonSetup2Hook.Dispose(); this.onAddonSetup2Hook.Dispose();
@ -383,7 +383,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IAddonLifecycle>] [ResolveVia<IAddonLifecycle>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLifecycle internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLifecycle
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get(); private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
@ -391,7 +391,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif
private readonly List<AddonLifecycleEventListener> eventListeners = new(); private readonly List<AddonLifecycleEventListener> eventListeners = new();
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
foreach (var listener in this.eventListeners) foreach (var listener in this.eventListeners)
{ {

View file

@ -23,7 +23,7 @@ namespace Dalamud.Game.ClientState;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class ClientState : IDisposable, IServiceType, IClientState internal sealed class ClientState : IInternalDisposableService, IClientState
{ {
private static readonly ModuleLog Log = new("ClientState"); private static readonly ModuleLog Log = new("ClientState");
@ -115,7 +115,7 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.setupTerritoryTypeHook.Dispose(); this.setupTerritoryTypeHook.Dispose();
this.framework.Update -= this.FrameworkOnOnUpdateEvent; this.framework.Update -= this.FrameworkOnOnUpdateEvent;
@ -196,7 +196,7 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IClientState>] [ResolveVia<IClientState>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class ClientStatePluginScoped : IDisposable, IServiceType, IClientState internal class ClientStatePluginScoped : IInternalDisposableService, IClientState
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly ClientState clientStateService = Service<ClientState>.Get(); private readonly ClientState clientStateService = Service<ClientState>.Get();
@ -257,7 +257,7 @@ internal class ClientStatePluginScoped : IDisposable, IServiceType, IClientState
public bool IsGPosing => this.clientStateService.IsGPosing; public bool IsGPosing => this.clientStateService.IsGPosing;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward; this.clientStateService.TerritoryChanged -= this.TerritoryChangedForward;
this.clientStateService.Login -= this.LoginForward; this.clientStateService.Login -= this.LoginForward;

View file

@ -10,7 +10,7 @@ namespace Dalamud.Game.ClientState.Conditions;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed partial class Condition : IServiceType, ICondition internal sealed partial class Condition : IInternalDisposableService, ICondition
{ {
/// <summary> /// <summary>
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has. /// 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 readonly bool[] cache = new bool[MaxConditionEntries];
private bool isDisposed;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private Condition(ClientState clientState) private Condition(ClientState clientState)
{ {
@ -35,6 +37,9 @@ internal sealed partial class Condition : IServiceType, ICondition
this.framework.Update += this.FrameworkUpdate; this.framework.Update += this.FrameworkUpdate;
} }
/// <summary>Finalizes an instance of the <see cref="Condition" /> class.</summary>
~Condition() => this.Dispose(false);
/// <inheritdoc/> /// <inheritdoc/>
public event ICondition.ConditionChangeDelegate? ConditionChange; public event ICondition.ConditionChangeDelegate? ConditionChange;
@ -60,6 +65,9 @@ internal sealed partial class Condition : IServiceType, ICondition
public bool this[ConditionFlag flag] public bool this[ConditionFlag flag]
=> this[(int)flag]; => this[(int)flag];
/// <inheritdoc/>
void IInternalDisposableService.DisposeService() => this.Dispose(true);
/// <inheritdoc/> /// <inheritdoc/>
public bool Any() public bool Any()
{ {
@ -89,6 +97,19 @@ internal sealed partial class Condition : IServiceType, ICondition
return false; 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) private void FrameworkUpdate(IFramework unused)
{ {
for (var i = 0; i < MaxConditionEntries; i++) for (var i = 0; i < MaxConditionEntries; i++)
@ -112,44 +133,6 @@ internal sealed partial class Condition : IServiceType, ICondition
} }
} }
/// <summary>
/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc.
/// </summary>
internal sealed partial class Condition : IDisposable
{
private bool isDisposed;
/// <summary>
/// Finalizes an instance of the <see cref="Condition" /> class.
/// </summary>
~Condition()
{
this.Dispose(false);
}
/// <summary>
/// Disposes this instance, alongside its hooks.
/// </summary>
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;
}
}
/// <summary> /// <summary>
/// Plugin-scoped version of a Condition service. /// Plugin-scoped version of a Condition service.
/// </summary> /// </summary>
@ -159,7 +142,7 @@ internal sealed partial class Condition : IDisposable
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ICondition>] [ResolveVia<ICondition>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class ConditionPluginScoped : IDisposable, IServiceType, ICondition internal class ConditionPluginScoped : IInternalDisposableService, ICondition
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly Condition conditionService = Service<Condition>.Get(); private readonly Condition conditionService = Service<Condition>.Get();
@ -185,7 +168,7 @@ internal class ConditionPluginScoped : IDisposable, IServiceType, ICondition
public bool this[int flag] => this.conditionService[flag]; public bool this[int flag] => this.conditionService[flag];
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.conditionService.ConditionChange -= this.ConditionChangedForward; this.conditionService.ConditionChange -= this.ConditionChangedForward;

View file

@ -21,7 +21,7 @@ namespace Dalamud.Game.ClientState.GamePad;
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IGamepadState>] [ResolveVia<IGamepadState>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
{ {
private readonly Hook<ControllerPoll>? gamepadPoll; private readonly Hook<ControllerPoll>? gamepadPoll;
@ -109,7 +109,7 @@ internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
/// <summary> /// <summary>
/// Disposes this instance, alongside its hooks. /// Disposes this instance, alongside its hooks.
/// </summary> /// </summary>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.Dispose(true); this.Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

View file

@ -19,7 +19,7 @@ namespace Dalamud.Game.Command;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class CommandManager : IServiceType, IDisposable, ICommandManager internal sealed class CommandManager : IInternalDisposableService, ICommandManager
{ {
private static readonly ModuleLog Log = new("Command"); private static readonly ModuleLog Log = new("Command");
@ -130,7 +130,7 @@ internal sealed class CommandManager : IServiceType, IDisposable, ICommandManage
} }
/// <inheritdoc/> /// <inheritdoc/>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled; this.chatGui.CheckMessageHandled -= this.OnCheckMessageHandled;
} }
@ -170,7 +170,7 @@ internal sealed class CommandManager : IServiceType, IDisposable, ICommandManage
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ICommandManager>] [ResolveVia<ICommandManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class CommandManagerPluginScoped : IDisposable, IServiceType, ICommandManager internal class CommandManagerPluginScoped : IInternalDisposableService, ICommandManager
{ {
private static readonly ModuleLog Log = new("Command"); private static readonly ModuleLog Log = new("Command");
@ -193,7 +193,7 @@ internal class CommandManagerPluginScoped : IDisposable, IServiceType, ICommandM
public ReadOnlyDictionary<string, CommandInfo> Commands => this.commandManagerService.Commands; public ReadOnlyDictionary<string, CommandInfo> Commands => this.commandManagerService.Commands;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
foreach (var command in this.pluginRegisteredCommands) foreach (var command in this.pluginRegisteredCommands)
{ {

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.Config;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable internal sealed class GameConfig : IInternalDisposableService, IGameConfig
{ {
private readonly TaskCompletionSource tcsInitialization = new(); private readonly TaskCompletionSource tcsInitialization = new();
private readonly TaskCompletionSource<GameConfigSection> tcsSystem = new(); private readonly TaskCompletionSource<GameConfigSection> 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); public void Set(UiControlOption option, string value) => this.UiControl.Set(option.GetName(), value);
/// <inheritdoc/> /// <inheritdoc/>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
var ode = new ObjectDisposedException(nameof(GameConfig)); var ode = new ObjectDisposedException(nameof(GameConfig));
this.tcsInitialization.SetExceptionIfIncomplete(ode); this.tcsInitialization.SetExceptionIfIncomplete(ode);
@ -248,7 +248,7 @@ internal sealed class GameConfig : IServiceType, IGameConfig, IDisposable
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IGameConfig>] [ResolveVia<IGameConfig>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class GameConfigPluginScoped : IDisposable, IServiceType, IGameConfig internal class GameConfigPluginScoped : IInternalDisposableService, IGameConfig
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly GameConfig gameConfigService = Service<GameConfig>.Get(); private readonly GameConfig gameConfigService = Service<GameConfig>.Get();
@ -295,7 +295,7 @@ internal class GameConfigPluginScoped : IDisposable, IServiceType, IGameConfig
public GameConfigSection UiControl => this.gameConfigService.UiControl; public GameConfigSection UiControl => this.gameConfigService.UiControl;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.gameConfigService.Changed -= this.ConfigChangedForward; this.gameConfigService.Changed -= this.ConfigChangedForward;
this.initializationTask.ContinueWith( this.initializationTask.ContinueWith(

View file

@ -13,7 +13,7 @@ namespace Dalamud.Game.DutyState;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal unsafe class DutyState : IDisposable, IServiceType, IDutyState internal unsafe class DutyState : IInternalDisposableService, IDutyState
{ {
private readonly DutyStateAddressResolver address; private readonly DutyStateAddressResolver address;
private readonly Hook<SetupContentDirectNetworkMessageDelegate> contentDirectorNetworkMessageHook; private readonly Hook<SetupContentDirectNetworkMessageDelegate> contentDirectorNetworkMessageHook;
@ -62,7 +62,7 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
private bool CompletedThisTerritory { get; set; } private bool CompletedThisTerritory { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.contentDirectorNetworkMessageHook.Dispose(); this.contentDirectorNetworkMessageHook.Dispose();
this.framework.Update -= this.FrameworkOnUpdateEvent; this.framework.Update -= this.FrameworkOnUpdateEvent;
@ -168,7 +168,7 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDutyState>] [ResolveVia<IDutyState>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class DutyStatePluginScoped : IDisposable, IServiceType, IDutyState internal class DutyStatePluginScoped : IInternalDisposableService, IDutyState
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly DutyState dutyStateService = Service<DutyState>.Get(); private readonly DutyState dutyStateService = Service<DutyState>.Get();
@ -200,7 +200,7 @@ internal class DutyStatePluginScoped : IDisposable, IServiceType, IDutyState
public bool IsDutyStarted => this.dutyStateService.IsDutyStarted; public bool IsDutyStarted => this.dutyStateService.IsDutyStarted;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.dutyStateService.DutyStarted -= this.DutyStartedForward; this.dutyStateService.DutyStarted -= this.DutyStartedForward;
this.dutyStateService.DutyWiped -= this.DutyWipedForward; this.dutyStateService.DutyWiped -= this.DutyWipedForward;

View file

@ -23,7 +23,7 @@ namespace Dalamud.Game;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class Framework : IDisposable, IServiceType, IFramework internal sealed class Framework : IInternalDisposableService, IFramework
{ {
private static readonly ModuleLog Log = new("Framework"); private static readonly ModuleLog Log = new("Framework");
@ -274,7 +274,7 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.RunOnFrameworkThread(() => this.RunOnFrameworkThread(() =>
{ {
@ -469,7 +469,7 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IFramework>] [ResolveVia<IFramework>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class FrameworkPluginScoped : IDisposable, IServiceType, IFramework internal class FrameworkPluginScoped : IInternalDisposableService, IFramework
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly Framework frameworkService = Service<Framework>.Get(); private readonly Framework frameworkService = Service<Framework>.Get();
@ -504,7 +504,7 @@ internal class FrameworkPluginScoped : IDisposable, IServiceType, IFramework
public bool IsFrameworkUnloading => this.frameworkService.IsFrameworkUnloading; public bool IsFrameworkUnloading => this.frameworkService.IsFrameworkUnloading;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.frameworkService.Update -= this.OnUpdateForward; this.frameworkService.Update -= this.OnUpdateForward;

View file

@ -29,7 +29,7 @@ namespace Dalamud.Game.Gui;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
{ {
private static readonly ModuleLog Log = new("ChatGui"); private static readonly ModuleLog Log = new("ChatGui");
@ -109,7 +109,7 @@ internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.printMessageHook.Dispose(); this.printMessageHook.Dispose();
this.populateItemLinkHook.Dispose(); this.populateItemLinkHook.Dispose();
@ -409,7 +409,7 @@ internal sealed unsafe class ChatGui : IDisposable, IServiceType, IChatGui
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IChatGui>] [ResolveVia<IChatGui>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class ChatGuiPluginScoped : IDisposable, IServiceType, IChatGui internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly ChatGui chatGuiService = Service<ChatGui>.Get(); private readonly ChatGui chatGuiService = Service<ChatGui>.Get();
@ -447,7 +447,7 @@ internal class ChatGuiPluginScoped : IDisposable, IServiceType, IChatGui
public IReadOnlyDictionary<(string PluginName, uint CommandId), Action<uint, SeString>> RegisteredLinkHandlers => this.chatGuiService.RegisteredLinkHandlers; public IReadOnlyDictionary<(string PluginName, uint CommandId), Action<uint, SeString>> RegisteredLinkHandlers => this.chatGuiService.RegisteredLinkHandlers;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.chatGuiService.ChatMessage -= this.OnMessageForward; this.chatGuiService.ChatMessage -= this.OnMessageForward;
this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward; this.chatGuiService.CheckMessageHandled -= this.OnCheckMessageForward;

View file

@ -28,7 +28,7 @@ namespace Dalamud.Game.Gui.ContextMenu;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe class ContextMenu : IDisposable, IServiceType, IContextMenu internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextMenu
{ {
private static readonly ModuleLog Log = new("ContextMenu"); private static readonly ModuleLog Log = new("ContextMenu");
@ -77,7 +77,7 @@ internal sealed unsafe class ContextMenu : IDisposable, IServiceType, IContextMe
private IReadOnlyList<MenuItem>? SubmenuItems { get; set; } private IReadOnlyList<MenuItem>? SubmenuItems { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
var manager = RaptureAtkUnitManager.Instance(); var manager = RaptureAtkUnitManager.Instance();
var menu = manager->GetAddonByName("ContextMenu"); var menu = manager->GetAddonByName("ContextMenu");
@ -496,7 +496,7 @@ original:
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IContextMenu>] [ResolveVia<IContextMenu>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class ContextMenuPluginScoped : IDisposable, IServiceType, IContextMenu internal class ContextMenuPluginScoped : IInternalDisposableService, IContextMenu
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly ContextMenu parentService = Service<ContextMenu>.Get(); private readonly ContextMenu parentService = Service<ContextMenu>.Get();
@ -514,7 +514,7 @@ internal class ContextMenuPluginScoped : IDisposable, IServiceType, IContextMenu
private object MenuItemsLock { get; } = new(); private object MenuItemsLock { get; } = new();
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.parentService.OnMenuOpened -= this.OnMenuOpenedForward; this.parentService.OnMenuOpened -= this.OnMenuOpenedForward;

View file

@ -22,7 +22,7 @@ namespace Dalamud.Game.Gui.Dtr;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar internal sealed unsafe class DtrBar : IInternalDisposableService, IDtrBar
{ {
private const uint BaseNodeId = 1000; private const uint BaseNodeId = 1000;
@ -101,7 +101,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
} }
/// <inheritdoc/> /// <inheritdoc/>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.addonLifecycle.UnregisterListener(this.dtrPostDrawListener); this.addonLifecycle.UnregisterListener(this.dtrPostDrawListener);
this.addonLifecycle.UnregisterListener(this.dtrPostRequestedUpdateListener); this.addonLifecycle.UnregisterListener(this.dtrPostRequestedUpdateListener);
@ -493,7 +493,7 @@ internal sealed unsafe class DtrBar : IDisposable, IServiceType, IDtrBar
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDtrBar>] [ResolveVia<IDtrBar>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class DtrBarPluginScoped : IDisposable, IServiceType, IDtrBar internal class DtrBarPluginScoped : IInternalDisposableService, IDtrBar
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly DtrBar dtrBarService = Service<DtrBar>.Get(); private readonly DtrBar dtrBarService = Service<DtrBar>.Get();
@ -501,7 +501,7 @@ internal class DtrBarPluginScoped : IDisposable, IServiceType, IDtrBar
private readonly Dictionary<string, DtrBarEntry> pluginEntries = new(); private readonly Dictionary<string, DtrBarEntry> pluginEntries = new();
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
foreach (var entry in this.pluginEntries) foreach (var entry in this.pluginEntries)
{ {

View file

@ -16,7 +16,7 @@ namespace Dalamud.Game.Gui.FlyText;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
{ {
/// <summary> /// <summary>
/// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>. /// The native function responsible for adding fly text to the UI. See <see cref="FlyTextGuiAddressResolver.AddFlyText"/>.
@ -78,7 +78,7 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
/// <summary> /// <summary>
/// Disposes of managed and unmanaged resources. /// Disposes of managed and unmanaged resources.
/// </summary> /// </summary>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.createFlyTextHook.Dispose(); this.createFlyTextHook.Dispose();
} }
@ -277,7 +277,7 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IFlyTextGui>] [ResolveVia<IFlyTextGui>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class FlyTextGuiPluginScoped : IDisposable, IServiceType, IFlyTextGui internal class FlyTextGuiPluginScoped : IInternalDisposableService, IFlyTextGui
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly FlyTextGui flyTextGuiService = Service<FlyTextGui>.Get(); private readonly FlyTextGui flyTextGuiService = Service<FlyTextGui>.Get();
@ -294,7 +294,7 @@ internal class FlyTextGuiPluginScoped : IDisposable, IServiceType, IFlyTextGui
public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated; public event IFlyTextGui.OnFlyTextCreatedDelegate? FlyTextCreated;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.flyTextGuiService.FlyTextCreated -= this.FlyTextCreatedForward; this.flyTextGuiService.FlyTextCreated -= this.FlyTextCreatedForward;

View file

@ -27,7 +27,7 @@ namespace Dalamud.Game.Gui;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
{ {
private static readonly ModuleLog Log = new("GameGui"); private static readonly ModuleLog Log = new("GameGui");
@ -344,7 +344,7 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
/// <summary> /// <summary>
/// Disables the hooks and submodules of this module. /// Disables the hooks and submodules of this module.
/// </summary> /// </summary>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.setGlobalBgmHook.Dispose(); this.setGlobalBgmHook.Dispose();
this.handleItemHoverHook.Dispose(); this.handleItemHoverHook.Dispose();
@ -520,7 +520,7 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IGameGui>] [ResolveVia<IGameGui>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class GameGuiPluginScoped : IDisposable, IServiceType, IGameGui internal class GameGuiPluginScoped : IInternalDisposableService, IGameGui
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly GameGui gameGuiService = Service<GameGui>.Get(); private readonly GameGui gameGuiService = Service<GameGui>.Get();
@ -558,7 +558,7 @@ internal class GameGuiPluginScoped : IDisposable, IServiceType, IGameGui
public HoveredAction HoveredAction => this.gameGuiService.HoveredAction; public HoveredAction HoveredAction => this.gameGuiService.HoveredAction;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.gameGuiService.UiHideToggled -= this.UiHideToggledForward; this.gameGuiService.UiHideToggled -= this.UiHideToggledForward;
this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward; this.gameGuiService.HoveredItemChanged -= this.HoveredItemForward;

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.Gui.PartyFinder;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGui internal sealed class PartyFinderGui : IInternalDisposableService, IPartyFinderGui
{ {
private readonly PartyFinderAddressResolver address; private readonly PartyFinderAddressResolver address;
private readonly IntPtr memory; private readonly IntPtr memory;
@ -47,7 +47,7 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.receiveListingHook.Dispose(); this.receiveListingHook.Dispose();
@ -131,7 +131,7 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IPartyFinderGui>] [ResolveVia<IPartyFinderGui>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class PartyFinderGuiPluginScoped : IDisposable, IServiceType, IPartyFinderGui internal class PartyFinderGuiPluginScoped : IInternalDisposableService, IPartyFinderGui
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly PartyFinderGui partyFinderGuiService = Service<PartyFinderGui>.Get(); private readonly PartyFinderGui partyFinderGuiService = Service<PartyFinderGui>.Get();
@ -148,7 +148,7 @@ internal class PartyFinderGuiPluginScoped : IDisposable, IServiceType, IPartyFin
public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing; public event IPartyFinderGui.PartyFinderListingEventDelegate? ReceiveListing;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.partyFinderGuiService.ReceiveListing -= this.ReceiveListingForward; this.partyFinderGuiService.ReceiveListing -= this.ReceiveListingForward;

View file

@ -14,7 +14,7 @@ namespace Dalamud.Game.Gui.Toast;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
{ {
private const uint QuestToastCheckmarkMagic = 60081; private const uint QuestToastCheckmarkMagic = 60081;
@ -73,7 +73,7 @@ internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui
/// <summary> /// <summary>
/// Disposes of managed and unmanaged resources. /// Disposes of managed and unmanaged resources.
/// </summary> /// </summary>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.showNormalToastHook.Dispose(); this.showNormalToastHook.Dispose();
this.showQuestToastHook.Dispose(); this.showQuestToastHook.Dispose();
@ -383,7 +383,7 @@ internal sealed partial class ToastGui
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IToastGui>] [ResolveVia<IToastGui>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class ToastGuiPluginScoped : IDisposable, IServiceType, IToastGui internal class ToastGuiPluginScoped : IInternalDisposableService, IToastGui
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly ToastGui toastGuiService = Service<ToastGui>.Get(); private readonly ToastGui toastGuiService = Service<ToastGui>.Get();
@ -408,7 +408,7 @@ internal class ToastGuiPluginScoped : IDisposable, IServiceType, IToastGui
public event IToastGui.OnErrorToastDelegate? ErrorToast; public event IToastGui.OnErrorToastDelegate? ErrorToast;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.toastGuiService.Toast -= this.ToastForward; this.toastGuiService.Toast -= this.ToastForward;
this.toastGuiService.QuestToast -= this.QuestToastForward; this.toastGuiService.QuestToast -= this.QuestToastForward;

View file

@ -12,7 +12,7 @@ namespace Dalamud.Game.Internal;
/// This class disables anti-debug functionality in the game client. /// This class disables anti-debug functionality in the game client.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [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 readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 };
private byte[] original; private byte[] original;
@ -43,16 +43,25 @@ internal sealed partial class AntiDebug : IServiceType
} }
} }
/// <summary>Finalizes an instance of the <see cref="AntiDebug"/> class.</summary>
~AntiDebug() => this.Disable();
/// <summary> /// <summary>
/// Gets a value indicating whether the anti-debugging is enabled. /// Gets a value indicating whether the anti-debugging is enabled.
/// </summary> /// </summary>
public bool IsEnabled { get; private set; } = false; public bool IsEnabled { get; private set; } = false;
/// <inheritdoc />
void IInternalDisposableService.DisposeService() => this.Disable();
/// <summary> /// <summary>
/// Enables the anti-debugging by overwriting code in memory. /// Enables the anti-debugging by overwriting code in memory.
/// </summary> /// </summary>
public void Enable() public void Enable()
{ {
if (this.IsEnabled)
return;
this.original = new byte[this.nop.Length]; this.original = new byte[this.nop.Length];
if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled)
{ {
@ -73,6 +82,9 @@ internal sealed partial class AntiDebug : IServiceType
/// </summary> /// </summary>
public void Disable() public void Disable()
{ {
if (!this.IsEnabled)
return;
if (this.debugCheckAddress != IntPtr.Zero && this.original != null) if (this.debugCheckAddress != IntPtr.Zero && this.original != null)
{ {
Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}");
@ -86,45 +98,3 @@ internal sealed partial class AntiDebug : IServiceType
this.IsEnabled = false; this.IsEnabled = false;
} }
} }
/// <summary>
/// Implementing IDisposable.
/// </summary>
internal sealed partial class AntiDebug : IDisposable
{
private bool disposed = false;
/// <summary>
/// Finalizes an instance of the <see cref="AntiDebug"/> class.
/// </summary>
~AntiDebug() => this.Dispose(false);
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
/// <param name="disposing">If this was disposed through calling Dispose() or from being finalized.</param>
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;
}
}

View file

@ -20,7 +20,7 @@ namespace Dalamud.Game.Internal;
/// This class implements in-game Dalamud options in the in-game System menu. /// This class implements in-game Dalamud options in the in-game System menu.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe partial class DalamudAtkTweaks : IServiceType internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
{ {
private readonly AtkValueChangeType atkValueChangeType; private readonly AtkValueChangeType atkValueChangeType;
private readonly AtkValueSetString atkValueSetString; private readonly AtkValueSetString atkValueSetString;
@ -40,6 +40,8 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
private readonly string locDalamudPlugins; private readonly string locDalamudPlugins;
private readonly string locDalamudSettings; private readonly string locDalamudSettings;
private bool disposed = false;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private DalamudAtkTweaks(TargetSigScanner sigScanner) private DalamudAtkTweaks(TargetSigScanner sigScanner)
{ {
@ -69,6 +71,9 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
} }
/// <summary>Finalizes an instance of the <see cref="DalamudAtkTweaks"/> class.</summary>
~DalamudAtkTweaks() => this.Dispose(false);
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type); 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); private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5);
/// <inheritdoc/>
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) private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
{ {
@ -229,45 +254,3 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
} }
} }
} }
/// <summary>
/// Implements IDisposable.
/// </summary>
internal sealed partial class DalamudAtkTweaks : IDisposable
{
private bool disposed = false;
/// <summary>
/// Finalizes an instance of the <see cref="DalamudAtkTweaks"/> class.
/// </summary>
~DalamudAtkTweaks() => this.Dispose(false);
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
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;
}
}

View file

@ -19,7 +19,7 @@ namespace Dalamud.Game.Inventory;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal class GameInventory : IDisposable, IServiceType internal class GameInventory : IInternalDisposableService
{ {
private readonly List<GameInventoryPluginScoped> subscribersPendingChange = new(); private readonly List<GameInventoryPluginScoped> subscribersPendingChange = new();
private readonly List<GameInventoryPluginScoped> subscribers = new(); private readonly List<GameInventoryPluginScoped> subscribers = new();
@ -61,7 +61,7 @@ internal class GameInventory : IDisposable, IServiceType
private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1); private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
lock (this.subscribersPendingChange) lock (this.subscribersPendingChange)
{ {
@ -351,7 +351,7 @@ internal class GameInventory : IDisposable, IServiceType
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IGameInventory>] [ResolveVia<IGameInventory>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInventory internal class GameInventoryPluginScoped : IInternalDisposableService, IGameInventory
{ {
private static readonly ModuleLog Log = new(nameof(GameInventoryPluginScoped)); private static readonly ModuleLog Log = new(nameof(GameInventoryPluginScoped));
@ -406,7 +406,7 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
public event IGameInventory.InventoryChangedDelegate<InventoryItemMergedArgs>? ItemMergedExplicit; public event IGameInventory.InventoryChangedDelegate<InventoryItemMergedArgs>? ItemMergedExplicit;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.gameInventoryService.Unsubscribe(this); this.gameInventoryService.Unsubscribe(this);

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game.Network;
/// </summary> /// </summary>
[InterfaceVersion("1.0")] [InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork internal sealed class GameNetwork : IInternalDisposableService, IGameNetwork
{ {
private readonly GameNetworkAddressResolver address; private readonly GameNetworkAddressResolver address;
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook; private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
@ -59,7 +59,7 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage; public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage;
/// <inheritdoc/> /// <inheritdoc/>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.processZonePacketDownHook.Dispose(); this.processZonePacketDownHook.Dispose();
this.processZonePacketUpHook.Dispose(); this.processZonePacketUpHook.Dispose();
@ -145,7 +145,7 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IGameNetwork>] [ResolveVia<IGameNetwork>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class GameNetworkPluginScoped : IDisposable, IServiceType, IGameNetwork internal class GameNetworkPluginScoped : IInternalDisposableService, IGameNetwork
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly GameNetwork gameNetworkService = Service<GameNetwork>.Get(); private readonly GameNetwork gameNetworkService = Service<GameNetwork>.Get();
@ -162,7 +162,7 @@ internal class GameNetworkPluginScoped : IDisposable, IServiceType, IGameNetwork
public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage; public event IGameNetwork.OnNetworkMessageDelegate? NetworkMessage;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.gameNetworkService.NetworkMessage -= this.NetworkMessageForward; this.gameNetworkService.NetworkMessage -= this.NetworkMessageForward;

View file

@ -26,7 +26,7 @@ namespace Dalamud.Game.Network.Internal;
/// This class handles network notifications and uploading market board data. /// This class handles network notifications and uploading market board data.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal unsafe class NetworkHandlers : IDisposable, IServiceType internal unsafe class NetworkHandlers : IInternalDisposableService
{ {
private readonly IMarketBoardUploader uploader; private readonly IMarketBoardUploader uploader;
@ -213,7 +213,7 @@ internal unsafe class NetworkHandlers : IDisposable, IServiceType
/// <summary> /// <summary>
/// Disposes of managed and unmanaged resources. /// Disposes of managed and unmanaged resources.
/// </summary> /// </summary>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.disposing = true; this.disposing = true;
this.Dispose(this.disposing); this.Dispose(this.disposing);

View file

@ -10,7 +10,7 @@ namespace Dalamud.Game.Network.Internal;
/// This class enables TCP optimizations in the game socket for better performance. /// This class enables TCP optimizations in the game socket for better performance.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed class WinSockHandlers : IDisposable, IServiceType internal sealed class WinSockHandlers : IInternalDisposableService
{ {
private Hook<SocketDelegate> ws2SocketHook; private Hook<SocketDelegate> ws2SocketHook;
@ -27,7 +27,7 @@ internal sealed class WinSockHandlers : IDisposable, IServiceType
/// <summary> /// <summary>
/// Disposes of managed and unmanaged resources. /// Disposes of managed and unmanaged resources.
/// </summary> /// </summary>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.ws2SocketHook?.Dispose(); this.ws2SocketHook?.Dispose();
} }

View file

@ -104,6 +104,10 @@ public class SigScanner : IDisposable, ISigScanner
/// <inheritdoc/> /// <inheritdoc/>
public ProcessModule Module { get; } public ProcessModule Module { get; }
/// <summary>Gets or sets a value indicating whether this instance of <see cref="SigScanner"/> is meant to be a
/// Dalamud service.</summary>
private protected bool IsService { get; set; }
private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize; private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize;
/// <summary> /// <summary>
@ -309,13 +313,11 @@ public class SigScanner : IDisposable, ISigScanner
} }
} }
/// <summary> /// <inheritdoc/>
/// Free the memory of the copied module search area on object disposal, if applicable.
/// </summary>
public void Dispose() public void Dispose()
{ {
this.Save(); if (!this.IsService)
Marshal.FreeHGlobal(this.moduleCopyPtr); this.DisposeCore();
} }
/// <summary> /// <summary>
@ -337,6 +339,15 @@ public class SigScanner : IDisposable, ISigScanner
} }
} }
/// <summary>
/// Free the memory of the copied module search area on object disposal, if applicable.
/// </summary>
private protected void DisposeCore()
{
this.Save();
Marshal.FreeHGlobal(this.moduleCopyPtr);
}
/// <summary> /// <summary>
/// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location. /// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location.
/// </summary> /// </summary>

View file

@ -15,7 +15,7 @@ namespace Dalamud.Game;
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ISigScanner>] [ResolveVia<ISigScanner>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class TargetSigScanner : SigScanner, IServiceType internal class TargetSigScanner : SigScanner, IPublicDisposableService
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TargetSigScanner"/> class. /// Initializes a new instance of the <see cref="TargetSigScanner"/> class.
@ -26,4 +26,14 @@ internal class TargetSigScanner : SigScanner, IServiceType
: base(Process.GetCurrentProcess().MainModule!, doCopy, cacheFile) : base(Process.GetCurrentProcess().MainModule!, doCopy, cacheFile)
{ {
} }
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
if (this.IsService)
this.DisposeCore();
}
/// <inheritdoc/>
void IPublicDisposableService.MarkDisposeOnlyFromService() => this.IsService = true;
} }

View file

@ -21,7 +21,7 @@ namespace Dalamud.Hooking.Internal;
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IGameInteropProvider>] [ResolveVia<IGameInteropProvider>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class GameInteropProviderPluginScoped : IGameInteropProvider, IServiceType, IDisposable internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternalDisposableService
{ {
private readonly LocalPlugin plugin; private readonly LocalPlugin plugin;
private readonly SigScanner scanner; private readonly SigScanner scanner;
@ -83,7 +83,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IServiceT
=> this.HookFromAddress(this.scanner.ScanText(signature), detour, backend); => this.HookFromAddress(this.scanner.ScanText(signature), detour, backend);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
var notDisposed = this.trackedHooks.Where(x => !x.IsDisposed).ToArray(); var notDisposed = this.trackedHooks.Where(x => !x.IsDisposed).ToArray();
if (notDisposed.Length != 0) if (notDisposed.Length != 0)

View file

@ -14,7 +14,7 @@ namespace Dalamud.Hooking.Internal;
/// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. /// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class HookManager : IDisposable, IServiceType internal class HookManager : IInternalDisposableService
{ {
/// <summary> /// <summary>
/// Logger shared with <see cref="Unhooker"/>. /// Logger shared with <see cref="Unhooker"/>.
@ -74,7 +74,7 @@ internal class HookManager : IDisposable, IServiceType
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
RevertHooks(); RevertHooks();
TrackedHooks.Clear(); TrackedHooks.Clear();

View file

@ -15,7 +15,7 @@ namespace Dalamud.Hooking.WndProcHook;
/// Manages WndProc hooks for game main window and extra ImGui viewport windows. /// Manages WndProc hooks for game main window and extra ImGui viewport windows.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed class WndProcHookManager : IServiceType, IDisposable internal sealed class WndProcHookManager : IInternalDisposableService
{ {
private static readonly ModuleLog Log = new(nameof(WndProcHookManager)); private static readonly ModuleLog Log = new(nameof(WndProcHookManager));
@ -56,7 +56,7 @@ internal sealed class WndProcHookManager : IServiceType, IDisposable
public event WndProcEventDelegate? PostWndProc; public event WndProcEventDelegate? PostWndProc;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
if (this.dispatchMessageWHook.IsDisposed) if (this.dispatchMessageWHook.IsDisposed)
return; return;

View file

@ -6,3 +6,20 @@
public interface IServiceType public interface IServiceType
{ {
} }
/// <summary><see cref="IDisposable"/>, but for <see cref="IServiceType"/>.</summary>
/// <remarks>Use this to prevent services from accidentally being disposed by plugins or <c>using</c> clauses.</remarks>
internal interface IInternalDisposableService : IServiceType
{
/// <summary>Disposes the service.</summary>
void DisposeService();
}
/// <summary>An <see cref="IInternalDisposableService"/> which happens to be public and needs to expose
/// <see cref="IDisposable.Dispose"/>.</summary>
internal interface IPublicDisposableService : IInternalDisposableService, IDisposable
{
/// <summary>Marks that only <see cref="IInternalDisposableService.DisposeService"/> should respond,
/// while suppressing <see cref="IDisposable.Dispose"/>.</summary>
void MarkDisposeOnlyFromService();
}

View file

@ -19,7 +19,7 @@ namespace Dalamud.Interface.DragDrop;
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDragDropManager>] [ResolveVia<IDragDropManager>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType internal partial class DragDropManager : IInternalDisposableService, IDragDropManager
{ {
private nint windowHandlePtr = nint.Zero; private nint windowHandlePtr = nint.Zero;
@ -56,6 +56,9 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService
/// <summary> Gets the list of directory paths currently being dragged from an external application over any FFXIV-related viewport or stored from the last drop. </summary> /// <summary> Gets the list of directory paths currently being dragged from an external application over any FFXIV-related viewport or stored from the last drop. </summary>
public IReadOnlyList<string> Directories { get; private set; } = Array.Empty<string>(); public IReadOnlyList<string> Directories { get; private set; } = Array.Empty<string>();
/// <inheritdoc/>
void IInternalDisposableService.DisposeService() => this.Disable();
/// <summary> Enable external drag and drop. </summary> /// <summary> Enable external drag and drop. </summary>
public void Enable() public void Enable()
{ {
@ -99,10 +102,6 @@ internal partial class DragDropManager : IDisposable, IDragDropManager, IService
this.ServiceAvailable = false; this.ServiceAvailable = false;
} }
/// <inheritdoc cref="Disable"/>
public void Dispose()
=> this.Disable();
/// <inheritdoc cref="IDragDropManager.CreateImGuiSource(string, Func{IDragDropManager, bool}, Func{IDragDropManager, bool})"/> /// <inheritdoc cref="IDragDropManager.CreateImGuiSource(string, Func{IDragDropManager, bool}, Func{IDragDropManager, bool})"/>
public void CreateImGuiSource(string label, Func<IDragDropManager, bool> validityCheck, Func<IDragDropManager, bool> tooltipBuilder) public void CreateImGuiSource(string label, Func<IDragDropManager, bool> validityCheck, Func<IDragDropManager, bool> tooltipBuilder)
{ {

View file

@ -93,7 +93,7 @@ public sealed class SingleFontChooserDialog : IDisposable
/// <summary>Initializes a new instance of the <see cref="SingleFontChooserDialog"/> class.</summary> /// <summary>Initializes a new instance of the <see cref="SingleFontChooserDialog"/> class.</summary>
/// <param name="newAsyncAtlas">A new instance of <see cref="IFontAtlas"/> created using /// <param name="newAsyncAtlas">A new instance of <see cref="IFontAtlas"/> created using
/// <see cref="FontAtlasAutoRebuildMode.Async"/> as its auto-rebuild mode.</param> /// <see cref="FontAtlasAutoRebuildMode.Async"/> as its auto-rebuild mode.</param>
/// <remarks>The passed instance of <see cref="newAsyncAtlas"/> will be disposed after use. If you pass an atlas /// <remarks>The passed instance of <paramref see="newAsyncAtlas"/> 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 /// that is already being used, then all the font handles under the passed atlas will be invalidated upon disposing
/// this font chooser. Consider using <see cref="SingleFontChooserDialog(UiBuilder, bool, string?)"/> for automatic /// this font chooser. Consider using <see cref="SingleFontChooserDialog(UiBuilder, bool, string?)"/> for automatic
/// handling of font atlas derived from a <see cref="UiBuilder"/>, or even <see cref="CreateAuto"/> for automatic /// handling of font atlas derived from a <see cref="UiBuilder"/>, or even <see cref="CreateAuto"/> for automatic

View file

@ -34,7 +34,7 @@ namespace Dalamud.Interface.Internal;
/// This class handles CJK IME. /// This class handles CJK IME.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe class DalamudIme : IDisposable, IServiceType internal sealed unsafe class DalamudIme : IInternalDisposableService
{ {
private const int CImGuiStbTextCreateUndoOffset = 0xB57A0; private const int CImGuiStbTextCreateUndoOffset = 0xB57A0;
private const int CImGuiStbTextUndoOffset = 0xB59C0; 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; this.candidateStrings.Count != 0 || this.ShowPartialConversion || this.inputModeIcon != default;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.interfaceManager.Draw -= this.Draw; this.interfaceManager.Draw -= this.Draw;
this.ReleaseUnmanagedResources(); this.ReleaseUnmanagedResources();

View file

@ -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. /// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class DalamudInterface : IDisposable, IServiceType internal class DalamudInterface : IInternalDisposableService
{ {
private const float CreditsDarkeningMaxAlpha = 0.8f; private const float CreditsDarkeningMaxAlpha = 0.8f;
@ -209,7 +209,7 @@ internal class DalamudInterface : IDisposable, IServiceType
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.interfaceManager.Draw -= this.OnDraw; this.interfaceManager.Draw -= this.OnDraw;

View file

@ -35,7 +35,7 @@ namespace Dalamud.Interface.Internal;
/// </para> /// </para>
/// </remarks> /// </remarks>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDisposable internal sealed unsafe class ImGuiClipboardFunctionProvider : IInternalDisposableService
{ {
private static readonly ModuleLog Log = new(nameof(ImGuiClipboardFunctionProvider)); private static readonly ModuleLog Log = new(nameof(ImGuiClipboardFunctionProvider));
private readonly nint clipboardUserDataOriginal; private readonly nint clipboardUserDataOriginal;
@ -75,7 +75,7 @@ internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDis
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
if (!this.clipboardUserData.IsAllocated) if (!this.clipboardUserData.IsAllocated)
return; return;

View file

@ -24,7 +24,7 @@ namespace Dalamud.Interface.Internal;
/// Change push_texture_id to only have one condition. /// Change push_texture_id to only have one condition.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal sealed unsafe class ImGuiDrawListFixProvider : IServiceType, IDisposable internal sealed unsafe class ImGuiDrawListFixProvider : IInternalDisposableService
{ {
private const int CImGuiImDrawListAddPolyLineOffset = 0x589B0; private const int CImGuiImDrawListAddPolyLineOffset = 0x589B0;
private const int CImGuiImDrawListAddRectFilled = 0x59FD0; private const int CImGuiImDrawListAddRectFilled = 0x59FD0;
@ -69,7 +69,7 @@ internal sealed unsafe class ImGuiDrawListFixProvider : IServiceType, IDisposabl
ImDrawFlags flags); ImDrawFlags flags);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.hookImDrawListAddPolyline.Dispose(); this.hookImDrawListAddPolyline.Dispose();
this.hookImDrawListAddRectFilled.Dispose(); this.hookImDrawListAddRectFilled.Dispose();

View file

@ -5,6 +5,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
@ -51,7 +52,7 @@ namespace Dalamud.Interface.Internal;
/// This class manages interaction with the ImGui interface. /// This class manages interaction with the ImGui interface.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal class InterfaceManager : IDisposable, IServiceType internal class InterfaceManager : IInternalDisposableService
{ {
/// <summary> /// <summary>
/// The default font size, in points. /// The default font size, in points.
@ -69,10 +70,13 @@ internal class InterfaceManager : IDisposable, IServiceType
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly WndProcHookManager wndProcHookManager = Service<WndProcHookManager>.Get(); private readonly WndProcHookManager wndProcHookManager = Service<WndProcHookManager>.Get();
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get();
private readonly SwapChainVtableResolver address = new(); private readonly SwapChainVtableResolver address = new();
private readonly Hook<SetCursorDelegate> setCursorHook;
private RawDX11Scene? scene; private RawDX11Scene? scene;
private Hook<SetCursorDelegate>? setCursorHook;
private Hook<PresentDelegate>? presentHook; private Hook<PresentDelegate>? presentHook;
private Hook<ResizeBuffersDelegate>? resizeBuffersHook; private Hook<ResizeBuffersDelegate>? resizeBuffersHook;
@ -87,8 +91,6 @@ internal class InterfaceManager : IDisposable, IServiceType
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private InterfaceManager() private InterfaceManager()
{ {
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(
null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
} }
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] [UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@ -233,25 +235,45 @@ internal class InterfaceManager : IDisposable, IServiceType
/// <summary> /// <summary>
/// Dispose of managed and unmanaged resources. /// Dispose of managed and unmanaged resources.
/// </summary> /// </summary>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
if (Service<Framework>.GetNullable() is { } framework) // Unload hooks from the framework thread if possible.
framework.RunOnFrameworkThread(Disposer).Wait(); // We're currently off the framework thread, as this function can only be called from
else // ServiceManager.UnloadAllServices, which is called from EntryPoint.RunThread.
Disposer(); // 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; return;
void Disposer() void ClearHooks()
{ {
this.setCursorHook.Dispose(); this.wndProcHookManager.PreWndProc -= this.WndProcHookManagerOnPreWndProc;
this.presentHook?.Dispose(); Interlocked.Exchange(ref this.setCursorHook, null)?.Dispose();
this.resizeBuffersHook?.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.")] "InterfaceManager accepts event registration and stuff even when the game window is not ready.")]
private void ContinueConstruction( private void ContinueConstruction(
TargetSigScanner sigScanner, TargetSigScanner sigScanner,
Framework framework,
FontAtlasFactory fontAtlasFactory) FontAtlasFactory fontAtlasFactory)
{ {
this.dalamudAtlas = fontAtlasFactory this.dalamudAtlas = fontAtlasFactory
@ -731,7 +752,7 @@ internal class InterfaceManager : IDisposable, IServiceType
this.DefaultFontHandle.ImFontChanged += (_, font) => this.DefaultFontHandle.ImFontChanged += (_, font) =>
{ {
var fontLocked = font.NewRef(); var fontLocked = font.NewRef();
Service<Framework>.Get().RunOnFrameworkThread( this.framework.RunOnFrameworkThread(
() => () =>
{ {
// Update the ImGui default font. // Update the ImGui default font.
@ -765,6 +786,7 @@ internal class InterfaceManager : IDisposable, IServiceType
Log.Error(ex, "Could not enable immersive mode"); Log.Error(ex, "Could not enable immersive mode");
} }
this.setCursorHook = Hook<SetCursorDelegate>.FromImport(null, "user32.dll", "SetCursor", 0, this.SetCursorDetour);
this.presentHook = Hook<PresentDelegate>.FromAddress(this.address.Present, this.PresentDetour); this.presentHook = Hook<PresentDelegate>.FromAddress(this.address.Present, this.PresentDetour);
this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.FromAddress(this.address.ResizeBuffers, this.ResizeBuffersDetour); this.resizeBuffersHook = Hook<ResizeBuffersDelegate>.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) if (this.lastWantCapture && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor)
return IntPtr.Zero; return IntPtr.Zero;
return this.setCursorHook.IsDisposed return this.setCursorHook?.IsDisposed is not false
? User32.SetCursor(new(hCursor, false)).DangerousGetHandle() ? User32.SetCursor(new(hCursor, false)).DangerousGetHandle()
: this.setCursorHook.Original(hCursor); : this.setCursorHook.Original(hCursor);
} }

View file

@ -27,7 +27,7 @@ namespace Dalamud.Interface.Internal;
[ResolveVia<ITextureProvider>] [ResolveVia<ITextureProvider>]
[ResolveVia<ITextureSubstitutionProvider>] [ResolveVia<ITextureSubstitutionProvider>]
#pragma warning restore SA1015 #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 IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex";
private const string HighResolutionIconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}_hr1.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
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.fallbackTextureWrap?.Dispose(); this.fallbackTextureWrap?.Dispose();
this.framework.Update -= this.FrameworkOnUpdate; this.framework.Update -= this.FrameworkOnUpdate;

View file

@ -74,7 +74,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget
this.standardEnabled = false; this.standardEnabled = false;
if (!this.rawEnabled) if (!this.rawEnabled)
{ {
this.scoped.Dispose(); ((IInternalDisposableService)this.scoped).DisposeService();
this.scoped = null; this.scoped = null;
} }
} }
@ -105,7 +105,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget
this.rawEnabled = false; this.rawEnabled = false;
if (!this.standardEnabled) if (!this.standardEnabled)
{ {
this.scoped.Dispose(); ((IInternalDisposableService)this.scoped).DisposeService();
this.scoped = null; this.scoped = null;
} }
} }
@ -135,7 +135,7 @@ internal class GameInventoryTestWidget : IDataWindowWidget
{ {
if (ImGui.Button("Disable##all-disable")) if (ImGui.Button("Disable##all-disable"))
{ {
this.scoped?.Dispose(); ((IInternalDisposableService)this.scoped)?.DisposeService();
this.scoped = null; this.scoped = null;
this.standardEnabled = this.rawEnabled = false; this.standardEnabled = this.rawEnabled = false;
} }

View file

@ -21,7 +21,7 @@ namespace Dalamud.Interface.Internal.Windows;
/// A cache for plugin icons and images. /// A cache for plugin icons and images.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class PluginImageCache : IDisposable, IServiceType internal class PluginImageCache : IInternalDisposableService
{ {
/// <summary> /// <summary>
/// Maximum plugin image width. /// Maximum plugin image width.
@ -136,7 +136,7 @@ internal class PluginImageCache : IDisposable, IServiceType
this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall, this.EmptyTexture); this.dalamudAssetManager.GetDalamudTextureWrap(DalamudAsset.LogoSmall, this.EmptyTexture);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.cancelToken.Cancel(); this.cancelToken.Cancel();
this.downloadQueue.CompleteAdding(); this.downloadQueue.CompleteAdding();

View file

@ -204,12 +204,12 @@ internal sealed partial class FontAtlasFactory
{ {
while (this.IsBuildInProgress) while (this.IsBuildInProgress)
await Task.Delay(100); await Task.Delay(100);
this.Garbage.Dispose(); this.Clear();
}); });
} }
else else
{ {
this.Garbage.Dispose(); this.Clear();
} }
return newRefCount; return newRefCount;
@ -227,6 +227,20 @@ internal sealed partial class FontAtlasFactory
var axisSubstance = this.Substances.OfType<GamePrebakedFontHandle.HandleSubstance>().Single(); var axisSubstance = this.Substances.OfType<GamePrebakedFontHandle.HandleSubstance>().Single();
return new(factory, this, axisSubstance, isAsync) { BuildStep = FontAtlasBuildStep.PreBuild }; 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 private class DalamudFontAtlas : IFontAtlas, DisposeSafety.IDisposeCallback
@ -547,13 +561,13 @@ internal sealed partial class FontAtlasFactory
{ {
if (this.buildIndex != rebuildIndex) if (this.buildIndex != rebuildIndex)
{ {
data.ExplicitDisposeIgnoreExceptions(); data.Release();
return; return;
} }
var prevBuiltData = this.builtData; var prevBuiltData = this.builtData;
this.builtData = data; this.builtData = data;
prevBuiltData.ExplicitDisposeIgnoreExceptions(); prevBuiltData?.Release();
this.buildTask = EmptyTask; this.buildTask = EmptyTask;
fontsAndLocks.EnsureCapacity(data.Substances.Sum(x => x.RelevantHandles.Count)); fontsAndLocks.EnsureCapacity(data.Substances.Sum(x => x.RelevantHandles.Count));

View file

@ -31,7 +31,7 @@ namespace Dalamud.Interface.ManagedFontAtlas.Internals;
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal sealed partial class FontAtlasFactory internal sealed partial class FontAtlasFactory
: IServiceType, GamePrebakedFontHandle.IGameFontTextureProvider, IDisposable : IInternalDisposableService, GamePrebakedFontHandle.IGameFontTextureProvider
{ {
private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new(); private readonly DisposeSafety.ScopedFinalizer scopedFinalizer = new();
private readonly CancellationTokenSource cancellationTokenSource = new(); private readonly CancellationTokenSource cancellationTokenSource = new();
@ -161,7 +161,7 @@ internal sealed partial class FontAtlasFactory
this.dalamudAssetManager.IsStreamImmediatelyAvailable(DalamudAsset.LodestoneGameSymbol); this.dalamudAssetManager.IsStreamImmediatelyAvailable(DalamudAsset.LodestoneGameSymbol);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.cancellationTokenSource.Cancel(); this.cancellationTokenSource.Cancel();
this.scopedFinalizer.Dispose(); this.scopedFinalizer.Dispose();

View file

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
@ -291,11 +292,15 @@ internal abstract class FontHandle : IFontHandle
{ {
if (disposing) if (disposing)
{ {
if (Interlocked.Exchange(ref this.manager, null) is not { } managerToDisassociate)
return;
if (this.pushedFonts.Count > 0) if (this.pushedFonts.Count > 0)
Log.Warning($"{nameof(IFontHandle)}.{nameof(IDisposable.Dispose)}: fonts were still in a stack."); 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?.InvokeSafely();
this.Disposed = null;
this.ImFontChanged = null; this.ImFontChanged = null;
} }
} }

View file

@ -193,7 +193,7 @@ internal class TitleScreenMenu : IServiceType, ITitleScreenMenu
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<ITitleScreenMenu>] [ResolveVia<ITitleScreenMenu>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class TitleScreenMenuPluginScoped : IDisposable, IServiceType, ITitleScreenMenu internal class TitleScreenMenuPluginScoped : IInternalDisposableService, ITitleScreenMenu
{ {
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly TitleScreenMenu titleScreenMenuService = Service<TitleScreenMenu>.Get(); private readonly TitleScreenMenu titleScreenMenuService = Service<TitleScreenMenu>.Get();
@ -204,7 +204,7 @@ internal class TitleScreenMenuPluginScoped : IDisposable, IServiceType, ITitleSc
public IReadOnlyList<TitleScreenMenuEntry>? Entries => this.titleScreenMenuService.Entries; public IReadOnlyList<TitleScreenMenuEntry>? Entries => this.titleScreenMenuService.Entries;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
foreach (var entry in this.pluginEntries) foreach (var entry in this.pluginEntries)
{ {

View file

@ -17,6 +17,7 @@ using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Plugin;
using Dalamud.Plugin.Internal.Types; using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
@ -605,6 +606,10 @@ public sealed class UiBuilder : IDisposable
} }
} }
/// <summary>Clean up resources allocated by this instance of <see cref="UiBuilder"/>.</summary>
/// <remarks>Dalamud internal use only.</remarks>
internal void DisposeInternal() => this.scopedFinalizer.Dispose();
/// <summary> /// <summary>
/// Open the registered configuration UI, if it exists. /// Open the registered configuration UI, if it exists.
/// </summary> /// </summary>

View file

@ -96,6 +96,17 @@ internal class ServiceScopeImpl : IServiceScope
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
foreach (var createdObject in this.scopeCreatedObjects.OfType<IDisposable>()) createdObject.Dispose(); foreach (var createdObject in this.scopeCreatedObjects)
{
switch (createdObject)
{
case IInternalDisposableService d:
d.DisposeService();
break;
case IDisposable d:
d.Dispose();
break;
}
}
} }
} }

View file

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

View file

@ -17,7 +17,7 @@ namespace Dalamud.Logging;
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IPluginLog>] [ResolveVia<IPluginLog>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal class ScopedPluginLogService : IServiceType, IPluginLog, IDisposable internal class ScopedPluginLogService : IServiceType, IPluginLog
{ {
private readonly LocalPlugin localPlugin; private readonly LocalPlugin localPlugin;
@ -53,12 +53,6 @@ internal class ScopedPluginLogService : IServiceType, IPluginLog, IDisposable
/// </summary> /// </summary>
public ILogger Logger { get; } public ILogger Logger { get; }
/// <inheritdoc />
public void Dispose()
{
GC.SuppressFinalize(this);
}
/// <inheritdoc /> /// <inheritdoc />
public void Fatal(string messageTemplate, params object[] values) => public void Fatal(string messageTemplate, params object[] values) =>
this.Write(LogEventLevel.Fatal, null, messageTemplate, values); this.Write(LogEventLevel.Fatal, null, messageTemplate, values);

View file

@ -12,7 +12,7 @@ namespace Dalamud.Networking.Http;
/// awareness. /// awareness.
/// </summary> /// </summary>
[ServiceManager.BlockingEarlyLoadedService] [ServiceManager.BlockingEarlyLoadedService]
internal class HappyHttpClient : IDisposable, IServiceType internal class HappyHttpClient : IInternalDisposableService
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HappyHttpClient"/> class. /// Initializes a new instance of the <see cref="HappyHttpClient"/> class.
@ -58,7 +58,7 @@ internal class HappyHttpClient : IDisposable, IServiceType
public HappyEyeballsCallback SharedHappyEyeballsCallback { get; } public HappyEyeballsCallback SharedHappyEyeballsCallback { get; }
/// <inheritdoc/> /// <inheritdoc/>
void IDisposable.Dispose() void IInternalDisposableService.DisposeService()
{ {
this.SharedHttpClient.Dispose(); this.SharedHttpClient.Dispose();
this.SharedHappyEyeballsCallback.Dispose(); this.SharedHappyEyeballsCallback.Dispose();

View file

@ -452,26 +452,28 @@ public sealed class DalamudPluginInterface : IDisposable
#endregion #endregion
/// <summary> /// <inheritdoc cref="Dispose"/>
/// Unregister your plugin and dispose all references.
/// </summary>
void IDisposable.Dispose() void IDisposable.Dispose()
{ {
this.UiBuilder.ExplicitDispose();
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName);
Service<Localization>.Get().LocalizationChanged -= this.OnLocalizationChanged;
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
} }
/// <summary> /// <summary>This function will do nothing. Dalamud will dispose this object on plugin unload.</summary>
/// Obsolete implicit dispose implementation. Should not be used. [Obsolete("This function will do nothing. Dalamud will dispose this object on plugin unload.", true)]
/// </summary>
[Obsolete("Do not dispose \"DalamudPluginInterface\".", true)]
public void Dispose() public void Dispose()
{ {
// ignored // ignored
} }
/// <summary>Unregister the plugin and dispose all references.</summary>
/// <remarks>Dalamud internal use only.</remarks>
internal void DisposeInternal()
{
Service<ChatGui>.Get().RemoveChatLinkHandler(this.plugin.InternalName);
Service<Localization>.Get().LocalizationChanged -= this.OnLocalizationChanged;
Service<DalamudConfiguration>.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved;
this.UiBuilder.DisposeInternal();
}
/// <summary> /// <summary>
/// Dispatch the active plugins changed event. /// Dispatch the active plugins changed event.
/// </summary> /// </summary>

View file

@ -55,7 +55,7 @@ namespace Dalamud.Plugin.Internal;
[InherentDependency<DataShare>] [InherentDependency<DataShare>]
#pragma warning restore SA1015 #pragma warning restore SA1015
internal partial class PluginManager : IDisposable, IServiceType internal partial class PluginManager : IInternalDisposableService
{ {
/// <summary> /// <summary>
/// Default time to wait between plugin unload and plugin assembly unload. /// Default time to wait between plugin unload and plugin assembly unload.
@ -370,7 +370,7 @@ internal partial class PluginManager : IDisposable, IServiceType
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
var disposablePlugins = var disposablePlugins =
this.installedPluginsList.Where(plugin => plugin.State is PluginState.Loaded or PluginState.LoadError).ToArray(); 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. // Now that we've waited enough, dispose the whole plugin.
// Since plugins should have been unloaded above, this should be done quickly. // Since plugins should have been unloaded above, this should be done quickly.
foreach (var plugin in disposablePlugins) 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(); this.assemblyLocationMonoHook?.Dispose();

View file

@ -16,7 +16,7 @@ namespace Dalamud.Plugin.Internal.Profiles;
/// Service responsible for profile-related chat commands. /// Service responsible for profile-related chat commands.
/// </summary> /// </summary>
[ServiceManager.EarlyLoadedService] [ServiceManager.EarlyLoadedService]
internal class ProfileCommandHandler : IServiceType, IDisposable internal class ProfileCommandHandler : IInternalDisposableService
{ {
private readonly CommandManager cmd; private readonly CommandManager cmd;
private readonly ProfileManager profileManager; private readonly ProfileManager profileManager;
@ -69,7 +69,7 @@ internal class ProfileCommandHandler : IServiceType, IDisposable
} }
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
this.cmd.RemoveHandler("/xlenablecollection"); this.cmd.RemoveHandler("/xlenablecollection");
this.cmd.RemoveHandler("/xldisablecollection"); this.cmd.RemoveHandler("/xldisablecollection");

View file

@ -240,7 +240,7 @@ internal class LocalPlugin : IDisposable
this.instance = null; this.instance = null;
} }
this.DalamudInterface?.ExplicitDispose(); this.DalamudInterface?.DisposeInternal();
this.DalamudInterface = null; this.DalamudInterface = null;
this.ServiceScope?.Dispose(); this.ServiceScope?.Dispose();
@ -426,7 +426,7 @@ internal class LocalPlugin : IDisposable
if (this.instance == null) if (this.instance == null)
{ {
this.State = PluginState.LoadError; this.State = PluginState.LoadError;
this.DalamudInterface.ExplicitDispose(); this.DalamudInterface.DisposeInternal();
Log.Error( Log.Error(
$"Error while loading {this.Name}, failed to bind and call the plugin constructor"); $"Error while loading {this.Name}, failed to bind and call the plugin constructor");
return; return;
@ -499,7 +499,7 @@ internal class LocalPlugin : IDisposable
this.instance = null; this.instance = null;
this.DalamudInterface?.ExplicitDispose(); this.DalamudInterface?.DisposeInternal();
this.DalamudInterface = null; this.DalamudInterface = null;
this.ServiceScope?.Dispose(); this.ServiceScope?.Dispose();

View file

@ -3,6 +3,7 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -175,7 +176,8 @@ internal static class ServiceManager
foreach (var serviceType in GetConcreteServiceTypes()) foreach (var serviceType in GetConcreteServiceTypes())
{ {
var serviceKind = serviceType.GetServiceKind(); 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 // Let IoC know about the interfaces this service implements
serviceContainer.RegisterInterfaces(serviceType); serviceContainer.RegisterInterfaces(serviceType);
@ -514,6 +516,44 @@ internal static class ServiceManager
return ServiceKind.ProvidedService; return ServiceKind.ProvidedService;
} }
/// <summary>Validate service type contracts, and throws exceptions accordingly.</summary>
/// <param name="serviceType">An instance of <see cref="Type"/> that is supposed to be a service type.</param>
/// <remarks>Does nothing on non-debug builds.</remarks>
[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
}
/// <summary> /// <summary>
/// Indicates that this constructor will be called for early initialization. /// Indicates that this constructor will be called for early initialization.
/// </summary> /// </summary>

View file

@ -65,6 +65,12 @@ internal static class Service<T> where T : IServiceType
None, None,
} }
/// <summary>Does nothing.</summary>
/// <remarks>Used to invoke the static ctor.</remarks>
public static void Nop()
{
}
/// <summary> /// <summary>
/// Sets the type in the service locator to the given object. /// Sets the type in the service locator to the given object.
/// </summary> /// </summary>
@ -72,6 +78,8 @@ internal static class Service<T> where T : IServiceType
public static void Provide(T obj) public static void Provide(T obj)
{ {
ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Provided", typeof(T).Name);
if (obj is IPublicDisposableService pds)
pds.MarkDisposeOnlyFromService();
instanceTcs.SetResult(obj); instanceTcs.SetResult(obj);
} }
@ -297,23 +305,26 @@ internal static class Service<T> where T : IServiceType
if (!instanceTcs.Task.IsCompletedSuccessfully) if (!instanceTcs.Task.IsCompletedSuccessfully)
return; return;
var instance = instanceTcs.Task.Result; switch (instanceTcs.Task.Result)
if (instance is IDisposable disposable)
{ {
case IInternalDisposableService d:
ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Disposing", typeof(T).Name);
try try
{ {
disposable.Dispose(); d.DisposeService();
ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Disposed", typeof(T).Name);
} }
catch (Exception e) catch (Exception e)
{ {
ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name); ServiceManager.Log.Warning(e, "Service<{0}>: Dispose failure", typeof(T).Name);
} }
}
else break;
{
default:
ServiceManager.CheckServiceTypeContracts(typeof(T));
ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name); ServiceManager.Log.Debug("Service<{0}>: Unset", typeof(T).Name);
break;
} }
instanceTcs = new TaskCompletionSource<T>(); instanceTcs = new TaskCompletionSource<T>();

View file

@ -27,7 +27,7 @@ namespace Dalamud.Storage.Assets;
#pragma warning disable SA1015 #pragma warning disable SA1015
[ResolveVia<IDalamudAssetManager>] [ResolveVia<IDalamudAssetManager>]
#pragma warning restore SA1015 #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 DownloadAttemptCount = 10;
private const int RenameAttemptCount = 10; private const int RenameAttemptCount = 10;
@ -67,7 +67,13 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
.Where(x => x.GetAttribute<DalamudAssetAttribute>()?.Required is true) .Where(x => x.GetAttribute<DalamudAssetAttribute>()?.Required is true)
.Select(this.CreateStreamAsync) .Select(this.CreateStreamAsync)
.Select(x => x.ToContentDisposedTask())) .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."); "Prevent Dalamud from loading more stuff, until we've ensured that all required assets are available.");
Task.WhenAll( Task.WhenAll(
@ -83,7 +89,7 @@ internal sealed class DalamudAssetManager : IServiceType, IDisposable, IDalamudA
public IDalamudTextureWrap Empty4X4 => this.GetDalamudTextureWrap(DalamudAsset.Empty4X4); public IDalamudTextureWrap Empty4X4 => this.GetDalamudTextureWrap(DalamudAsset.Empty4X4);
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() void IInternalDisposableService.DisposeService()
{ {
lock (this.syncRoot) lock (this.syncRoot)
{ {

View file

@ -22,17 +22,22 @@ namespace Dalamud.Storage;
/// This is not an early-loaded service, as it is needed before they are initialized. /// This is not an early-loaded service, as it is needed before they are initialized.
/// </remarks> /// </remarks>
[ServiceManager.ProvidedService] [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 static readonly ModuleLog Log = new("VFS");
private readonly object syncRoot = new(); private readonly object syncRoot = new();
private SQLiteConnection? db; private SQLiteConnection? db;
private bool isService;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ReliableFileStorage"/> class. /// Initializes a new instance of the <see cref="ReliableFileStorage"/> class.
/// </summary> /// </summary>
/// <param name="vfsDbPath">Path to the VFS.</param> /// <param name="vfsDbPath">Path to the VFS.</param>
[Obsolete("Dalamud internal use only.", false)]
[Api10ToDo("Make internal, and remove #pragma guard from the caller.")]
public ReliableFileStorage(string vfsDbPath) public ReliableFileStorage(string vfsDbPath)
{ {
var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db"); var databasePath = Path.Combine(vfsDbPath, "dalamudVfs.db");
@ -288,9 +293,20 @@ public class ReliableFileStorage : IServiceType, IDisposable
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
this.db?.Dispose(); if (!this.isService)
this.DisposeCore();
} }
/// <inheritdoc/>
void IInternalDisposableService.DisposeService()
{
if (this.isService)
this.DisposeCore();
}
/// <inheritdoc/>
void IPublicDisposableService.MarkDisposeOnlyFromService() => this.isService = true;
/// <summary> /// <summary>
/// Replace possible non-portable parts of a path with portable versions. /// Replace possible non-portable parts of a path with portable versions.
/// </summary> /// </summary>
@ -312,6 +328,8 @@ public class ReliableFileStorage : IServiceType, IDisposable
this.db.CreateTable<DbFile>(); this.db.CreateTable<DbFile>();
} }
private void DisposeCore() => this.db?.Dispose();
private class DbFile private class DbFile
{ {
[PrimaryKey] [PrimaryKey]

View file

@ -70,7 +70,16 @@ public static class DisposeSafety
r => r =>
{ {
if (!r.IsCompletedSuccessfully) if (!r.IsCompletedSuccessfully)
return ignoreAllExceptions ? Task.CompletedTask : r; {
if (ignoreAllExceptions)
{
_ = r.Exception;
return Task.CompletedTask;
}
return r;
}
try try
{ {
r.Result.Dispose(); r.Result.Dispose();

View file

@ -19,7 +19,6 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Logging.Internal;
using ImGuiNET; using ImGuiNET;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Serilog; using Serilog;
@ -639,42 +638,6 @@ public static class Util
throw new Win32Exception(); throw new Win32Exception();
} }
/// <summary>
/// Dispose this object.
/// </summary>
/// <param name="obj">The object to dispose.</param>
/// <typeparam name="T">The type of object to dispose.</typeparam>
internal static void ExplicitDispose<T>(this T obj) where T : IDisposable
{
obj.Dispose();
}
/// <summary>
/// Dispose this object.
/// </summary>
/// <param name="obj">The object to dispose.</param>
/// <param name="logMessage">Log message to print, if specified and an error occurs.</param>
/// <param name="moduleLog">Module logger, if any.</param>
/// <typeparam name="T">The type of object to dispose.</typeparam>
internal static void ExplicitDisposeIgnoreExceptions<T>(
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);
}
}
/// <summary> /// <summary>
/// Gets a random, inoffensive, human-friendly string. /// Gets a random, inoffensive, human-friendly string.
/// </summary> /// </summary>