Skip to content

Commit fb60a0f

Browse files
committed
Refactor memory management and add new utilities
- Updated `AllocationCallbacks.cs` to use `NativeMemory` for allocation and deallocation in .NET 5.0+. - Incremented `PackageVersion` in `Hexa.NET.Utilities.csproj` from 2.2.1 to 2.2.2. - Refactored `SemaphoreLight.cs` for improved state management and error handling. - Changed `StrBuilder` to a regular struct for enhanced flexibility. - Enhanced read/write operations in `ReadWriteLock.cs`. - Improved memory management and hash code handling in `UnsafeDictionary.cs`. - Removed outdated sorting methods in `Utils.cs` and introduced new generic `Swap` and `QSort` methods. - Modified `Program.cs` to utilize unsafe code for pointer manipulation and sorting. - Added `UnsafeMutex.cs` for cross-platform mutex implementation. - Introduced `Utils.Sorting.cs` for a generic quicksort implementation. - Added `Utf8StringBuilder.cs` for dynamic UTF-8 string building utilities.
1 parent d2bec05 commit fb60a0f

File tree

11 files changed

+525
-186
lines changed

11 files changed

+525
-186
lines changed

Hexa.NET.Utilities/AllocationCallbacks.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace Hexa.NET.Utilities
22
{
3+
using System.Drawing;
34
using System.Runtime.InteropServices;
45

56
public unsafe class AllocationCallbacks : IAllocationCallbacks
@@ -90,7 +91,12 @@ public void ReportDuplicateInstances()
9091
#endif
9192
)
9293
{
93-
void* ptr = (void*)Marshal.AllocHGlobal((nint)size); ;
94+
#if NET5_0_OR_GREATER
95+
void* ptr = NativeMemory.Alloc(size);
96+
#else
97+
void* ptr = (void*)Marshal.AllocHGlobal((nint)size);
98+
#endif
99+
94100
#if TRACELEAK
95101
Allocation allocation = new(ptr, size, name);
96102
lock (allocations)
@@ -110,7 +116,11 @@ public void ReportDuplicateInstances()
110116
#if TRACELEAK
111117
Remove(ptr);
112118
#endif
113-
ptr = (void*)Marshal.ReAllocHGlobal((nint)ptr, (nint)size); ;
119+
#if NET5_0_OR_GREATER
120+
ptr = NativeMemory.Realloc(ptr, size);
121+
#else
122+
ptr = (void*)Marshal.ReAllocHGlobal((nint)ptr, (nint)size);
123+
#endif
114124
#if TRACELEAK
115125
Allocation allocation = new(ptr, size, name);
116126
lock (allocations)
@@ -126,7 +136,11 @@ public void Free(void* ptr)
126136
#if TRACELEAK
127137
Remove(ptr);
128138
#endif
139+
#if NET5_0_OR_GREATER
140+
NativeMemory.Free(ptr);
141+
#else
129142
Marshal.FreeHGlobal((nint)ptr);
143+
#endif
130144
}
131145

132146
#if !NETSTANDARD2_1_OR_GREATER && !NET5_0_OR_GREATER

Hexa.NET.Utilities/Hexa.NET.Utilities.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<PropertyGroup>
2020
<PackageId>Hexa.NET.Utilities</PackageId>
2121
<AssemblyVersion>1.0.0</AssemblyVersion>
22-
<PackageVersion>2.2.1</PackageVersion>
22+
<PackageVersion>2.2.2</PackageVersion>
2323
<Authors>Juna</Authors>
2424
<AssemblyName>Hexa.NET.Utilities</AssemblyName>
2525
<PackageProjectUrl>https://github.com/HexaEngine/Hexa.NET.Utilities</PackageProjectUrl>

Hexa.NET.Utilities/SemaphoreLight.cs

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,65 +4,46 @@
44

55
public struct SemaphoreLight
66
{
7-
private int currentCount;
7+
private volatile int count;
88
private readonly int maxCount;
9-
private int spinLock;
109

1110
public SemaphoreLight(int initialCount, int maxCount)
1211
{
13-
if (initialCount > maxCount || initialCount < 0 || maxCount <= 0)
12+
if (initialCount < 0 || maxCount <= 0 || initialCount > maxCount)
1413
{
15-
throw new ArgumentException("Invalid semaphore initial or max count");
14+
throw new ArgumentException("Invalid semaphore initialization values.");
1615
}
1716

18-
currentCount = initialCount;
17+
count = initialCount;
1918
this.maxCount = maxCount;
20-
spinLock = 0;
2119
}
2220

23-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
24-
private void AcquireLock()
25-
{
26-
while (Interlocked.CompareExchange(ref spinLock, 1, 0) != 0)
27-
{
28-
Thread.Yield(); // Minimiert CPU-Belastung, aber ist noch kein Spin-Waiting
29-
}
30-
}
21+
public readonly int CurrentCount => count;
3122

3223
[MethodImpl(MethodImplOptions.AggressiveInlining)]
33-
private void ReleaseLock()
34-
{
35-
Volatile.Write(ref spinLock, 0);
36-
}
37-
3824
public void Wait()
3925
{
4026
while (true)
4127
{
42-
AcquireLock();
43-
if (currentCount > 0)
28+
int oldCount = count;
29+
if (count > 0 && Interlocked.CompareExchange(ref count, oldCount - 1, oldCount) == oldCount)
4430
{
45-
currentCount--;
46-
ReleaseLock();
47-
break;
31+
return;
4832
}
49-
ReleaseLock();
50-
Thread.Sleep(1); // Minimale Blockierung, um CPU zu entlasten, kein Spin-Waiting
33+
34+
Thread.Yield();
5135
}
5236
}
5337

38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5439
public void Release()
5540
{
56-
AcquireLock();
57-
if (currentCount < maxCount)
41+
if (count >= maxCount)
5842
{
59-
currentCount++;
43+
throw new SemaphoreFullException("Semaphore already at maximum count.");
6044
}
61-
else
62-
{
63-
throw new InvalidOperationException("Semaphore released too many times");
64-
}
65-
ReleaseLock();
45+
46+
Interlocked.Increment(ref count);
6647
}
6748
}
6849
}

Hexa.NET.Utilities/Text/StrBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using System;
44
using System.Globalization;
55

6-
public unsafe ref struct StrBuilder
6+
public unsafe struct StrBuilder
77
{
88
public int Index;
99
public byte* Buffer;
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
namespace Hexa.NET.Utilities.Text
2+
{
3+
using System;
4+
using System.Globalization;
5+
using System.Runtime.CompilerServices;
6+
7+
public unsafe class Utf8StringBuilder : IDisposable
8+
{
9+
private const int DefaultCapacity = 1024 * 16;
10+
public int Index;
11+
private byte* buffer;
12+
private int capacity;
13+
14+
public Utf8StringBuilder()
15+
{
16+
buffer = (byte*)Alloc(DefaultCapacity);
17+
capacity = DefaultCapacity;
18+
}
19+
20+
public static readonly Utf8StringBuilder Shared = new();
21+
22+
public byte* Buffer => buffer;
23+
24+
public int Capacity
25+
{
26+
get => capacity;
27+
set
28+
{
29+
buffer = (byte*)ReAlloc(buffer, value);
30+
capacity = value;
31+
}
32+
}
33+
34+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
35+
private void EnsureCapacity(int newCapacity)
36+
{
37+
if (newCapacity > capacity)
38+
{
39+
Capacity = Math.Max(newCapacity, capacity * 2);
40+
}
41+
}
42+
43+
public void Append(string text)
44+
{
45+
int result = 0;
46+
while (result == 0)
47+
{
48+
result = Utf8Formatter.ConvertUtf16ToUtf8(text, buffer + Index, capacity - Index);
49+
if (result == 0) EnsureCapacity(capacity + text.Length * 2);
50+
}
51+
Index += result;
52+
}
53+
54+
public void Append(ReadOnlySpan<byte> text)
55+
{
56+
EnsureCapacity(capacity + text.Length);
57+
byte* start = buffer + Index;
58+
byte* ptr = start;
59+
byte* end = buffer + capacity;
60+
int i = 0;
61+
while (ptr != end && i < text.Length)
62+
{
63+
*ptr = text[i];
64+
ptr++; i++;
65+
}
66+
int written = (int)(ptr - start);
67+
Index += written;
68+
}
69+
70+
public void Append(char c)
71+
{
72+
int result = 0;
73+
while (result == 0)
74+
{
75+
result = Utf8Formatter.ConvertUtf16ToUtf8(c, buffer + Index, capacity - Index);
76+
if (result == 0) EnsureCapacity(capacity + 2);
77+
}
78+
Index += result;
79+
}
80+
81+
public void Append(byte c)
82+
{
83+
EnsureCapacity(capacity + 1);
84+
if (Index + 1 >= capacity) return;
85+
buffer[Index++] = c;
86+
}
87+
88+
public void Append(double value, int digits = -1)
89+
{
90+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index, digits);
91+
}
92+
93+
public void Append(float value, int digits = -1)
94+
{
95+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index, digits);
96+
}
97+
98+
public void Append(int value)
99+
{
100+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index);
101+
}
102+
103+
public void Append(uint value)
104+
{
105+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index);
106+
}
107+
108+
public void Append(long value)
109+
{
110+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index);
111+
}
112+
113+
public void Append(ulong value)
114+
{
115+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index);
116+
}
117+
118+
public void Append(short value)
119+
{
120+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index);
121+
}
122+
123+
public void Append(ushort value)
124+
{
125+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index);
126+
}
127+
128+
public void Append(DateTime value, string format, CultureInfo cultureInfo)
129+
{
130+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index, format, cultureInfo);
131+
}
132+
133+
public void Append(DateTime value, string format)
134+
{
135+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index, format);
136+
}
137+
138+
public void Append(DateTime value)
139+
{
140+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index);
141+
}
142+
143+
public void Append(TimeSpan value, string format, CultureInfo cultureInfo)
144+
{
145+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index, format, cultureInfo);
146+
}
147+
148+
public void Append(TimeSpan value, string format)
149+
{
150+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index, format);
151+
}
152+
153+
public void Append(TimeSpan value)
154+
{
155+
Index += Utf8Formatter.Format(value, buffer + Index, capacity - Index);
156+
}
157+
158+
public void AppendByteSize(long value, bool addSuffixSpace, int digits = -1)
159+
{
160+
Index += Utf8Formatter.FormatByteSize(buffer + Index, capacity - Index, value, addSuffixSpace, digits);
161+
}
162+
163+
public void End()
164+
{
165+
if (Index >= capacity)
166+
{
167+
Index = capacity - 1;
168+
}
169+
buffer[Index] = 0;
170+
}
171+
172+
public void Reset()
173+
{
174+
Index = 0;
175+
}
176+
177+
public void Dispose()
178+
{
179+
if (buffer != null)
180+
{
181+
Free(buffer);
182+
buffer = null;
183+
capacity = 0;
184+
Index = 0;
185+
}
186+
}
187+
188+
public static implicit operator byte*(Utf8StringBuilder builder) => builder.buffer;
189+
}
190+
}

Hexa.NET.Utilities/Threading/ReadWriteLock.cs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,8 @@ public void Dispose()
4242

4343
public void BeginRead()
4444
{
45-
readLock.Reset();
46-
4745
writeLock.Wait();
48-
46+
readLock.Reset();
4947
readSemaphore.Wait();
5048
}
5149

@@ -57,19 +55,17 @@ public IDisposable BeginReadBlock()
5755

5856
public void EndRead()
5957
{
60-
readSemaphore.Release();
61-
if (readSemaphore.CurrentCount == maxReader)
58+
var value = readSemaphore.Release();
59+
if (value == maxReader - 1)
6260
{
6361
readLock.Set();
6462
}
6563
}
6664

6765
public void BeginWrite()
6866
{
69-
writeLock.Reset();
70-
7167
readLock.Wait();
72-
68+
writeLock.Reset();
7369
writeSemaphore.Wait();
7470
}
7571

@@ -81,9 +77,8 @@ public IDisposable BeginWriteBlock()
8177

8278
public void EndWrite()
8379
{
84-
writeSemaphore.Release();
85-
86-
if (writeSemaphore.CurrentCount == maxWriter)
80+
var value = writeSemaphore.Release();
81+
if (value == maxWriter - 1)
8782
{
8883
writeLock.Set();
8984
}

0 commit comments

Comments
 (0)