diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 205681cb8..7e166d8b3 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -112,10 +112,6 @@ - - - - diff --git a/Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs b/Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs new file mode 100644 index 000000000..14def2036 --- /dev/null +++ b/Dalamud/Game/Addon/AddonLifecyclePooledArgs.cs @@ -0,0 +1,107 @@ +using System.Runtime.CompilerServices; +using System.Threading; + +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; + +namespace Dalamud.Game.Addon; + +/// Argument pool for Addon Lifecycle services. +[ServiceManager.EarlyLoadedService] +internal sealed class AddonLifecyclePooledArgs : IServiceType +{ + private readonly AddonSetupArgs?[] addonSetupArgPool = new AddonSetupArgs?[64]; + private readonly AddonFinalizeArgs?[] addonFinalizeArgPool = new AddonFinalizeArgs?[64]; + private readonly AddonDrawArgs?[] addonDrawArgPool = new AddonDrawArgs?[64]; + private readonly AddonUpdateArgs?[] addonUpdateArgPool = new AddonUpdateArgs?[64]; + private readonly AddonRefreshArgs?[] addonRefreshArgPool = new AddonRefreshArgs?[64]; + private readonly AddonRequestedUpdateArgs?[] addonRequestedUpdateArgPool = new AddonRequestedUpdateArgs?[64]; + private readonly AddonReceiveEventArgs?[] addonReceiveEventArgPool = new AddonReceiveEventArgs?[64]; + + [ServiceManager.ServiceConstructor] + private AddonLifecyclePooledArgs() + { + } + + /// Rents an instance of an argument. + /// The rented instance. + /// The returner. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledEntry Rent(out AddonSetupArgs arg) => new(out arg, this.addonSetupArgPool); + + /// Rents an instance of an argument. + /// The rented instance. + /// The returner. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledEntry Rent(out AddonFinalizeArgs arg) => new(out arg, this.addonFinalizeArgPool); + + /// Rents an instance of an argument. + /// The rented instance. + /// The returner. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledEntry Rent(out AddonDrawArgs arg) => new(out arg, this.addonDrawArgPool); + + /// Rents an instance of an argument. + /// The rented instance. + /// The returner. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledEntry Rent(out AddonUpdateArgs arg) => new(out arg, this.addonUpdateArgPool); + + /// Rents an instance of an argument. + /// The rented instance. + /// The returner. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledEntry Rent(out AddonRefreshArgs arg) => new(out arg, this.addonRefreshArgPool); + + /// Rents an instance of an argument. + /// The rented instance. + /// The returner. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledEntry Rent(out AddonRequestedUpdateArgs arg) => + new(out arg, this.addonRequestedUpdateArgPool); + + /// Rents an instance of an argument. + /// The rented instance. + /// The returner. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PooledEntry Rent(out AddonReceiveEventArgs arg) => + new(out arg, this.addonReceiveEventArgPool); + + /// Returns the object to the pool on dispose. + /// The type. + public readonly ref struct PooledEntry + where T : AddonArgs, new() + { + private readonly Span pool; + private readonly T obj; + + /// Initializes a new instance of the struct. + /// An instance of the argument. + /// The pool to rent from and return to. + public PooledEntry(out T arg, Span pool) + { + this.pool = pool; + foreach (ref var item in pool) + { + if (Interlocked.Exchange(ref item, null) is { } v) + { + this.obj = arg = v; + return; + } + } + + this.obj = arg = new(); + } + + /// Returns the item to the pool. + public void Dispose() + { + var tmp = this.obj; + foreach (ref var item in this.pool) + { + if (Interlocked.Exchange(ref item, tmp) is not { } tmp2) + return; + tmp = tmp2; + } + } + } +} diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs index 4231b0d09..8ee09bed8 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManager.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs @@ -18,7 +18,7 @@ namespace Dalamud.Game.Addon.Events; /// Service provider for addon event management. /// [InterfaceVersion("1.0")] -[ServiceManager.BlockingEarlyLoadedService] +[ServiceManager.EarlyLoadedService] internal unsafe class AddonEventManager : IDisposable, IServiceType { /// diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs index 4ab3de5ca..1095202cc 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonArgs.cs @@ -44,10 +44,10 @@ public abstract unsafe class AddonArgs get => this.addon; set { - if (this.addon == value) - return; - this.addon = value; + + // Note: always clear addonName on updating the addon being pointed. + // Same address may point to a different addon. this.addonName = null; } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs index beaab7fcd..37f12ce3a 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -19,7 +18,7 @@ namespace Dalamud.Game.Addon.Lifecycle; /// This class provides events for in-game addon lifecycles. /// [InterfaceVersion("1.0")] -[ServiceManager.BlockingEarlyLoadedService] +[ServiceManager.EarlyLoadedService] internal unsafe class AddonLifecycle : IDisposable, IServiceType { private static readonly ModuleLog Log = new("AddonLifecycle"); @@ -27,6 +26,9 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType [ServiceManager.ServiceDependency] private readonly Framework framework = Service.Get(); + [ServiceManager.ServiceDependency] + private readonly AddonLifecyclePooledArgs argsPool = Service.Get(); + private readonly nint disallowedReceiveEventAddress; private readonly AddonLifecycleAddressResolver address; @@ -38,18 +40,6 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private readonly Hook onAddonRefreshHook; private readonly CallHook onAddonRequestedUpdateHook; - // Note: these can be sourced from ObjectPool of appropriate types instead, but since we don't import that NuGet - // package, and these events are always called from the main thread, this is fine. -#pragma warning disable CS0618 // Type or member is obsolete - // TODO: turn constructors of these internal - private readonly AddonSetupArgs recyclingSetupArgs = new(); - private readonly AddonFinalizeArgs recyclingFinalizeArgs = new(); - private readonly AddonDrawArgs recyclingDrawArgs = new(); - private readonly AddonUpdateArgs recyclingUpdateArgs = new(); - private readonly AddonRefreshArgs recyclingRefreshArgs = new(); - private readonly AddonRequestedUpdateArgs recyclingRequestedUpdateArgs = new(); -#pragma warning restore CS0618 // Type or member is obsolete - [ServiceManager.ServiceConstructor] private AddonLifecycle(TargetSigScanner sigScanner) { @@ -253,12 +243,13 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration."); } - this.recyclingSetupArgs.AddonInternal = (nint)addon; - this.recyclingSetupArgs.AtkValueCount = valueCount; - this.recyclingSetupArgs.AtkValues = (nint)values; - this.InvokeListenersSafely(AddonEvent.PreSetup, this.recyclingSetupArgs); - valueCount = this.recyclingSetupArgs.AtkValueCount; - values = (AtkValue*)this.recyclingSetupArgs.AtkValues; + using var returner = this.argsPool.Rent(out AddonSetupArgs arg); + arg.AddonInternal = (nint)addon; + arg.AtkValueCount = valueCount; + arg.AtkValues = (nint)values; + this.InvokeListenersSafely(AddonEvent.PreSetup, arg); + valueCount = arg.AtkValueCount; + values = (AtkValue*)arg.AtkValues; try { @@ -269,7 +260,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType Log.Error(e, "Caught exception when calling original AddonSetup. This may be a bug in the game or another plugin hooking this method."); } - this.InvokeListenersSafely(AddonEvent.PostSetup, this.recyclingSetupArgs); + this.InvokeListenersSafely(AddonEvent.PostSetup, arg); } private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase) @@ -284,8 +275,9 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal."); } - this.recyclingFinalizeArgs.AddonInternal = (nint)atkUnitBase[0]; - this.InvokeListenersSafely(AddonEvent.PreFinalize, this.recyclingFinalizeArgs); + using var returner = this.argsPool.Rent(out AddonFinalizeArgs arg); + arg.AddonInternal = (nint)atkUnitBase[0]; + this.InvokeListenersSafely(AddonEvent.PreFinalize, arg); try { @@ -299,8 +291,9 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType private void OnAddonDraw(AtkUnitBase* addon) { - this.recyclingDrawArgs.AddonInternal = (nint)addon; - this.InvokeListenersSafely(AddonEvent.PreDraw, this.recyclingDrawArgs); + using var returner = this.argsPool.Rent(out AddonDrawArgs arg); + arg.AddonInternal = (nint)addon; + this.InvokeListenersSafely(AddonEvent.PreDraw, arg); try { @@ -311,14 +304,15 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType Log.Error(e, "Caught exception when calling original AddonDraw. This may be a bug in the game or another plugin hooking this method."); } - this.InvokeListenersSafely(AddonEvent.PostDraw, this.recyclingDrawArgs); + this.InvokeListenersSafely(AddonEvent.PostDraw, arg); } private void OnAddonUpdate(AtkUnitBase* addon, float delta) { - this.recyclingUpdateArgs.AddonInternal = (nint)addon; - this.recyclingUpdateArgs.TimeDeltaInternal = delta; - this.InvokeListenersSafely(AddonEvent.PreUpdate, this.recyclingUpdateArgs); + using var returner = this.argsPool.Rent(out AddonUpdateArgs arg); + arg.AddonInternal = (nint)addon; + arg.TimeDeltaInternal = delta; + this.InvokeListenersSafely(AddonEvent.PreUpdate, arg); try { @@ -329,19 +323,20 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType Log.Error(e, "Caught exception when calling original AddonUpdate. This may be a bug in the game or another plugin hooking this method."); } - this.InvokeListenersSafely(AddonEvent.PostUpdate, this.recyclingUpdateArgs); + this.InvokeListenersSafely(AddonEvent.PostUpdate, arg); } private byte OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values) { byte result = 0; - this.recyclingRefreshArgs.AddonInternal = (nint)addon; - this.recyclingRefreshArgs.AtkValueCount = valueCount; - this.recyclingRefreshArgs.AtkValues = (nint)values; - this.InvokeListenersSafely(AddonEvent.PreRefresh, this.recyclingRefreshArgs); - valueCount = this.recyclingRefreshArgs.AtkValueCount; - values = (AtkValue*)this.recyclingRefreshArgs.AtkValues; + using var returner = this.argsPool.Rent(out AddonRefreshArgs arg); + arg.AddonInternal = (nint)addon; + arg.AtkValueCount = valueCount; + arg.AtkValues = (nint)values; + this.InvokeListenersSafely(AddonEvent.PreRefresh, arg); + valueCount = arg.AtkValueCount; + values = (AtkValue*)arg.AtkValues; try { @@ -352,18 +347,19 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType Log.Error(e, "Caught exception when calling original AddonRefresh. This may be a bug in the game or another plugin hooking this method."); } - this.InvokeListenersSafely(AddonEvent.PostRefresh, this.recyclingRefreshArgs); + this.InvokeListenersSafely(AddonEvent.PostRefresh, arg); return result; } private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) { - this.recyclingRequestedUpdateArgs.AddonInternal = (nint)addon; - this.recyclingRequestedUpdateArgs.NumberArrayData = (nint)numberArrayData; - this.recyclingRequestedUpdateArgs.StringArrayData = (nint)stringArrayData; - this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, this.recyclingRequestedUpdateArgs); - numberArrayData = (NumberArrayData**)this.recyclingRequestedUpdateArgs.NumberArrayData; - stringArrayData = (StringArrayData**)this.recyclingRequestedUpdateArgs.StringArrayData; + using var returner = this.argsPool.Rent(out AddonRequestedUpdateArgs arg); + arg.AddonInternal = (nint)addon; + arg.NumberArrayData = (nint)numberArrayData; + arg.StringArrayData = (nint)stringArrayData; + this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, arg); + numberArrayData = (NumberArrayData**)arg.NumberArrayData; + stringArrayData = (StringArrayData**)arg.StringArrayData; try { @@ -374,7 +370,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType Log.Error(e, "Caught exception when calling original AddonRequestedUpdate. This may be a bug in the game or another plugin hooking this method."); } - this.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, this.recyclingRequestedUpdateArgs); + this.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, arg); } } diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs index 43aa71661..fd3b5d79d 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycleReceiveEventListener.cs @@ -16,12 +16,8 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable { private static readonly ModuleLog Log = new("AddonLifecycle"); - // Note: these can be sourced from ObjectPool of appropriate types instead, but since we don't import that NuGet - // package, and these events are always called from the main thread, this is fine. -#pragma warning disable CS0618 // Type or member is obsolete - // TODO: turn constructors of these internal - private readonly AddonReceiveEventArgs recyclingReceiveEventArgs = new(); -#pragma warning restore CS0618 // Type or member is obsolete + [ServiceManager.ServiceDependency] + private readonly AddonLifecyclePooledArgs argsPool = Service.Get(); /// /// Initializes a new instance of the class. @@ -82,16 +78,17 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable return; } - this.recyclingReceiveEventArgs.AddonInternal = (nint)addon; - this.recyclingReceiveEventArgs.AtkEventType = (byte)eventType; - this.recyclingReceiveEventArgs.EventParam = eventParam; - this.recyclingReceiveEventArgs.AtkEvent = (IntPtr)atkEvent; - this.recyclingReceiveEventArgs.Data = data; - this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, this.recyclingReceiveEventArgs); - eventType = (AtkEventType)this.recyclingReceiveEventArgs.AtkEventType; - eventParam = this.recyclingReceiveEventArgs.EventParam; - atkEvent = (AtkEvent*)this.recyclingReceiveEventArgs.AtkEvent; - data = this.recyclingReceiveEventArgs.Data; + using var returner = this.argsPool.Rent(out AddonReceiveEventArgs arg); + arg.AddonInternal = (nint)addon; + arg.AtkEventType = (byte)eventType; + arg.EventParam = eventParam; + arg.AtkEvent = (IntPtr)atkEvent; + arg.Data = data; + this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, arg); + eventType = (AtkEventType)arg.AtkEventType; + eventParam = arg.EventParam; + atkEvent = (AtkEvent*)arg.AtkEvent; + data = arg.Data; try { @@ -102,6 +99,6 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable Log.Error(e, "Caught exception when calling original AddonReceiveEvent. This may be a bug in the game or another plugin hooking this method."); } - this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PostReceiveEvent, this.recyclingReceiveEventArgs); + this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PostReceiveEvent, arg); } }