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})", }; }