Reduce heap allocation every frame in AddonLifecycle (#1555)

This commit is contained in:
srkizer 2023-12-08 08:49:09 +09:00 committed by GitHub
parent 711d5e2859
commit 0bfcc55774
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 212 additions and 188 deletions

View file

@ -14,22 +14,51 @@ public abstract unsafe class AddonArgs
public const string InvalidAddon = "NullAddon"; public const string InvalidAddon = "NullAddon";
private string? addonName; private string? addonName;
private IntPtr addon;
/// <summary> /// <summary>
/// Gets the name of the addon this args referrers to. /// Gets the name of the addon this args referrers to.
/// </summary> /// </summary>
public string AddonName => this.GetAddonName(); public string AddonName => this.GetAddonName();
/// <summary> /// <summary>
/// Gets the pointer to the addons AtkUnitBase. /// Gets the pointer to the addons AtkUnitBase.
/// </summary> /// </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> /// <summary>
/// Gets the type of these args. /// Gets the type of these args.
/// </summary> /// </summary>
public abstract AddonArgsType Type { get; } 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> /// <summary>
/// Helper method for ensuring the name of the addon is valid. /// Helper method for ensuring the name of the addon is valid.
/// </summary> /// </summary>

View file

@ -3,8 +3,14 @@
/// <summary> /// <summary>
/// Addon argument data for Draw events. /// Addon argument data for Draw events.
/// </summary> /// </summary>
public class AddonDrawArgs : AddonArgs public class AddonDrawArgs : AddonArgs, ICloneable
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Draw; public override AddonArgsType Type => AddonArgsType.Draw;
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonDrawArgs Clone() => (AddonDrawArgs)this.MemberwiseClone();
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
} }

View file

@ -3,8 +3,14 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary> /// <summary>
/// Addon argument data for ReceiveEvent events. /// Addon argument data for ReceiveEvent events.
/// </summary> /// </summary>
public class AddonFinalizeArgs : AddonArgs public class AddonFinalizeArgs : AddonArgs, ICloneable
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Finalize; public override AddonArgsType Type => AddonArgsType.Finalize;
/// <inheritdoc cref="ICloneable.Clone"/>
public AddonFinalizeArgs Clone() => (AddonFinalizeArgs)this.MemberwiseClone();
/// <inheritdoc cref="Clone"/>
object ICloneable.Clone() => this.Clone();
} }

View file

@ -3,28 +3,34 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary> /// <summary>
/// Addon argument data for ReceiveEvent events. /// Addon argument data for ReceiveEvent events.
/// </summary> /// </summary>
public class AddonReceiveEventArgs : AddonArgs public class AddonReceiveEventArgs : AddonArgs, ICloneable
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.ReceiveEvent; public override AddonArgsType Type => AddonArgsType.ReceiveEvent;
/// <summary> /// <summary>
/// Gets the AtkEventType for this event message. /// Gets or sets the AtkEventType for this event message.
/// </summary> /// </summary>
public byte AtkEventType { get; init; } public byte AtkEventType { get; set; }
/// <summary> /// <summary>
/// Gets the event id for this event message. /// Gets or sets the event id for this event message.
/// </summary> /// </summary>
public int EventParam { get; init; } public int EventParam { get; set; }
/// <summary> /// <summary>
/// Gets the pointer to an AtkEvent for this event message. /// Gets or sets the pointer to an AtkEvent for this event message.
/// </summary> /// </summary>
public nint AtkEvent { get; init; } public nint AtkEvent { get; set; }
/// <summary> /// <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> /// </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();
} }

View file

@ -5,23 +5,29 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary> /// <summary>
/// Addon argument data for Refresh events. /// Addon argument data for Refresh events.
/// </summary> /// </summary>
public class AddonRefreshArgs : AddonArgs public class AddonRefreshArgs : AddonArgs, ICloneable
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Refresh; public override AddonArgsType Type => AddonArgsType.Refresh;
/// <summary> /// <summary>
/// Gets the number of AtkValues. /// Gets or sets the number of AtkValues.
/// </summary> /// </summary>
public uint AtkValueCount { get; init; } public uint AtkValueCount { get; set; }
/// <summary> /// <summary>
/// Gets the address of the AtkValue array. /// Gets or sets the address of the AtkValue array.
/// </summary> /// </summary>
public nint AtkValues { get; init; } public nint AtkValues { get; set; }
/// <summary> /// <summary>
/// Gets the AtkValues in the form of a span. /// Gets the AtkValues in the form of a span.
/// </summary> /// </summary>
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount); 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();
} }

View file

@ -3,18 +3,24 @@
/// <summary> /// <summary>
/// Addon argument data for OnRequestedUpdate events. /// Addon argument data for OnRequestedUpdate events.
/// </summary> /// </summary>
public class AddonRequestedUpdateArgs : AddonArgs public class AddonRequestedUpdateArgs : AddonArgs, ICloneable
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.RequestedUpdate; public override AddonArgsType Type => AddonArgsType.RequestedUpdate;
/// <summary> /// <summary>
/// Gets the NumberArrayData** for this event. /// Gets or sets the NumberArrayData** for this event.
/// </summary> /// </summary>
public nint NumberArrayData { get; init; } public nint NumberArrayData { get; set; }
/// <summary> /// <summary>
/// Gets the StringArrayData** for this event. /// Gets or sets the StringArrayData** for this event.
/// </summary> /// </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();
} }

View file

@ -5,23 +5,29 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary> /// <summary>
/// Addon argument data for Setup events. /// Addon argument data for Setup events.
/// </summary> /// </summary>
public class AddonSetupArgs : AddonArgs public class AddonSetupArgs : AddonArgs, ICloneable
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Setup; public override AddonArgsType Type => AddonArgsType.Setup;
/// <summary> /// <summary>
/// Gets the number of AtkValues. /// Gets or sets the number of AtkValues.
/// </summary> /// </summary>
public uint AtkValueCount { get; init; } public uint AtkValueCount { get; set; }
/// <summary> /// <summary>
/// Gets the address of the AtkValue array. /// Gets or sets the address of the AtkValue array.
/// </summary> /// </summary>
public nint AtkValues { get; init; } public nint AtkValues { get; set; }
/// <summary> /// <summary>
/// Gets the AtkValues in the form of a span. /// Gets the AtkValues in the form of a span.
/// </summary> /// </summary>
public unsafe Span<AtkValue> AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount); 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();
} }

View file

@ -3,7 +3,7 @@ namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
/// <summary> /// <summary>
/// Addon argument data for Update events. /// Addon argument data for Update events.
/// </summary> /// </summary>
public class AddonUpdateArgs : AddonArgs public class AddonUpdateArgs : AddonArgs, ICloneable
{ {
/// <inheritdoc/> /// <inheritdoc/>
public override AddonArgsType Type => AddonArgsType.Update; public override AddonArgsType Type => AddonArgsType.Update;
@ -11,5 +11,11 @@ public class AddonUpdateArgs : AddonArgs
/// <summary> /// <summary>
/// Gets the time since the last update. /// Gets the time since the last update.
/// </summary> /// </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();
} }

View file

@ -1,6 +1,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
using Dalamud.Hooking; using Dalamud.Hooking;
@ -40,6 +41,15 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
private readonly ConcurrentBag<AddonLifecycleEventListener> newEventListeners = new(); private readonly ConcurrentBag<AddonLifecycleEventListener> newEventListeners = new();
private readonly ConcurrentBag<AddonLifecycleEventListener> removeEventListeners = 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] [ServiceManager.ServiceConstructor]
private AddonLifecycle(TargetSigScanner sigScanner) private AddonLifecycle(TargetSigScanner sigScanner)
{ {
@ -132,12 +142,27 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
/// </summary> /// </summary>
/// <param name="eventType">Event Type.</param> /// <param name="eventType">Event Type.</param>
/// <param name="args">AddonArgs.</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. // Do not use linq; this is a high-traffic function, and more heap allocations avoided, the better.
foreach (var listener in this.EventListeners.Where(listener => listener.EventType == eventType && (listener.AddonName == args.AddonName || listener.AddonName == string.Empty))) 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."); Log.Error(e, "Exception in OnAddonSetup ReceiveEvent Registration.");
} }
try this.recyclingSetupArgs.Addon = (nint)addon;
{ this.recyclingSetupArgs.AtkValueCount = valueCount;
this.InvokeListeners(AddonEvent.PreSetup, new AddonSetupArgs this.recyclingSetupArgs.AtkValues = (nint)values;
{ this.InvokeListenersSafely(AddonEvent.PreSetup, this.recyclingSetupArgs);
Addon = (nint)addon, valueCount = this.recyclingSetupArgs.AtkValueCount;
AtkValueCount = valueCount, values = (AtkValue*)this.recyclingSetupArgs.AtkValues;
AtkValues = (nint)values,
});
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonSetup pre-setup invoke.");
}
try 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."); 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.InvokeListenersSafely(AddonEvent.PostSetup, this.recyclingSetupArgs);
{
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.");
}
} }
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase) 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."); Log.Error(e, "Exception in OnAddonFinalize ReceiveEvent Removal.");
} }
try this.recyclingFinalizeArgs.Addon = (nint)atkUnitBase[0];
{ this.InvokeListenersSafely(AddonEvent.PreFinalize, this.recyclingFinalizeArgs);
this.InvokeListeners(AddonEvent.PreFinalize, new AddonFinalizeArgs { Addon = (nint)atkUnitBase[0] });
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonFinalize pre-finalize invoke.");
}
try try
{ {
@ -321,14 +321,8 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
private void OnAddonDraw(AtkUnitBase* addon) private void OnAddonDraw(AtkUnitBase* addon)
{ {
try this.recyclingDrawArgs.Addon = (nint)addon;
{ this.InvokeListenersSafely(AddonEvent.PreDraw, this.recyclingDrawArgs);
this.InvokeListeners(AddonEvent.PreDraw, new AddonDrawArgs { Addon = (nint)addon });
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonDraw pre-draw invoke.");
}
try 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."); 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.InvokeListenersSafely(AddonEvent.PostDraw, this.recyclingDrawArgs);
{
this.InvokeListeners(AddonEvent.PostDraw, new AddonDrawArgs { Addon = (nint)addon });
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonDraw post-draw invoke.");
}
} }
private void OnAddonUpdate(AtkUnitBase* addon, float delta) private void OnAddonUpdate(AtkUnitBase* addon, float delta)
{ {
try this.recyclingUpdateArgs.Addon = (nint)addon;
{ this.recyclingUpdateArgs.TimeDelta = delta;
this.InvokeListeners(AddonEvent.PreUpdate, new AddonUpdateArgs { Addon = (nint)addon, TimeDelta = delta }); this.InvokeListenersSafely(AddonEvent.PreUpdate, this.recyclingUpdateArgs);
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonUpdate pre-update invoke.");
}
try 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."); 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.InvokeListenersSafely(AddonEvent.PostUpdate, this.recyclingUpdateArgs);
{
this.InvokeListeners(AddonEvent.PostUpdate, new AddonUpdateArgs { Addon = (nint)addon, TimeDelta = delta });
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonUpdate post-update invoke.");
}
} }
private byte OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values) private byte OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values)
{ {
byte result = 0; byte result = 0;
try this.recyclingRefreshArgs.Addon = (nint)addon;
{ this.recyclingRefreshArgs.AtkValueCount = valueCount;
this.InvokeListeners(AddonEvent.PreRefresh, new AddonRefreshArgs this.recyclingRefreshArgs.AtkValues = (nint)values;
{ this.InvokeListenersSafely(AddonEvent.PreRefresh, this.recyclingRefreshArgs);
Addon = (nint)addon, valueCount = this.recyclingRefreshArgs.AtkValueCount;
AtkValueCount = valueCount, values = (AtkValue*)this.recyclingRefreshArgs.AtkValues;
AtkValues = (nint)values,
});
}
catch (Exception e)
{
Log.Error(e, "Exception in OnAddonRefresh pre-refresh invoke.");
}
try 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."); 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.InvokeListenersSafely(AddonEvent.PostRefresh, this.recyclingRefreshArgs);
{
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.");
}
return result; return result;
} }
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{ {
try this.recyclingRequestedUpdateArgs.Addon = (nint)addon;
{ this.recyclingRequestedUpdateArgs.NumberArrayData = (nint)numberArrayData;
this.InvokeListeners(AddonEvent.PreRequestedUpdate, new AddonRequestedUpdateArgs this.recyclingRequestedUpdateArgs.StringArrayData = (nint)stringArrayData;
{ this.InvokeListenersSafely(AddonEvent.PreRequestedUpdate, this.recyclingRequestedUpdateArgs);
Addon = (nint)addon, numberArrayData = (NumberArrayData**)this.recyclingRequestedUpdateArgs.NumberArrayData;
NumberArrayData = (nint)numberArrayData, stringArrayData = (StringArrayData**)this.recyclingRequestedUpdateArgs.StringArrayData;
StringArrayData = (nint)stringArrayData,
});
}
catch (Exception e)
{
Log.Error(e, "Exception in OnRequestedUpdate pre-requestedUpdate invoke.");
}
try 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."); 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.InvokeListenersSafely(AddonEvent.PostRequestedUpdate, this.recyclingRequestedUpdateArgs);
{
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.");
}
} }
} }

View file

@ -16,6 +16,10 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
{ {
private static readonly ModuleLog Log = new("AddonLifecycle"); 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> /// <summary>
/// Initializes a new instance of the <see cref="AddonLifecycleReceiveEventListener"/> class. /// Initializes a new instance of the <see cref="AddonLifecycleReceiveEventListener"/> class.
/// </summary> /// </summary>
@ -74,22 +78,17 @@ internal unsafe class AddonLifecycleReceiveEventListener : IDisposable
this.Hook!.Original(addon, eventType, eventParam, atkEvent, data); this.Hook!.Original(addon, eventType, eventParam, atkEvent, data);
return; return;
} }
try this.recyclingReceiveEventArgs.Addon = (nint)addon;
{ this.recyclingReceiveEventArgs.AtkEventType = (byte)eventType;
this.AddonLifecycle.InvokeListeners(AddonEvent.PreReceiveEvent, new AddonReceiveEventArgs this.recyclingReceiveEventArgs.EventParam = eventParam;
{ this.recyclingReceiveEventArgs.AtkEvent = (IntPtr)atkEvent;
Addon = (nint)addon, this.recyclingReceiveEventArgs.Data = data;
AtkEventType = (byte)eventType, this.AddonLifecycle.InvokeListenersSafely(AddonEvent.PreReceiveEvent, this.recyclingReceiveEventArgs);
EventParam = eventParam, eventType = (AtkEventType)this.recyclingReceiveEventArgs.AtkEventType;
AtkEvent = (nint)atkEvent, eventParam = this.recyclingReceiveEventArgs.EventParam;
Data = data, atkEvent = (AtkEvent*)this.recyclingReceiveEventArgs.AtkEvent;
}); data = this.recyclingReceiveEventArgs.Data;
}
catch (Exception e)
{
Log.Error(e, "Exception in OnReceiveEvent pre-receiveEvent invoke.");
}
try 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."); 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.InvokeListenersSafely(AddonEvent.PostReceiveEvent, this.recyclingReceiveEventArgs);
{
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.");
}
} }
} }

View file

@ -163,6 +163,38 @@ public static unsafe class MemoryHelper
#region ReadString #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> /// <summary>
/// Read a UTF-8 encoded string from a specified memory address. /// Read a UTF-8 encoded string from a specified memory address.
/// </summary> /// </summary>