Files
Performance-Fish/Source/PerformanceFish/Log.cs
T

206 lines
5.3 KiB
C#

// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using FisheryLib.Pools;
using PerformanceFish.Prepatching;
namespace PerformanceFish;
public static class Log
{
public static void Message(string message) => Config.Message(message);
public static void Message(PooledStringHandler message) => Config.Message(message.GetFormattedText());
public static void Warning(string message) => Config.Warning(message);
public static void Warning(PooledStringHandler message) => Config.Warning(message.GetFormattedText());
public static void Error(string message) => Config.Error(message);
public static void Error(PooledStringHandler message) => Config.Error(message.GetFormattedText());
[PublicAPI]
public static class Config
{
public static Action<string> Message { get; set; } = static text =>
{
Verse.Log.logLock ??= new(); // in case this runs from the Log cctor, and in turn before it finishes setup
Verse.Log.Message(text);
HandleEarlyMessage(text, LogMessageType.Message);
};
public static Action<string> Warning { get; set; } = static text =>
{
Verse.Log.logLock ??= new();
Verse.Log.Warning(text);
HandleEarlyMessage(text, LogMessageType.Warning);
};
public static Action<string> Error { get; set; } = static text =>
{
Verse.Log.logLock ??= new();
Verse.Log.Error(text);
HandleEarlyMessage(text, LogMessageType.Error);
};
}
private static void HandleEarlyMessage(string text, LogMessageType messageType)
{
if (Ready)
return;
FindAndEnqueueUnhandledMessage(text, messageType);
}
private static void FindAndEnqueueUnhandledMessage(string text, LogMessageType messageType)
{
var foundMessage = false;
var lastMessage = Verse.Log.messageQueue.lastMessage;
if (!TryEnqueueUnhandledMessage(text, messageType, lastMessage, ref foundMessage))
{
foreach (var message in Verse.Log.messageQueue.messages)
{
if (TryEnqueueUnhandledMessage(text, messageType, message, ref foundMessage))
break;
}
}
if (!foundMessage)
{
var message = Verse.Log.messageQueue.messages.FirstOrDefault(message
=> message.text.Contains(text) || text.Contains(message.text));
if (message != null)
UnhandledMessages!.Enqueue(message);
}
}
private static bool TryEnqueueUnhandledMessage(string text, LogMessageType messageType, LogMessage message,
ref bool foundMessage)
{
if (message.text != text || message.type != messageType)
return false;
UnhandledMessages!.Enqueue(message);
foundMessage = true;
return true;
}
public static UnhandledMessageQueue? UnhandledMessages { get; }
= SetupUnhandledMessageQueue();
[SuppressMessage("Reliability", "CA2000")]
private static UnhandledMessageQueue? SetupUnhandledMessageQueue()
{
if (Ready)
return null;
ref var stash = ref FishStash.Get;
if (stash.UnhandledMessages is { } stashedMessages)
{
stash.MessageQueueGCHandle.Free();
return stashedMessages;
}
stash.MessageQueueGCHandle = GCHandle.Alloc(new UnhandledMessageQueue(), GCHandleType.Pinned);
return stash.UnhandledMessages;
}
public static bool Ready { get; set; } = PerformanceFishMod.Mod != null;
}
[StructLayout(LayoutKind.Sequential)]
public record struct LogMessageData : IDisposable
{
public IntPtr Text,
StackTrace;
public int Type,
Repeats;
public LogMessage ToLogMessage()
=> new((LogMessageType)Type, Marshal.PtrToStringUni(Text), Marshal.PtrToStringUni(StackTrace))
{
repeats = Repeats
};
public LogMessageData(LogMessage logMessage)
{
Text = Marshal.StringToHGlobalUni(logMessage.text);
Type = (int)logMessage.type;
Repeats = logMessage.repeats;
StackTrace = Marshal.StringToHGlobalUni(logMessage.stackTrace);
}
public void Dispose()
{
Marshal.FreeHGlobal(Text);
Marshal.FreeHGlobal(StackTrace);
}
}
[StructLayout(LayoutKind.Sequential)]
public unsafe class UnhandledMessageQueue : IDisposable
{
public int Count { get; private set; }
private int _arrayLength;
private LogMessageData* _messages;
public void Enqueue(LogMessageData message)
{
lock (this)
{
if (Count >= _arrayLength)
Expand();
_messages[Count++] = message;
}
}
public void Enqueue(LogMessage message) => Enqueue(new LogMessageData(message));
public bool TryDequeue(out LogMessageData message)
{
lock (this)
{
if (--Count >= 0)
{
message = _messages[Count];
_messages[Count] = default;
return true;
}
message = default;
return false;
}
}
private void Expand()
{
if (_arrayLength == 0)
{
_arrayLength = 1;
_messages = (LogMessageData*)Marshal.AllocHGlobal(sizeof(LogMessageData));
}
else
{
_arrayLength <<= 1;
_messages = (LogMessageData*)Marshal.ReAllocHGlobal((IntPtr)_messages,
(IntPtr)(_arrayLength * sizeof(LogMessageData)));
}
}
// ReSharper disable once InconsistentlySynchronizedField
protected virtual void Dispose(bool disposing) => Marshal.FreeHGlobal((IntPtr)_messages);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~UnhandledMessageQueue() => Dispose(false);
}