Skip to content

Commit

Permalink
implemented RAII
Browse files Browse the repository at this point in the history
  • Loading branch information
guerro323 committed Apr 7, 2022
1 parent 23def83 commit ebb1b7a
Show file tree
Hide file tree
Showing 2 changed files with 289 additions and 8 deletions.
51 changes: 43 additions & 8 deletions revghost/Utility/NativeAllocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct Data
/// <typeparam name="T">The type of the managed object</typeparam>
/// <returns>Return the managed object on native memory</returns>
/// <remarks><see cref="additionalSize"/> can be used for strings (<see cref="NativeAllocatorExtensions.AllocString"/>)</remarks>
public T AllocZeroed<T>(int additionalSize = 0)
public readonly T AllocZeroed<T>(int additionalSize = 0)
{
var size = (nuint) (((int*) typeof(T).TypeHandle.Value)![1] + additionalSize);
var memory = (byte*) Context->Alloc(ref *Context, ContextManagedObject, size);
Expand All @@ -47,7 +47,7 @@ public T AllocZeroed<T>(int additionalSize = 0)
/// <typeparam name="T">The type of the managed object</typeparam>
/// <returns>Return the managed object on native memory</returns>
/// <remarks><see cref="additionalSize"/> can be used for strings (<see cref="NativeAllocatorExtensions.AllocString"/>)</remarks>
public readonly T Alloc<T>(int additionalSize = 0)
public readonly T New<T>(int additionalSize = 0)
{
var size = (nuint) (((int*) typeof(T).TypeHandle.Value)![1] + additionalSize);
var memory = (byte*) Context->Alloc(ref *Context, ContextManagedObject, size);
Expand All @@ -73,7 +73,7 @@ public readonly ref byte GetObjectBaseMemory(object obj)
/// <param name="obj">The object to free</param>
/// <typeparam name="T">The type of the managed object</typeparam>
/// <returns>Whether or not it was successfully freed (if you don't use a tracking allocator the result will always be true)</returns>
public bool Free<T>(ref T obj)
public readonly bool Free<T>(ref T obj)
{
if (obj == null)
return false;
Expand Down Expand Up @@ -126,7 +126,7 @@ public static NativeAllocator CreateContext(bool tracking = true)
/// <param name="data">Must be created from <see cref="NativeMemory.Alloc"/></param>
/// <param name="managedObjectCompanion">Managed object that can be used as a companion for allocator methods</param>
/// <returns>A new <see cref="NativeAllocator"/></returns>
public static NativeAllocator CreateContext(ref Data data, object managedObjectCompanion = null)
public static NativeAllocator CreateCustomContext(ref Data data, object managedObjectCompanion = null)
{
NativeAllocator allocator;
allocator.Context = (Data*) Unsafe.AsPointer(ref data);
Expand Down Expand Up @@ -179,9 +179,9 @@ private static void TrackingDispose(ref Data data, object companion)

public static class NativeAllocatorExtensions
{
public static string AllocString(this in NativeAllocator allocator, int size)
public static string NewString(this in NativeAllocator allocator, int size)
{
var str = allocator.Alloc<string>((size + 1) * sizeof(char));
var str = allocator.New<string>((size + 1) * sizeof(char));
var memorySpan = MemoryMarshal.CreateSpan(
ref allocator.GetObjectBaseMemory(str),
// Header + Length + FirstChar
Expand All @@ -195,12 +195,47 @@ ref allocator.GetObjectBaseMemory(str),
return str;
}

public static string AllocString(this in NativeAllocator allocator, ReadOnlySpan<char> chars)
public static string NewString(this in NativeAllocator allocator, ReadOnlySpan<char> chars)
{
var str = AllocString(allocator, chars.Length);
var str = NewString(allocator, chars.Length);
var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(str.AsSpan()), chars.Length);
chars.CopyTo(span);

return str;
}

public static string Join(this in NativeAllocator allocator, ReadOnlySpan<char> left, ReadOnlySpan<char> right)
{
var final = NewString(allocator, left.Length + right.Length);
var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(final.AsSpan()), final.Length);

left.CopyTo(span[..left.Length]);
right.CopyTo(span.Slice(left.Length, right.Length));

return final;
}

public static GuardAllocation<T> GuardAlloc<T>(this in NativeAllocator allocator)
{
return new GuardAllocation<T>(allocator, allocator.AllocZeroed<T>());
}
}

public unsafe struct GuardAllocation<T> : IDisposable
{
private NativeAllocator _allocator;
private object _object;

public GuardAllocation(NativeAllocator allocator, object obj)
{
_allocator = allocator;
_object = obj;
}

public T Value => Unsafe.As<object, T>(ref Unsafe.AsRef<GuardAllocation<T>>(Unsafe.AsPointer(ref this))._object);

public void Dispose()
{
_allocator.Dispose();
}
}
246 changes: 246 additions & 0 deletions revghost/Utility/RAII.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace revghost.Utility;

public static unsafe class RAII
{
public static readonly NativeAllocator Allocator;

public static bool IsTracking = true;

private struct Information
{

}

private static readonly Dictionary<IntPtr, Information> Tracked = new();

private static readonly NativeAllocator.Data MethodTable = new()
{
Alloc = &Alloc,
Free = &Free,
Dispose = &Dispose
};

static RAII()
{
Allocator = NativeAllocator.CreateCustomContext(
ref MethodTable
);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref int GetCounter(object obj)
{
var ptr = (int*) Unsafe.As<object, IntPtr>(ref obj);
return ref *(ptr - 1);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsRAIIObject(object obj)
{
var ptr = Unsafe.As<object, IntPtr>(ref obj);
return Tracked.ContainsKey(IntPtr.Subtract(ptr, sizeof(int)));
}

public static void Increment(object obj)
{
GetCounter(obj) += 1;
}

public static bool Decrement(object obj)
{
ref var counter = ref GetCounter(obj);
counter -= 1;
if (counter <= 0)
{
Allocator.Free(ref obj);
return true;
}

return false;
}

private static void* Alloc(ref NativeAllocator.Data data, object companion, nuint size)
{
var memory = (byte*) NativeMemory.Alloc(size + sizeof(int));
Unsafe.AsRef<int>(memory) = 0;

Console.WriteLine($"alloc at {(IntPtr) memory}");

Tracked.Add((IntPtr) memory, new Information());

return memory + sizeof(int);
}

private static bool Free(ref NativeAllocator.Data data, object companion, void* ptr)
{
var managedPtr = (IntPtr) (byte*) ptr - sizeof(int);
Console.WriteLine($"free at {managedPtr}");

if (Tracked.ContainsKey(managedPtr))
{
NativeMemory.Free((byte*) ptr - sizeof(int));
Tracked.Remove(managedPtr);

return true;
}

return false;
}

private static void Dispose(ref NativeAllocator.Data data, object companion)
{

}
}

public unsafe struct RefClass<T> : IDisposable
where T : class
{
// we use this field to throw if we are doing any structural changes on a copied RefClass<T>
private IntPtr _creationAddr;
private T _object;

public RefClass()
{
this = default;

_creationAddr = (IntPtr) Unsafe.AsPointer(ref this);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void StructuralChange()
{
var currentAddr = (IntPtr) Unsafe.AsPointer(ref this);
if (_creationAddr != IntPtr.Zero && _creationAddr != currentAddr)
throw new InvalidOperationException(
$"A RefClass<{typeof(T)}> had incoming structural changed but was copied. " +
$"Original={_creationAddr}, Current={currentAddr}"
);

_creationAddr = currentAddr;
}

public void Create()
{
StructuralChange();

_object = RAII.Allocator.New<T>();
RAII.Increment(_object);
}

public void Set(T replace)
{
StructuralChange();

var previous = _object;
if (RAII.IsRAIIObject(replace))
{
RAII.Increment(replace);
}

_object = replace;

if (RAII.IsRAIIObject(previous))
{
RAII.Decrement(previous);
}
}

public T Get()
{
if (RAII.IsRAIIObject(_object))
{
RAII.Increment(_object);
}

return _object;
}

public T GetUnsafe()
{
return _object;
}

public TRet Act<TArg, TRet>(Func<T, TArg, TRet> func, TArg arg)
{
TRet ret;
if (RAII.IsRAIIObject(_object))
{
RAII.Increment(_object);
ret = func(_object, arg);
RAII.Decrement(_object);
}
else
{
ret = func(_object, arg);
}

return ret;
}

public TRet Act<TRet>(Func<T, TRet> func)
{
TRet ret;
if (RAII.IsRAIIObject(_object))
{
RAII.Increment(_object);
ret = func(_object);
RAII.Decrement(_object);
}
else
{
ret = func(_object);
}

return ret;
}

public void Act<TArg>(Action<T, TArg> action, TArg arg)
{
if (RAII.IsRAIIObject(_object))
{
RAII.Increment(_object);
action(_object, arg);
RAII.Decrement(_object);
}
else
{
action(_object, arg);
}
}

public void Act(Action<T> action)
{
if (RAII.IsRAIIObject(_object))
{
RAII.Increment(_object);
action(_object);
RAII.Decrement(_object);
}
else
{
action(_object);
}
}

public void Dispose()
{
if (RAII.IsRAIIObject(_object))
{
RAII.Decrement(_object);
}
}

public static implicit operator RefClass<T>(T obj)
{
var ret = new RefClass<T>();
ret.Set(obj);

return ret;
}
}

0 comments on commit ebb1b7a

Please sign in to comment.