Skip to content

Commit d36cb44

Browse files
_zero_ cost resize of ArenaList in some cases
1 parent 552ddc4 commit d36cb44

File tree

2 files changed

+39
-4
lines changed

2 files changed

+39
-4
lines changed

Flamui/Arena.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ public class Arena : IDisposable
77
{
88
public VirtualBuffer VirtualBuffer;
99

10+
/// <summary>
11+
/// Counts how many allocations happened (calls to <see cref="Allocate{T}"/> or <see cref="AllocateSlice{T}"/>)
12+
/// This is useful for dynamic arrays that can make growing more efficient when no other allocation happened in between resizes
13+
/// </summary>
14+
public int AllocNum { get; private set; }
15+
1016
private readonly Dictionary<object, GCHandle> objectToHandle = [];
1117

1218
public Arena(VirtualBuffer virtualBuffer)
@@ -33,6 +39,7 @@ public ManagedRef<T> AddReference<T>(T obj) where T : class
3339

3440
public void Reset()
3541
{
42+
AllocNum = 0;
3643
VirtualBuffer.Reset();
3744
foreach (var (_, value) in objectToHandle)
3845
{
@@ -43,6 +50,8 @@ public void Reset()
4350

4451
public unsafe T* Allocate<T>(T value) where T : unmanaged
4552
{
53+
AllocNum++;
54+
4655
var span = VirtualBuffer.AllocateRange(sizeof(T));
4756
fixed (byte* ptr = span)
4857
{
@@ -54,6 +63,8 @@ public void Reset()
5463

5564
public unsafe Slice<T> AllocateSlice<T>(int count) where T : unmanaged
5665
{
66+
AllocNum++;
67+
5768
if (count == 0)
5869
return new Slice<T>(null, 0);
5970

Flamui/ArenaList.cs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,59 @@
22

33
namespace Flamui;
44

5+
//todo, implement IEnumerable
6+
7+
/// <summary>
8+
/// Basic dynamic array on the arena
9+
/// </summary>
10+
/// <typeparam name="T"></typeparam>
511
public struct ArenaList<T> where T : unmanaged
612
{
713
private Arena? _arena;
814
private Slice<T> _backingSlice;
15+
private int _backingSliceAllocateNum;
916

1017
public int Count;
1118
public int Capacity => _backingSlice.Count;
1219

1320
public ArenaList(Arena arena, int initialCapacity)
1421
{
22+
ArgumentNullException.ThrowIfNull(arena);
23+
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(initialCapacity);
24+
1525
_arena = arena;
1626
_backingSlice = _arena.AllocateSlice<T>(initialCapacity);
27+
_backingSliceAllocateNum = _arena.AllocNum;
1728
}
1829

19-
public void Add(T value)
30+
public unsafe void Add(T value)
2031
{
2132
_arena ??= Ui.Arena;
2233
if (Capacity == 0) //in case the ArenaList isn't initialized via the constructor
2334
{
2435
_backingSlice = _arena.AllocateSlice<T>(1);
36+
_backingSliceAllocateNum = _arena.AllocNum;
2537
}
2638

2739
Debug.Assert(Count <= Capacity);
2840

2941
if (Count == Capacity)
3042
{
31-
var newSlice = _arena.AllocateSlice<T>(Capacity * 2);
32-
_backingSlice.Span.CopyTo(newSlice.Span);
33-
_backingSlice = newSlice;
43+
//if there hasn't been another allocation on the arena, we don't need to allocate a new slice, we can just "extend" the current one
44+
45+
//todo, this isn't really elegant or easy to understand, would be better to have a method directly on the
46+
//arena, that can tell you if a slice is at the end of the arena, and therefore allows *zero* cost resize
47+
if (_backingSliceAllocateNum == _arena.AllocNum)
48+
{
49+
_arena.AllocateSlice<T>(_backingSlice.Count);
50+
_backingSlice = new Slice<T>(_backingSlice.Items, _backingSlice.Count * 2);
51+
}
52+
else
53+
{
54+
var newSlice = _arena.AllocateSlice<T>(Capacity * 2);
55+
_backingSlice.Span.CopyTo(newSlice.Span);
56+
_backingSlice = newSlice;
57+
}
3458
}
3559

3660
_backingSlice[Count] = value;

0 commit comments

Comments
 (0)