Some fancy classes for faster serialization
This commit is contained in:
182
Core/Buffer.cs
Normal file
182
Core/Buffer.cs
Normal file
@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using GTA.Math;
|
||||
|
||||
namespace RageCoop.Core
|
||||
{
|
||||
internal unsafe abstract class BufferBase
|
||||
{
|
||||
public int Size { get; protected set; }
|
||||
public int CurrentIndex { get; protected set; }
|
||||
public byte* Address { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ensure memory safety and advance position by specified number of bytes
|
||||
/// </summary>
|
||||
/// <param name="cbSize"></param>
|
||||
/// <returns>Pointer to the current position in the buffer</returns>
|
||||
protected abstract byte* Alloc(int cbSize);
|
||||
|
||||
protected T* Alloc<T>(int count = 1) where T : unmanaged
|
||||
=> (T*)Alloc(count * sizeof(T));
|
||||
}
|
||||
|
||||
internal unsafe sealed class WriteBuffer : BufferBase
|
||||
{
|
||||
public WriteBuffer(int size)
|
||||
{
|
||||
Resize(size);
|
||||
}
|
||||
|
||||
public void Resize(int size)
|
||||
{
|
||||
if (size < Size)
|
||||
{
|
||||
Size = size;
|
||||
return;
|
||||
}
|
||||
|
||||
var newAddr = (byte*)Marshal.AllocHGlobal(size);
|
||||
if (Address != null)
|
||||
{
|
||||
Buffer.MemoryCopy(Address, newAddr, size, Size);
|
||||
Marshal.FreeHGlobal((IntPtr)Address);
|
||||
}
|
||||
Size = size;
|
||||
Address = newAddr;
|
||||
}
|
||||
|
||||
protected override byte* Alloc(int cbSize)
|
||||
{
|
||||
var index = CurrentIndex;
|
||||
CurrentIndex += cbSize;
|
||||
|
||||
// Resize the buffer by at least 50% if there's no sufficient space
|
||||
if (CurrentIndex > Size)
|
||||
Resize(Math.Max(CurrentIndex + 1, (int)(Size * 1.5f)));
|
||||
|
||||
return Address + index;
|
||||
}
|
||||
|
||||
public void Write<T>(ref T value) where T : unmanaged
|
||||
{
|
||||
var addr = Alloc<T>();
|
||||
*addr = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For passing struct smaller than word size (4/8 bytes on 32/64 bit system)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="value"></param>
|
||||
public void WriteVal<T>(T value) where T : unmanaged
|
||||
{
|
||||
var addr = Alloc<T>();
|
||||
*addr = value;
|
||||
}
|
||||
|
||||
|
||||
public void Write(ReadOnlySpan<char> str)
|
||||
{
|
||||
// Prefixed by its size in bytes
|
||||
var cbBody = Encoding.UTF8.GetByteCount(str);
|
||||
WriteVal(cbBody);
|
||||
|
||||
// Allocate and write string body
|
||||
var pBody = Alloc(cbBody);
|
||||
Encoding.UTF8.GetBytes(str, new(pBody, cbBody));
|
||||
}
|
||||
|
||||
// Struct in GTA.Math have pack/padding for memory alignment, we don't want it to waste the bandwidth
|
||||
|
||||
public void Write(ref Vector2 vec2)
|
||||
{
|
||||
var faddr = Alloc<float>(2);
|
||||
faddr[0] = vec2.X;
|
||||
faddr[1] = vec2.Y;
|
||||
}
|
||||
|
||||
public void Write(ref Vector3 vec3)
|
||||
{
|
||||
var faddr = Alloc<float>(3);
|
||||
faddr[0] = vec3.X;
|
||||
faddr[1] = vec3.Y;
|
||||
faddr[2] = vec3.Z;
|
||||
}
|
||||
|
||||
public void Write(ref Quaternion quat)
|
||||
{
|
||||
var faddr = Alloc<float>(4);
|
||||
faddr[0] = quat.X;
|
||||
faddr[1] = quat.Y;
|
||||
faddr[2] = quat.Z;
|
||||
faddr[3] = quat.W;
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe sealed class ReadBuffer : BufferBase
|
||||
{
|
||||
public ReadBuffer(byte* address, int size)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
protected override byte* Alloc(int cbSize)
|
||||
{
|
||||
var index = CurrentIndex;
|
||||
CurrentIndex += cbSize;
|
||||
|
||||
if (CurrentIndex > Size)
|
||||
throw new InvalidOperationException("Attempting to read beyond the existing buffer");
|
||||
|
||||
return Address + index;
|
||||
}
|
||||
|
||||
|
||||
public T ReadVal<T>() where T : unmanaged => *Alloc<T>();
|
||||
|
||||
public void Read<T>(out T result) where T : unmanaged
|
||||
=> result = *Alloc<T>();
|
||||
|
||||
public void Read(out string str)
|
||||
{
|
||||
var cbBody = ReadVal<int>();
|
||||
str = Encoding.UTF8.GetString(Alloc(cbBody), cbBody);
|
||||
}
|
||||
|
||||
public void Read(out Vector2 vec)
|
||||
{
|
||||
var faddr = Alloc<float>(2);
|
||||
vec = new()
|
||||
{
|
||||
X = faddr[0],
|
||||
Y = faddr[1],
|
||||
};
|
||||
}
|
||||
|
||||
public void Read(out Vector3 vec)
|
||||
{
|
||||
var faddr = Alloc<float>(3);
|
||||
vec = new()
|
||||
{
|
||||
X = faddr[0],
|
||||
Y = faddr[1],
|
||||
Z = faddr[2],
|
||||
};
|
||||
}
|
||||
|
||||
public void Read(out Quaternion quat)
|
||||
{
|
||||
var faddr = Alloc<float>(4);
|
||||
quat = new()
|
||||
{
|
||||
X = faddr[0],
|
||||
Y = faddr[1],
|
||||
Z = faddr[2],
|
||||
W = faddr[3],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ using RageCoop.Core.Scripting;
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client")]
|
||||
[assembly: InternalsVisibleTo("RageCoop.Client.Installer")]
|
||||
[assembly: InternalsVisibleTo("DataDumper")]
|
||||
[assembly: InternalsVisibleTo("UnitTest")]
|
||||
[assembly: InternalsVisibleTo("RageCoop.ResourceBuilder")]
|
||||
|
||||
namespace RageCoop.Core
|
||||
|
74
Tools/UnitTest/Program.cs
Normal file
74
Tools/UnitTest/Program.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using GTA.Math;
|
||||
using RageCoop.Core;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class TestElement
|
||||
{
|
||||
static SHA256 sha = SHA256.Create();
|
||||
static int blah = new Random().Next();
|
||||
public TestElement(int i)
|
||||
{
|
||||
num = (i + blah) * 1024;
|
||||
vec2 = new(num * 10, num * 20);
|
||||
vec3 = new(num * 10, num * 20, num * 30);
|
||||
quat = new(num * 10, num * 20, num * 30, num * 40);
|
||||
str = sha.ComputeHash(BitConverter.GetBytes(num)).Dump();
|
||||
}
|
||||
public int num;
|
||||
public Vector2 vec2;
|
||||
public Vector3 vec3;
|
||||
public Quaternion quat;
|
||||
public string str;
|
||||
}
|
||||
internal unsafe class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
TestElement[] test = new TestElement[1024];
|
||||
Console.WriteLine("Testing buffers");
|
||||
var buf = new WriteBuffer(1024);
|
||||
for (int i = 0; i < 1024; i++)
|
||||
{
|
||||
var e = test[i] = new TestElement(i);
|
||||
buf.WriteVal(e.num);
|
||||
buf.Write(ref e.vec2);
|
||||
buf.Write(ref e.vec3);
|
||||
buf.Write(ref e.quat);
|
||||
buf.Write(e.str);
|
||||
}
|
||||
Console.WriteLine($"Buffer size: {buf.Size}");
|
||||
Console.WriteLine($"Current position: {buf.CurrentIndex}");
|
||||
|
||||
Console.WriteLine("Validating data");
|
||||
var reader = new ReadBuffer(buf.Address, buf.Size);
|
||||
for (int i = 0; i < 1024; i++)
|
||||
{
|
||||
var e = test[i];
|
||||
reader.Read(out int num);
|
||||
reader.Read(out Vector2 vec2);
|
||||
reader.Read(out Vector3 vec3);
|
||||
reader.Read(out Quaternion quat);
|
||||
reader.Read(out string str);
|
||||
|
||||
if (num != e.num)
|
||||
throw new Exception("POCO fail");
|
||||
|
||||
if (vec2 != e.vec2)
|
||||
throw new Exception("vec2 fail");
|
||||
|
||||
if (vec3 != e.vec3)
|
||||
throw new Exception("vec3 fail");
|
||||
|
||||
if (quat != e.quat)
|
||||
throw new Exception("quat fail");
|
||||
|
||||
if (str != e.str)
|
||||
throw new Exception("str fail");
|
||||
}
|
||||
|
||||
Console.WriteLine("Buffers OK");
|
||||
}
|
||||
}
|
||||
}
|
20
Tools/UnitTest/UnitTest.csproj
Normal file
20
Tools/UnitTest/UnitTest.csproj
Normal file
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Core\RageCoop.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="ScriptHookVDotNetCore">
|
||||
<HintPath>..\..\libs\ScriptHookVDotNetCore.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
Binary file not shown.
Reference in New Issue
Block a user