Skip to content

Commit 49495b1

Browse files
[Core] WIP on Freeform Allocator;
1 parent 3924b1b commit 49495b1

File tree

3 files changed

+345
-0
lines changed

3 files changed

+345
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using Staple.Utilities;
2+
using System.Numerics;
3+
4+
namespace CoreTests;
5+
6+
internal class FreeformAllocatorTests
7+
{
8+
[Test]
9+
public void TestAllocate()
10+
{
11+
var allocator = new FreeformAllocator<Vector2>();
12+
13+
var entry = allocator.Allocate(20);
14+
15+
Assert.That(entry.start, Is.EqualTo(0));
16+
Assert.That(entry.length, Is.EqualTo(20));
17+
18+
var second = allocator.Allocate(30);
19+
20+
Assert.That(second.start, Is.EqualTo(20));
21+
Assert.That(second.length, Is.EqualTo(30));
22+
23+
Assert.That(allocator.buffer.Length, Is.EqualTo(50));
24+
25+
var span = allocator.Get(entry);
26+
27+
Assert.That(span.Length, Is.EqualTo(20));
28+
29+
span = allocator.Get(second);
30+
31+
Assert.That(span.Length, Is.EqualTo(30));
32+
}
33+
34+
[Test]
35+
public void TestFree()
36+
{
37+
var allocator = new FreeformAllocator<Vector2>();
38+
39+
var entry = allocator.Allocate(20);
40+
41+
Assert.That(entry.start, Is.EqualTo(0));
42+
Assert.That(entry.length, Is.EqualTo(20));
43+
44+
var second = allocator.Allocate(30);
45+
46+
Assert.That(second.start, Is.EqualTo(20));
47+
Assert.That(second.length, Is.EqualTo(30));
48+
49+
allocator.Free(entry);
50+
51+
Assert.That(entry.freed, Is.True);
52+
53+
Assert.That(allocator.freeEntries, Has.Count.EqualTo(1));
54+
55+
var free = allocator.freeEntries.FirstOrDefault();
56+
57+
Assert.That(free.start, Is.EqualTo(0));
58+
Assert.That(free.length, Is.EqualTo(20));
59+
}
60+
61+
[Test]
62+
public void TestCompact()
63+
{
64+
var allocator = new FreeformAllocator<Vector2>();
65+
66+
var entry = allocator.Allocate(20);
67+
68+
Assert.That(entry.start, Is.EqualTo(0));
69+
Assert.That(entry.length, Is.EqualTo(20));
70+
71+
var span = allocator.Get(entry);
72+
73+
Assert.That(span.Length, Is.EqualTo(20));
74+
75+
for(var i = 0; i < span.Length; i++)
76+
{
77+
span[i] = Vector2.One;
78+
}
79+
80+
var second = allocator.Allocate(30);
81+
82+
Assert.That(second.start, Is.EqualTo(20));
83+
Assert.That(second.length, Is.EqualTo(30));
84+
85+
span = allocator.Get(second);
86+
87+
Assert.That(span.Length, Is.EqualTo(30));
88+
89+
for (var i = 0; i < span.Length; i++)
90+
{
91+
Assert.That(span[i], Is.EqualTo(Vector2.Zero));
92+
93+
span[i] = new(0, 1);
94+
}
95+
96+
allocator.Free(entry);
97+
98+
Assert.That(allocator.freeEntries, Has.Count.EqualTo(1));
99+
100+
var free = allocator.freeEntries.FirstOrDefault();
101+
102+
Assert.That(free.start, Is.EqualTo(0));
103+
Assert.That(free.length, Is.EqualTo(20));
104+
105+
entry = allocator.Allocate(10);
106+
107+
Assert.That(entry.start, Is.EqualTo(0));
108+
Assert.That(entry.length, Is.EqualTo(10));
109+
110+
span = allocator.Get(entry);
111+
112+
Assert.That(span.Length, Is.EqualTo(10));
113+
114+
for (var i = 0; i < span.Length; i++)
115+
{
116+
Assert.That(span[i], Is.EqualTo(Vector2.One));
117+
118+
span[i] = Vector2.Zero;
119+
}
120+
121+
var third = allocator.Allocate(20);
122+
123+
Assert.That(entry.start, Is.EqualTo(0));
124+
Assert.That(entry.length, Is.EqualTo(10));
125+
126+
Assert.That(second.start, Is.EqualTo(10));
127+
Assert.That(second.length, Is.EqualTo(30));
128+
129+
Assert.That(third.start, Is.EqualTo(40));
130+
Assert.That(third.length, Is.EqualTo(20));
131+
132+
span = allocator.Get(entry);
133+
134+
for (var i = 0; i < span.Length; i++)
135+
{
136+
Assert.That(span[i], Is.EqualTo(Vector2.Zero));
137+
}
138+
139+
Assert.That(span.Length, Is.EqualTo(10));
140+
141+
span = allocator.Get(second);
142+
143+
for (var i = 0; i < span.Length; i++)
144+
{
145+
Assert.That(span[i], Is.EqualTo(new Vector2(0, 1)));
146+
}
147+
148+
Assert.That(span.Length, Is.EqualTo(30));
149+
150+
span = allocator.Get(third);
151+
152+
Assert.That(span.Length, Is.EqualTo(20));
153+
154+
for (var i = 0; i < span.Length; i++)
155+
{
156+
Assert.That(span[i], Is.EqualTo(Vector2.Zero));
157+
}
158+
}
159+
}

Engine/Staple.Core/Staple.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@
412412
<Compile Include="Serialization\App\AppSettingsHeader.cs" />
413413
<Compile Include="Utilities\DictionaryExtensions.cs" />
414414
<Compile Include="Utilities\ExpandableContainer.cs" />
415+
<Compile Include="Utilities\FreeformAllocator.cs" />
415416
<Compile Include="Utilities\GuidGenerator.cs" />
416417
<Compile Include="Utilities\IntLookupCache.cs" />
417418
<Compile Include="Utilities\ObjectCreation.cs" />
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.InteropServices;
4+
5+
namespace Staple.Utilities;
6+
7+
internal class FreeformAllocator<T> where T: unmanaged
8+
{
9+
public class Entry
10+
{
11+
public int start;
12+
public int length;
13+
public bool freed = false;
14+
}
15+
16+
internal readonly List<Entry> freeEntries = [];
17+
18+
private readonly List<Entry> entries = [];
19+
20+
public T[] buffer = [];
21+
22+
private GCHandle pinHandle;
23+
24+
internal nint pinAddress;
25+
26+
private readonly int elementSize = Marshal.SizeOf<T>();
27+
28+
private void Repin()
29+
{
30+
if (pinHandle.IsAllocated)
31+
{
32+
pinHandle.Free();
33+
}
34+
35+
pinHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
36+
37+
pinAddress = pinHandle.AddrOfPinnedObject();
38+
}
39+
40+
public void EnsurePin()
41+
{
42+
if (pinHandle.IsAllocated == false)
43+
{
44+
pinHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
45+
}
46+
}
47+
48+
public Entry Allocate(int length)
49+
{
50+
Entry outValue = null;
51+
52+
if (freeEntries.Count == 0)
53+
{
54+
var start = buffer.Length;
55+
56+
Array.Resize(ref buffer, buffer.Length + length);
57+
58+
outValue = new Entry()
59+
{
60+
start = start,
61+
length = length,
62+
};
63+
64+
entries.Add(outValue);
65+
66+
return outValue;
67+
}
68+
69+
for (var i = 0; i < freeEntries.Count; i++)
70+
{
71+
var entry = freeEntries[i];
72+
73+
if (length <= entry.length)
74+
{
75+
freeEntries.RemoveAt(i);
76+
77+
var difference = entry.length - length;
78+
79+
if(difference > 0)
80+
{
81+
freeEntries.Add(new()
82+
{
83+
start = entry.start + length,
84+
length = difference,
85+
freed = true,
86+
});
87+
}
88+
89+
outValue = new Entry()
90+
{
91+
start = entry.start,
92+
length = length,
93+
};
94+
95+
entries.Add(outValue);
96+
97+
entries.Sort((a, b) => a.start.CompareTo(b.start));
98+
99+
return outValue;
100+
}
101+
}
102+
103+
var compactedLength = buffer.Length;
104+
105+
foreach(var entry in freeEntries)
106+
{
107+
compactedLength -= entry.length;
108+
}
109+
110+
var newBuffer = new T[compactedLength + length];
111+
112+
var newPosition = 0;
113+
114+
foreach (var entry in entries)
115+
{
116+
entry.start = newPosition;
117+
118+
newPosition += entry.length;
119+
}
120+
121+
newPosition = 0;
122+
123+
var position = 0;
124+
125+
foreach (var entry in freeEntries)
126+
{
127+
var l = entry.start - position;
128+
129+
Array.Copy(buffer, position, newBuffer, newPosition, l);
130+
131+
position = entry.start + entry.length;
132+
newPosition += l;
133+
}
134+
135+
if(newPosition < compactedLength)
136+
{
137+
Array.Copy(buffer, position, newBuffer, newPosition, compactedLength - newPosition);
138+
}
139+
140+
buffer = newBuffer;
141+
142+
outValue = new Entry()
143+
{
144+
start = compactedLength,
145+
length = length,
146+
};
147+
148+
entries.Add(outValue);
149+
150+
Repin();
151+
152+
return outValue;
153+
}
154+
155+
public void Free(Entry entry)
156+
{
157+
if(entry.freed)
158+
{
159+
return;
160+
}
161+
162+
entry.freed = true;
163+
164+
entries.Remove(entry);
165+
166+
freeEntries.Add(entry);
167+
}
168+
169+
public Span<T> Get(Entry entry)
170+
{
171+
if(entry.freed ||
172+
entry.start >= buffer.Length ||
173+
entry.start + entry.length > buffer.Length)
174+
{
175+
return default;
176+
}
177+
178+
return buffer.AsSpan(entry.start, entry.length);
179+
}
180+
181+
public nint GetNative(Entry entry)
182+
{
183+
return pinAddress + elementSize * entry.start;
184+
}
185+
}

0 commit comments

Comments
 (0)