mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Reduce heap allocation every frame in AddonLifecycle (#1555)
This commit is contained in:
parent
711d5e2859
commit
0bfcc55774
11 changed files with 212 additions and 188 deletions
|
|
@ -14,22 +14,51 @@ public abstract unsafe class AddonArgs
|
|||
public const string InvalidAddon = "NullAddon";
|
||||
|
||||
private string? addonName;
|
||||
private IntPtr addon;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the addon this args referrers to.
|
||||
/// </summary>
|
||||
public string AddonName => this.GetAddonName();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to the addons AtkUnitBase.
|
||||
/// </summary>
|
||||
public nint Addon { get; init; }
|
||||
|
||||
public nint Addon
|
||||
{
|
||||
get => this.addon;
|
||||
internal set
|
||||
{
|
||||
if (this.addon == value)
|
||||
return;
|
||||
|
||||
this.addon = value;
|
||||
this.addonName = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of these args.
|
||||
/// </summary>
|
||||
public abstract AddonArgsType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if addon name matches the given span of char.
|
||||
/// </summary>
|
||||
/// <param name="name">The name to check.</param>
|
||||
/// <returns>Whether it is the case.</returns>
|
||||
internal bool IsAddon(ReadOnlySpan<char> name)
|
||||
{
|
||||
if (this.Addon == nint.Zero) return false;
|
||||
if (name.Length is 0 or > 0x20)
|
||||
return false;
|
||||
|
||||
var addonPointer = (AtkUnitBase*)this.Addon;
|
||||
if (addonPointer->Name is null) return false;
|
||||
|
||||
return MemoryHelper.EqualsZeroTerminatedString(name, (nint)addonPointer->Name, null, 0x20);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for ensuring the name of the addon is valid.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@
|
|||
/// <summary>
|
||||
/// Addon argument data for Draw events.
|
||||
/// </summary>
|
||||
public class AddonDrawArgs : AddonArgs
|
||||
public class AddonDrawArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Draw;
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonDrawArgs Clone() => (AddonDrawArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Addon argument data for ReceiveEvent events.
|
||||
/// </summary>
|
||||
public class AddonFinalizeArgs : AddonArgs
|
||||
public class AddonFinalizeArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Finalize;
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonFinalizeArgs Clone() => (AddonFinalizeArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,28 +3,34 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Addon argument data for ReceiveEvent events.
|
||||
/// </summary>
|
||||
public class AddonReceiveEventArgs : AddonArgs
|
||||
public class AddonReceiveEventArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.ReceiveEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AtkEventType for this event message.
|
||||
/// Gets or sets the AtkEventType for this event message.
|
||||
/// </summary>
|
||||
public byte AtkEventType { get; init; }
|
||||
public byte AtkEventType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the event id for this event message.
|
||||
/// Gets or sets the event id for this event message.
|
||||
/// </summary>
|
||||
public int EventParam { get; init; }
|
||||
public int EventParam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to an AtkEvent for this event message.
|
||||
/// Gets or sets the pointer to an AtkEvent for this event message.
|
||||
/// </summary>
|
||||
public nint AtkEvent { get; init; }
|
||||
public nint AtkEvent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pointer to a block of data for this event message.
|
||||
/// Gets or sets the pointer to a block of data for this event message.
|
||||
/// </summary>
|
||||
public nint Data { get; init; }
|
||||
public nint Data { get; set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonReceiveEventArgs Clone() => (AddonReceiveEventArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,23 +5,29 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Addon argument data for Refresh events.
|
||||
/// </summary>
|
||||
public class AddonRefreshArgs : AddonArgs
|
||||
public class AddonRefreshArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Refresh;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of AtkValues.
|
||||
/// Gets or sets the number of AtkValues.
|
||||
/// </summary>
|
||||
public uint AtkValueCount { get; init; }
|
||||
public uint AtkValueCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkValue array.
|
||||
/// Gets or sets the address of the AtkValue array.
|
||||
/// </summary>
|
||||
public nint AtkValues { get; init; }
|
||||
public nint AtkValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AtkValues in the form of a span.
|
||||
/// </summary>
|
||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonRefreshArgs Clone() => (AddonRefreshArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,24 @@
|
|||
/// <summary>
|
||||
/// Addon argument data for OnRequestedUpdate events.
|
||||
/// </summary>
|
||||
public class AddonRequestedUpdateArgs : AddonArgs
|
||||
public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.RequestedUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NumberArrayData** for this event.
|
||||
/// Gets or sets the NumberArrayData** for this event.
|
||||
/// </summary>
|
||||
public nint NumberArrayData { get; init; }
|
||||
public nint NumberArrayData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StringArrayData** for this event.
|
||||
/// Gets or sets the StringArrayData** for this event.
|
||||
/// </summary>
|
||||
public nint StringArrayData { get; init; }
|
||||
public nint StringArrayData { get; set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonRequestedUpdateArgs Clone() => (AddonRequestedUpdateArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,23 +5,29 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Addon argument data for Setup events.
|
||||
/// </summary>
|
||||
public class AddonSetupArgs : AddonArgs
|
||||
public class AddonSetupArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Setup;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of AtkValues.
|
||||
/// Gets or sets the number of AtkValues.
|
||||
/// </summary>
|
||||
public uint AtkValueCount { get; init; }
|
||||
public uint AtkValueCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the AtkValue array.
|
||||
/// Gets or sets the address of the AtkValue array.
|
||||
/// </summary>
|
||||
public nint AtkValues { get; init; }
|
||||
public nint AtkValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AtkValues in the form of a span.
|
||||
/// </summary>
|
||||
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount);
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonSetupArgs Clone() => (AddonSetupArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
|||
/// <summary>
|
||||
/// Addon argument data for Update events.
|
||||
/// </summary>
|
||||
public class AddonUpdateArgs : AddonArgs
|
||||
public class AddonUpdateArgs : AddonArgs, ICloneable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override AddonArgsType Type => AddonArgsType.Update;
|
||||
|
|
@ -11,5 +11,11 @@ public class AddonUpdateArgs : AddonArgs
|
|||
/// <summary>
|
||||
/// Gets the time since the last update.
|
||||
/// </summary>
|
||||
public float TimeDelta { get; init; }
|
||||
public float TimeDelta { get; internal set; }
|
||||
|
||||
/// <inheritdoc cref="ICloneable.Clone"/>
|
||||
public AddonUpdateArgs Clone() => (AddonUpdateArgs)this.MemberwiseClone();
|
||||
|
||||
/// <inheritdoc cref="Clone"/>
|
||||
object ICloneable.Clone() => this.Clone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||
using Dalamud.Hooking;
|
||||
|
|
@ -40,6 +41,15 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
private readonly ConcurrentBag<AddonLifecycleEventListener> newEventListeners = new();
|
||||
private readonly ConcurrentBag<AddonLifecycleEventListener> removeEventListeners = new();
|
||||
|
||||
// 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.
|
||||
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();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private AddonLifecycle(TargetSigScanner sigScanner)
|
||||
{
|
||||
|
|
@ -132,12 +142,27 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
/// </summary>
|
||||
/// <param name="eventType">Event Type.</param>
|
||||
/// <param name="args">AddonArgs.</param>
|
||||
internal void InvokeListeners(AddonEvent eventType, AddonArgs args)
|
||||
/// <param name="blame">What to blame on errors.</param>
|
||||
internal void InvokeListenersSafely(AddonEvent eventType, AddonArgs args, [CallerMemberName] string blame = "")
|
||||
{
|
||||
// Match on string.empty for listeners that want events for all addons.
|
||||
foreach (var listener in this.EventListeners.Where(listener => listener.EventType == eventType && (listener.AddonName == args.AddonName || listener.AddonName == string.Empty)))
|
||||
// Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better.
|
||||
foreach (var listener in this.EventListeners)
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
if (listener.EventType != eventType)
|
||||
continue;
|
||||
|
||||
// Match on string.empty for listeners that want events for all addons.
|
||||
if (!string.IsNullOrWhiteSpace(listener.AddonName) && !args.IsAddon(listener.AddonName))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
listener.FunctionDelegate.Invoke(eventType, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Exception in {blame} during {eventType} invoke.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -249,20 +274,13 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
{
|
||||
Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreSetup, new AddonSetupArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkValueCount = valueCount,
|
||||
AtkValues = (nint)values,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonSetup pre-setup invoke.");
|
||||
}
|
||||
|
||||
this.recyclingSetupArgs.Addon = (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;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -273,19 +291,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.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostSetup, new AddonSetupArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkValueCount = valueCount,
|
||||
AtkValues = (nint)values,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonSetup post-setup invoke.");
|
||||
}
|
||||
this.InvokeListenersSafely(AddonEvent.PostSetup, this.recyclingSetupArgs);
|
||||
}
|
||||
|
||||
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
||||
|
|
@ -299,15 +305,9 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
{
|
||||
Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreFinalize, new AddonFinalizeArgs { Addon = (nint)atkUnitBase[0] });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonFinalize pre-finalize invoke.");
|
||||
}
|
||||
|
||||
this.recyclingFinalizeArgs.Addon = (nint)atkUnitBase[0];
|
||||
this.InvokeListenersSafely(AddonEvent.PreFinalize, this.recyclingFinalizeArgs);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -321,14 +321,8 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
|
|||
|
||||
private void OnAddonDraw(AtkUnitBase* addon)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreDraw, new AddonDrawArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonDraw pre-draw invoke.");
|
||||
}
|
||||
this.recyclingDrawArgs.Addon = (nint)addon;
|
||||
this.InvokeListenersSafely(AddonEvent.PreDraw, this.recyclingDrawArgs);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -339,26 +333,14 @@ 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.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostDraw, new AddonDrawArgs { Addon = (nint)addon });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonDraw post-draw invoke.");
|
||||
}
|
||||
this.InvokeListenersSafely(AddonEvent.PostDraw, this.recyclingDrawArgs);
|
||||
}
|
||||
|
||||
private void OnAddonUpdate(AtkUnitBase* addon, float delta)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreUpdate, new AddonUpdateArgs { Addon = (nint)addon, TimeDelta = delta });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonUpdate pre-update invoke.");
|
||||
}
|
||||
this.recyclingUpdateArgs.Addon = (nint)addon;
|
||||
this.recyclingUpdateArgs.TimeDelta = delta;
|
||||
this.InvokeListenersSafely(AddonEvent.PreUpdate, this.recyclingUpdateArgs);
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -369,33 +351,19 @@ 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.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostUpdate, new AddonUpdateArgs { Addon = (nint)addon, TimeDelta = delta });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonUpdate post-update invoke.");
|
||||
}
|
||||
this.InvokeListenersSafely(AddonEvent.PostUpdate, this.recyclingUpdateArgs);
|
||||
}
|
||||
|
||||
private byte OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values)
|
||||
{
|
||||
byte result = 0;
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreRefresh, new AddonRefreshArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkValueCount = valueCount,
|
||||
AtkValues = (nint)values,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonRefresh pre-refresh invoke.");
|
||||
}
|
||||
|
||||
this.recyclingRefreshArgs.Addon = (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;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -406,38 +374,18 @@ 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.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostRefresh, new AddonRefreshArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkValueCount = valueCount,
|
||||
AtkValues = (nint)values,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonRefresh post-refresh invoke.");
|
||||
}
|
||||
|
||||
this.InvokeListenersSafely(AddonEvent.PostRefresh, this.recyclingRefreshArgs);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PreRequestedUpdate, new AddonRequestedUpdateArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
NumberArrayData = (nint)numberArrayData,
|
||||
StringArrayData = (nint)stringArrayData,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnRequestedUpdate pre-requestedUpdate invoke.");
|
||||
}
|
||||
this.recyclingRequestedUpdateArgs.Addon = (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;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -448,19 +396,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.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.InvokeListeners(AddonEvent.PostRequestedUpdate, new AddonRequestedUpdateArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
NumberArrayData = (nint)numberArrayData,
|
||||
StringArrayData = (nint)stringArrayData,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnRequestedUpdate post-requestedUpdate invoke.");
|
||||
}
|
||||
this.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, this.recyclingRequestedUpdateArgs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ 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.
|
||||
private readonly AddonReceiveEventArgs recyclingReceiveEventArgs = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AddonLifecycleReceiveEventListener"/> class.
|
||||
/// </summary>
|
||||
|
|
@ -74,22 +78,17 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
|
|||
this.Hook!.Original(addon, eventType, eventParam, atkEvent, data);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.AddonLifecycle.InvokeListeners(AddonEvent.PreReceiveEvent, new AddonReceiveEventArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkEventType = (byte)eventType,
|
||||
EventParam = eventParam,
|
||||
AtkEvent = (nint)atkEvent,
|
||||
Data = data,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnReceiveEvent pre-receiveEvent invoke.");
|
||||
}
|
||||
|
||||
this.recyclingReceiveEventArgs.Addon = (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;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -100,20 +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.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.AddonLifecycle.InvokeListeners(AddonEvent.PostReceiveEvent, new AddonReceiveEventArgs
|
||||
{
|
||||
Addon = (nint)addon,
|
||||
AtkEventType = (byte)eventType,
|
||||
EventParam = eventParam,
|
||||
AtkEvent = (nint)atkEvent,
|
||||
Data = data,
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Exception in OnAddonRefresh post-receiveEvent invoke.");
|
||||
}
|
||||
this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PostReceiveEvent, this.recyclingReceiveEventArgs);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,6 +163,38 @@ public static unsafe class MemoryHelper
|
|||
|
||||
#region ReadString
|
||||
|
||||
/// <summary>
|
||||
/// Compares if the given char span equals to the null-terminated string at <paramref name="memoryAddress"/>.
|
||||
/// </summary>
|
||||
/// <param name="charSpan">The character span.</param>
|
||||
/// <param name="memoryAddress">The address of null-terminated string.</param>
|
||||
/// <param name="encoding">The encoding of the null-terminated string.</param>
|
||||
/// <param name="maxLength">The maximum length of the null-terminated string.</param>
|
||||
/// <returns>Whether they are equal.</returns>
|
||||
public static bool EqualsZeroTerminatedString(
|
||||
ReadOnlySpan<char> charSpan,
|
||||
nint memoryAddress,
|
||||
Encoding? encoding = null,
|
||||
int maxLength = int.MaxValue)
|
||||
{
|
||||
encoding ??= Encoding.UTF8;
|
||||
maxLength = Math.Min(maxLength, charSpan.Length + 4);
|
||||
|
||||
var pmem = ((byte*)memoryAddress)!;
|
||||
var length = 0;
|
||||
while (length < maxLength && pmem[length] != 0)
|
||||
length++;
|
||||
|
||||
var mem = new Span<byte>(pmem, length);
|
||||
var memCharCount = encoding.GetCharCount(mem);
|
||||
if (memCharCount != charSpan.Length)
|
||||
return false;
|
||||
|
||||
Span<char> chars = stackalloc char[memCharCount];
|
||||
encoding.GetChars(mem, chars);
|
||||
return charSpan.SequenceEqual(chars);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a UTF-8 encoded string from a specified memory address.
|
||||
/// </summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue