Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asynchronous Logging #16

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ bld/

# Visual Studio 2015 cache/options directory
.vs/
.config/
IRBTModUtils/.config/
IRBTModUtilsTests/.config/
dotnet-tools.json
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

Expand Down
2 changes: 1 addition & 1 deletion IRBTModUtils/IRBTModUtils/Feature/MovementMods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ internal static float ModifiedDistanceExt(this Mech mech, bool skipExternalAll,
}

// Make comparisons safer
modifiedDist = (float)Math.Ceiling(modifiedDist);
modifiedDist = (float)System.Math.Ceiling(modifiedDist);

if (modifiedDist < Mod.Config.MinimumMove)
{
Expand Down
2 changes: 1 addition & 1 deletion IRBTModUtils/IRBTModUtils/GifSupport/UniGifDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ private static byte[] DecodeGifLZW(List<byte> compData, int lzwMinimumCodeSize,
/// <param name="clearCode">out Clear code</param>
/// <param name="finishCode">out Finish code</param>
private static void InitDictionary(Dictionary<int, string> dic, int lzwMinimumCodeSize, out int lzwCodeSize, out int clearCode, out int finishCode) {
int dicLength = (int)Math.Pow(2, lzwMinimumCodeSize);
int dicLength = (int)System.Math.Pow(2, lzwMinimumCodeSize);

clearCode = dicLength;
finishCode = clearCode + 1;
Expand Down
4 changes: 2 additions & 2 deletions IRBTModUtils/IRBTModUtils/GifSupport/UniGifFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private static bool SetGifHeader(byte[] gifBytes, ref int byteIndex, ref GifData

// Size of Global Color Table(3 Bits)
int val = (gifBytes[10] & 7) + 1;
gifData.m_sizeOfGlobalColorTable = (int)Math.Pow(2, val);
gifData.m_sizeOfGlobalColorTable = (int)System.Math.Pow(2, val);
}

// Background Color Index(1 Byte)
Expand Down Expand Up @@ -228,7 +228,7 @@ private static void SetImageBlock(byte[] gifBytes, ref int byteIndex, ref GifDat

// Size of Local Color Table(3 Bits)
int val = (gifBytes[byteIndex] & 7) + 1;
ib.m_sizeOfLocalColorTable = (int)Math.Pow(2, val);
ib.m_sizeOfLocalColorTable = (int)System.Math.Pow(2, val);

byteIndex++;
}
Expand Down
138 changes: 138 additions & 0 deletions IRBTModUtils/IRBTModUtils/Helper/FastFormatDate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using System;
using System.Runtime.CompilerServices;
using System.Security;

namespace IRBTModUtils.Helper
{
/// <summary>
/// Provides direct formatting functions to speed up System.DateTime
/// </summary>
public static class FastFormatDate
{
/// <summary>
/// Faster formatting function for "HH:mm:ss.fff ". Allocates new string
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static string ToHHmmssfff_(ref DateTime dateTime)
{
char* chars = stackalloc char[13];
chars[0] = (char)((int)(dateTime.Hour / 10) + 48);
chars[1] = (char)((dateTime.Hour % 10) + 48);
chars[2] = ':';
chars[3] = (char)((int)(dateTime.Minute / 10) + 48);
chars[4] = (char)((dateTime.Minute % 10) + 48);
chars[5] = ':';
chars[6] = (char)((dateTime.Second / 10) + 48);
chars[7] = (char)((dateTime.Second % 10) + 48);
chars[8] = '.';
chars[9] = (char)((dateTime.Millisecond / 100) + 48);
chars[10] = (char)(((dateTime.Millisecond % 100) / 10) + 48);
chars[11] = (char)(((dateTime.Millisecond % 10)) + 48);
chars[12] = ' ';
return new string(chars);
}

/// <summary>
/// Faster formatting function for "[HH:mm:ss.fff]". Allocates new string
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static string ToHHmmssfffBR(ref DateTime dateTime)
{
char* chars = stackalloc char[14];
chars[0] = '[';
chars[1] = (char)((int)(dateTime.Hour / 10) + 48);
chars[2] = (char)((dateTime.Hour % 10) + 48);
chars[3] = ':';
chars[4] = (char)((int)(dateTime.Minute / 10) + 48);
chars[5] = (char)((dateTime.Minute % 10) + 48);
chars[6] = ':';
chars[7] = (char)((dateTime.Second / 10) + 48);
chars[8] = (char)((dateTime.Second % 10) + 48);
chars[9] = '.';
chars[10] = (char)((dateTime.Millisecond / 100) + 48);
chars[11] = (char)(((dateTime.Millisecond % 100) / 10) + 48);
chars[12] = (char)(((dateTime.Millisecond % 10)) + 48);
chars[13] = ']';
return new string(chars);
}

/// <summary>
/// Faster formatting function for "HH:mm:ss.ffff ". Does not allocate a new string. In Len > 14
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressUnmanagedCodeSecurity]
public unsafe static void ToHHmmssffff(ref DateTime dateTime, ref char[] chars)
{
chars[0] = (char)((int)(dateTime.Hour / 10) + 48);
chars[1] = (char)((dateTime.Hour % 10) + 48);
chars[2] = ':';
chars[3] = (char)((int)(dateTime.Minute / 10) + 48);
chars[4] = (char)((dateTime.Minute % 10) + 48);
chars[5] = ':';
chars[6] = (char)((dateTime.Second / 10) + 48);
chars[7] = (char)((dateTime.Second % 10) + 48);
chars[8] = '.';
chars[9] = (char)((dateTime.Millisecond / 100) + 48);
chars[10] = (char)(((dateTime.Millisecond % 100) / 10) + 48);
chars[11] = (char)(((dateTime.Millisecond % 10)) + 48);
chars[12] = (char)(((dateTime.Millisecond % 1)) + 48);
chars[13] = ' ';
}

/// <summary>
/// Faster formatting function for "HH:mm:ss.fff ". Does not allocate a new string. In Len > 13
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static void ToHHmmssfff(ref DateTime dateTime, ref char[] chars)
{
chars[0] = (char)((int)(dateTime.Hour / 10) + 48);
chars[1] = (char)((dateTime.Hour % 10) + 48);
chars[2] = ':';
chars[3] = (char)((int)(dateTime.Minute / 10) + 48);
chars[4] = (char)((dateTime.Minute % 10) + 48);
chars[5] = ':';
chars[6] = (char)((dateTime.Second / 10) + 48);
chars[7] = (char)((dateTime.Second % 10) + 48);
chars[8] = '.';
chars[9] = (char)((dateTime.Millisecond / 100) + 48);
chars[10] = (char)(((dateTime.Millisecond % 100) / 10) + 48);
chars[11] = (char)(((dateTime.Millisecond % 10)) + 48);
chars[12] = ' ';
}

/// <summary>
/// Faster formatting function for "HH-mm-ss ". Does not allocate a new string. In Len > 9.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static void ToHHmmss(ref DateTime dateTime, ref char[] chars)
{
chars[0] = (char)((int)(dateTime.Hour / 10) + 48);
chars[1] = (char)((dateTime.Hour % 10) + 48);
chars[2] = '.';
chars[3] = (char)((int)(dateTime.Minute / 10) + 48);
chars[4] = (char)((dateTime.Minute % 10) + 48);
chars[5] = '.';
chars[6] = (char)((dateTime.Second / 10) + 48);
chars[7] = (char)((dateTime.Second % 10) + 48);
chars[8] = ' ';
}

/// <summary>
/// Faster formatting function for "HH-mm-ss". Allocates a new string
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static string ToHHmmss(ref DateTime dateTime)
{
char* chars = stackalloc char[8];
chars[0] = (char)((int)(dateTime.Hour / 10) + 48);
chars[1] = (char)((dateTime.Hour % 10) + 48);
chars[2] = '.';
chars[3] = (char)((int)(dateTime.Minute / 10) + 48);
chars[4] = (char)((dateTime.Minute % 10) + 48);
chars[5] = '.';
chars[6] = (char)((dateTime.Second / 10) + 48);
chars[7] = (char)((dateTime.Second % 10) + 48);
return new string(chars);
}
}
}
127 changes: 127 additions & 0 deletions IRBTModUtils/IRBTModUtils/Helper/MPMCQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

namespace IRBTModUtils.Helper
{
// From: https://github.com/alexandrnikitin/MPMCQueue.NET?tab=readme-ov-file
// Generally follows ideas in existing MPMC queues. Underlying implementation for later.NET ConcurrentQueue versions
[StructLayout(LayoutKind.Explicit, Size = 384)]
public class MPMCQueue
{
/// <summary>
/// 128 bytes cache line already exists in some CPUs.
/// </summary>
/// <remarks>
/// Also "the spatial prefetcher strives to keep pairs of cache lines in the L2 cache."
/// https://stackoverflow.com/questions/29199779/false-sharing-and-128-byte-alignment-padding
/// </remarks>
internal const int SAFE_CACHE_LINE = 128;

[FieldOffset(SAFE_CACHE_LINE)]
private readonly Cell[] _enqueueBuffer;

[FieldOffset(SAFE_CACHE_LINE + 8)]
private volatile int _enqueuePos;

// Separate access to buffers from enqueue and dequeue.
// This removes false sharing and accessing a buffer
// reference also prefetches the following Pos with [(64 - (8 + 4 + 4)) = 52]/64 probability.

[FieldOffset(SAFE_CACHE_LINE * 2)]
private readonly Cell[] _dequeueBuffer;

[FieldOffset(SAFE_CACHE_LINE * 2 + 8)]
private volatile int _dequeuePos;

public MPMCQueue(int bufferSize)
{
if (bufferSize < 2) throw new ArgumentException($"{nameof(bufferSize)} should be greater than or equal to 2");
if ((bufferSize & (bufferSize - 1)) != 0) throw new ArgumentException($"{nameof(bufferSize)} should be a power of 2");

_enqueueBuffer = new Cell[bufferSize];

for (var i = 0; i < bufferSize; i++)
{
_enqueueBuffer[i] = new Cell(i, null);
}

_dequeueBuffer = _enqueueBuffer;
_enqueuePos = 0;
_dequeuePos = 0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryEnqueue(object item)
{
var spinner = new SpinWait();
do
{
var buffer = _enqueueBuffer;
var pos = _enqueuePos;
var index = pos & (buffer.Length - 1);
ref var cell = ref buffer[index];
if (cell.Sequence == pos && Interlocked.CompareExchange(ref _enqueuePos, pos + 1, pos) == pos)
{
cell.Element = item;
cell.Sequence = pos + 1;
return true;
}

if (cell.Sequence < pos)
{
return false;
}

spinner.SpinOnce();
} while (true);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryDequeue(out object result)
{
result = null;
var spinner = new SpinWait();
do
{
var buffer = _dequeueBuffer;
var pos = _dequeuePos;
var index = pos & (buffer.Length - 1);
ref var cell = ref buffer[index];
if (cell.Sequence == pos + 1 && Interlocked.CompareExchange(ref _dequeuePos, pos + 1, pos) == pos)
{
result = cell.Element;
cell.Element = null;
cell.Sequence = pos + buffer.Length;
break;
}

if (cell.Sequence < pos + 1)
{
break;
}

spinner.SpinOnce();
} while (true);

return result != null;
}

[StructLayout(LayoutKind.Explicit, Size = 16)]
private struct Cell
{
[FieldOffset(0)]
public volatile int Sequence;

[FieldOffset(8)]
public object Element;

public Cell(int sequence, object element)
{
Sequence = sequence;
Element = element;
}
}
}
}
2 changes: 1 addition & 1 deletion IRBTModUtils/IRBTModUtils/Helper/SkillUtils.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using BattleTech;

using IRBTModUtils;
using System;
using System.Collections.Generic;
Expand Down
10 changes: 4 additions & 6 deletions IRBTModUtils/IRBTModUtils/IRBTModUtils.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
<DebugType>none</DebugType>
<AssemblyTitle>IRBTModUtils</AssemblyTitle>
<Product>IRBTModUtils</Product>
<Copyright>Copyright © 2023</Copyright>
<AssemblyVersion>2.0.3.0</AssemblyVersion>
<FileVersion>2.0.3.0</FileVersion>
<LangVersion>11</LangVersion>
<Copyright>Copyright © 2025</Copyright>
<AssemblyVersion>2.0.4.0</AssemblyVersion>
<FileVersion>2.0.4.0</FileVersion>
<LangVersion>13</LangVersion>

<!-- Necessary for the Xoshiro random code -->
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand Down Expand Up @@ -65,14 +65,12 @@
<Reference Include="Newtonsoft.Json">
<Private>False</Private>
</Reference>

<Reference Include="System">
<Private>False</Private>
</Reference>
<Reference Include="System.Core">
<Private>False</Private>
</Reference>

<Reference Include="Unity.TextMeshPro">
<Private>False</Private>
</Reference>
Expand Down
36 changes: 36 additions & 0 deletions IRBTModUtils/IRBTModUtils/Logging/AsyncLogMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace IRBTModUtils.Logging
{
/// <summary>
/// Async Message data to be dispatched. Attempts to reduce queue allocations and marshals managed memory instead
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = 32)]
public class AsyncLogMessage
{
[FieldOffset(0)]
public long _ticks;
[FieldOffset(8)]
public StreamWriter _writer;
[FieldOffset(16)]
public Exception _e;
[FieldOffset(24)]
public string _message;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public AsyncLogMessage(
string message,
Exception e,
long ticks,
StreamWriter writer)
{
_e = e;
_writer = writer;
_ticks = ticks;
_message = message;
}
};
}
Loading