using System.Text.Json.Nodes;
namespace Penumbra.CrashHandler.Buffers;
/// The types of currently hooked and relevant animation loading functions.
public enum AnimationInvocationType : int
{
PapLoad,
ActionLoad,
ScheduleClipUpdate,
LoadTimelineResources,
LoadCharacterVfx,
LoadCharacterSound,
ApricotSoundPlay,
LoadAreaVfx,
CharacterBaseLoadAnimation,
}
/// The full crash entry for an invoked vfx function.
public record struct VfxFuncInvokedEntry(
double Age,
DateTimeOffset Timestamp,
int ThreadId,
string InvocationType,
string CharacterName,
string CharacterAddress,
Guid CollectionId) : ICrashDataEntry;
/// Only expose the write interface for the buffer.
public interface IAnimationInvocationBufferWriter
{
/// Write a line into the buffer with the given data.
/// The address of the related character, if known.
/// The name of the related character, anonymized or relying on index if unavailable, if known.
/// The GUID of the associated collection.
/// The type of VFX func called.
public void WriteLine(nint characterAddress, ReadOnlySpan characterName, Guid collectionId, AnimationInvocationType type);
}
internal sealed class AnimationInvocationBuffer : MemoryMappedBuffer, IAnimationInvocationBufferWriter, IBufferReader
{
private const int _version = 1;
private const int _lineCount = 64;
private const int _lineCapacity = 128;
private const string _name = "Penumbra.AnimationInvocation";
public void WriteLine(nint characterAddress, ReadOnlySpan characterName, Guid collectionId, AnimationInvocationType type)
{
var accessor = GetCurrentLineLocking();
lock (accessor)
{
accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
accessor.Write(8, Environment.CurrentManagedThreadId);
accessor.Write(12, (int)type);
accessor.Write(16, characterAddress);
var span = GetSpan(accessor, 24, 16);
collectionId.TryWriteBytes(span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
span = GetSpan(accessor, 40);
WriteSpan(characterName, span);
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
public uint TotalCount
=> TotalWrittenLines;
public IEnumerable GetLines(DateTimeOffset crashTime)
{
var lineCount = (int)CurrentLineCount;
for (var i = lineCount - 1; i >= 0; --i)
{
var line = GetLine(i);
var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
var thread = BitConverter.ToInt32(line[8..]);
var type = (AnimationInvocationType)BitConverter.ToInt32(line[12..]);
var address = BitConverter.ToUInt64(line[16..]);
var collectionId = new Guid(line[24..40]);
var characterName = ReadString(line[40..]);
yield return new JsonObject()
{
[nameof(VfxFuncInvokedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
[nameof(VfxFuncInvokedEntry.Timestamp)] = timestamp,
[nameof(VfxFuncInvokedEntry.ThreadId)] = thread,
[nameof(VfxFuncInvokedEntry.InvocationType)] = ToName(type),
[nameof(VfxFuncInvokedEntry.CharacterName)] = characterName,
[nameof(VfxFuncInvokedEntry.CharacterAddress)] = address.ToString("X"),
[nameof(VfxFuncInvokedEntry.CollectionId)] = collectionId,
};
}
}
public static IBufferReader CreateReader(int pid)
=> new AnimationInvocationBuffer(false, pid);
public static IAnimationInvocationBufferWriter CreateWriter(int pid)
=> new AnimationInvocationBuffer(pid);
private AnimationInvocationBuffer(bool writer, int pid)
: base($"{_name}_{pid}_{_version}", _version)
{ }
private AnimationInvocationBuffer(int pid)
: base($"{_name}_{pid}_{_version}", _version, _lineCount, _lineCapacity)
{ }
private static string ToName(AnimationInvocationType type)
=> type switch
{
AnimationInvocationType.PapLoad => "PAP Load",
AnimationInvocationType.ActionLoad => "Action Load",
AnimationInvocationType.ScheduleClipUpdate => "Schedule Clip Update",
AnimationInvocationType.LoadTimelineResources => "Load Timeline Resources",
AnimationInvocationType.LoadCharacterVfx => "Load Character VFX",
AnimationInvocationType.LoadCharacterSound => "Load Character Sound",
AnimationInvocationType.ApricotSoundPlay => "Apricot Sound Play",
AnimationInvocationType.LoadAreaVfx => "Load Area VFX",
AnimationInvocationType.CharacterBaseLoadAnimation => "Load Animation (CharacterBase)",
_ => $"Unknown ({(int)type})",
};
}