Skip to content

Commit f74929d

Browse files
author
LoneWandererProductions
committed
Add a custom list
1 parent eca454a commit f74929d

3 files changed

Lines changed: 325 additions & 3 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: CommonExtendedObjectsTests
4+
* FILE: IntListUnitTests.cs
5+
* PURPOSE: Tests for my custom list
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
using Microsoft.VisualStudio.TestTools.UnitTesting;
10+
using System;
11+
using System.Diagnostics;
12+
using ExtendedSystemObjects;
13+
14+
namespace CommonExtendedObjectsTests
15+
{
16+
[TestClass]
17+
public class IntListTests
18+
{
19+
/// <summary>
20+
/// Adds the pop peek behavior.
21+
/// </summary>
22+
[TestMethod]
23+
public void AddPopPeekBehavior()
24+
{
25+
using var list = new IntList(4);
26+
27+
list.Add(10);
28+
list.Add(20);
29+
list.Add(30);
30+
31+
Assert.AreEqual(3, list.Count);
32+
Assert.AreEqual(30, list.Peek());
33+
34+
var popped = list.Pop();
35+
Assert.AreEqual(30, popped);
36+
Assert.AreEqual(2, list.Count);
37+
Assert.AreEqual(20, list.Peek());
38+
39+
list[0] = 100;
40+
Assert.AreEqual(100, list[0]);
41+
42+
list.Clear();
43+
Assert.AreEqual(0, list.Count);
44+
}
45+
46+
/// <summary>
47+
/// Pops the empty throws.
48+
/// </summary>
49+
[TestMethod]
50+
[ExpectedException(typeof(InvalidOperationException))]
51+
public void PopEmptyThrows()
52+
{
53+
using var list = new IntList();
54+
list.Pop();
55+
}
56+
57+
/// <summary>
58+
/// Peeks the empty throws.
59+
/// </summary>
60+
[TestMethod]
61+
[ExpectedException(typeof(InvalidOperationException))]
62+
public void PeekEmptyThrows()
63+
{
64+
using var list = new IntList();
65+
list.Peek();
66+
}
67+
68+
/// <summary>
69+
/// Capacities the resizes correctly.
70+
/// </summary>
71+
[TestMethod]
72+
public void CapacityResizesCorrectly()
73+
{
74+
using var list = new IntList(2);
75+
for (var i = 0; i < 1000; i++)
76+
list.Add(i);
77+
78+
Assert.AreEqual(1000, list.Count);
79+
Assert.AreEqual(999, list[999]);
80+
}
81+
82+
/// <summary>
83+
/// Performances the benchmark.
84+
/// </summary>
85+
[TestMethod]
86+
public void PerformanceBenchmark()
87+
{
88+
const int N = 1_000_000;
89+
90+
// IntList timing
91+
using var intList = new IntList(N);
92+
var sw = Stopwatch.StartNew();
93+
for (var i = 0; i < N; i++)
94+
intList.Add(i);
95+
sw.Stop();
96+
var intListTime = sw.ElapsedMilliseconds;
97+
98+
// List<int> timing
99+
var list = new System.Collections.Generic.List<int>(N);
100+
sw.Restart();
101+
for (var i = 0; i < N; i++)
102+
list.Add(i);
103+
sw.Stop();
104+
var listTime = sw.ElapsedMilliseconds;
105+
106+
// Stack<int> timing
107+
var stack = new System.Collections.Generic.Stack<int>(N);
108+
sw.Restart();
109+
for (var i = 0; i < N; i++)
110+
stack.Push(i);
111+
sw.Stop();
112+
var stackTime = sw.ElapsedMilliseconds;
113+
114+
// Output times
115+
Trace.WriteLine($"IntList (unmanaged) time: {intListTime} ms");
116+
Trace.WriteLine($"List<int> time: {listTime} ms");
117+
Trace.WriteLine($"Stack<int> time: {stackTime} ms");
118+
119+
// Assert nothing here — just print results
120+
}
121+
}
122+
}

ExtendedSystemObjects/IntArray.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
* COPYRIGHT: See COPYING in the top level directory
33
* PROJECT: ExtendedSystemObjects
44
* FILE: ExtendedSystemObjects/IntArray.cs
5-
* PURPOSE: A high-performance array implementation with reduced features.
5+
* PURPOSE: A high-performance array implementation with reduced features. Limited to integer Values.
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
77
*/
88

99
using System;
10+
using System.Runtime.CompilerServices;
1011
using System.Runtime.InteropServices;
1112

1213
namespace ExtendedSystemObjects
@@ -29,6 +30,11 @@ public unsafe class IntArray : IDisposable
2930
/// </summary>
3031
private int _length;
3132

33+
/// <summary>
34+
/// The pointer
35+
/// </summary>
36+
private int* _ptr;
37+
3238
/// <summary>
3339
/// Gets the current number of elements in the array.
3440
/// </summary>
@@ -42,6 +48,7 @@ public IntArray(int size)
4248
{
4349
_length = size;
4450
_buffer = Marshal.AllocHGlobal(size * sizeof(int));
51+
_ptr = (int*)_buffer;
4552
}
4653

4754
/// <summary>
@@ -52,26 +59,29 @@ public IntArray(int size)
5259
/// <exception cref="IndexOutOfRangeException">Thrown in debug mode when the index is out of bounds.</exception>
5360
public int this[int i]
5461
{
62+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5563
get
5664
{
5765
#if DEBUG
5866
if (i < 0 || i >= _length) throw new IndexOutOfRangeException();
5967
#endif
60-
return *((int*)_buffer + i);
68+
return _ptr[i];
6169
}
70+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6271
set
6372
{
6473
#if DEBUG
6574
if (i < 0 || i >= _length) throw new IndexOutOfRangeException();
6675
#endif
67-
*((int*)_buffer + i) = value;
76+
_ptr[i] = value;
6877
}
6978
}
7079

7180
/// <summary>
7281
/// Removes the element at the specified index by shifting remaining elements left.
7382
/// </summary>
7483
/// <param name="index">The index of the element to remove.</param>
84+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
7585
public void RemoveAt(int index)
7686
{
7787
#if DEBUG
@@ -95,6 +105,7 @@ public void RemoveAt(int index)
95105
public void Resize(int newSize)
96106
{
97107
_buffer = Marshal.ReAllocHGlobal(_buffer, (IntPtr)(newSize * sizeof(int)));
108+
_ptr = (int*)_buffer;
98109
_length = newSize;
99110
}
100111

@@ -116,6 +127,14 @@ public void Clear()
116127
/// <returns>A <see cref="Span{Int32}"/> over the internal buffer.</returns>
117128
public Span<int> AsSpan() => new((void*)_buffer, _length);
118129

130+
/// <summary>
131+
/// Finalizes an instance of the <see cref="IntArray"/> class.
132+
/// </summary>
133+
~IntArray()
134+
{
135+
Dispose();
136+
}
137+
119138
/// <summary>
120139
/// Frees the unmanaged memory held by the array.
121140
/// After disposal, the instance should not be used.

ExtendedSystemObjects/IntList.cs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: ExtendedSystemObjects
4+
* FILE: ExtendedSystemObjects/IntList.cs
5+
* PURPOSE: A high-performance List implementation with reduced features. Limited to integer Values.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
using System;
10+
using System.Runtime.InteropServices;
11+
12+
namespace ExtendedSystemObjects
13+
{
14+
/// <summary>
15+
/// A high-performance list of integers backed by unmanaged memory.
16+
/// Supports fast adding, popping, and random access with minimal overhead.
17+
/// Designed for scenarios where manual memory management is needed.
18+
/// </summary>
19+
/// <seealso cref="System.IDisposable" />
20+
public unsafe class IntList : IDisposable
21+
{
22+
/// <summary>
23+
/// The buffer
24+
/// </summary>
25+
private IntPtr _buffer;
26+
27+
/// <summary>
28+
/// The count
29+
/// </summary>
30+
private int _count;
31+
32+
/// <summary>
33+
/// The capacity
34+
/// </summary>
35+
private int _capacity;
36+
37+
/// <summary>
38+
/// Gets the number of elements contained in the <see cref="IntList"/>.
39+
/// </summary>
40+
public int Count => _count;
41+
42+
/// <summary>
43+
/// Pointer to the unmanaged buffer holding the integer elements.
44+
/// </summary>
45+
private int* _ptr;
46+
47+
/// <summary>
48+
/// Initializes a new instance of the <see cref="IntList"/> class with the specified initial capacity.
49+
/// </summary>
50+
/// <param name="initialCapacity">The initial number of elements the list can hold without resizing. Default is 16.</param>
51+
public IntList(int initialCapacity = 16)
52+
{
53+
_capacity = initialCapacity > 0 ? initialCapacity : 16;
54+
_buffer = Marshal.AllocHGlobal(_capacity * sizeof(int));
55+
_ptr = (int*)_buffer;
56+
}
57+
58+
/// <summary>
59+
/// Pushes the specified value.
60+
/// </summary>
61+
/// <param name="value">The value.</param>
62+
public void Push(int value)
63+
{
64+
Add(value);
65+
}
66+
67+
/// <summary>
68+
/// Adds an integer value to the end of the list, resizing if necessary.
69+
/// </summary>
70+
/// <param name="value">The integer value to add.</param>
71+
public void Add(int value)
72+
{
73+
EnsureCapacity(_count + 1);
74+
_ptr[_count++] = value;
75+
}
76+
77+
/// <summary>
78+
/// Removes and returns the last element from the list.
79+
/// </summary>
80+
/// <returns>The last integer element in the list.</returns>
81+
/// <exception cref="InvalidOperationException">Thrown when the list is empty.</exception>
82+
public int Pop()
83+
{
84+
if (_count == 0)
85+
throw new InvalidOperationException("Stack empty");
86+
return _ptr[--_count];
87+
}
88+
89+
/// <summary>
90+
/// Returns the last element without removing it from the list.
91+
/// </summary>
92+
/// <returns>The last integer element in the list.</returns>
93+
/// <exception cref="InvalidOperationException">Thrown when the list is empty.</exception>
94+
public int Peek()
95+
{
96+
if (_count == 0)
97+
throw new InvalidOperationException("Stack empty");
98+
return _ptr[_count - 1];
99+
}
100+
101+
/// <summary>
102+
/// Gets or sets the element at the specified index.
103+
/// </summary>
104+
/// <param name="i">The zero-based index of the element to get or set.</param>
105+
/// <returns>The element at the specified index.</returns>
106+
/// <exception cref="IndexOutOfRangeException">Thrown in debug builds if the index is out of bounds.</exception>
107+
public int this[int i]
108+
{
109+
get
110+
{
111+
#if DEBUG
112+
if (i < 0 || i >= _count) throw new IndexOutOfRangeException();
113+
#endif
114+
return _ptr[i];
115+
}
116+
set
117+
{
118+
#if DEBUG
119+
if (i < 0 || i >= _count) throw new IndexOutOfRangeException();
120+
#endif
121+
_ptr[i] = value;
122+
}
123+
}
124+
125+
/// <summary>
126+
/// Ensures the capacity of the internal buffer is at least the specified minimum size.
127+
/// Resizes the buffer if necessary by doubling its capacity or setting it to the minimum required size.
128+
/// </summary>
129+
/// <param name="min">The minimum capacity required.</param>
130+
private void EnsureCapacity(int min)
131+
{
132+
if (min <= _capacity) return;
133+
134+
int newCapacity = _capacity * 2;
135+
if (newCapacity < min)
136+
newCapacity = min;
137+
138+
_buffer = Marshal.ReAllocHGlobal(_buffer, (IntPtr)(newCapacity * sizeof(int)));
139+
_ptr = (int*)_buffer;
140+
_capacity = newCapacity;
141+
}
142+
143+
/// <summary>
144+
/// Removes all elements from the list. The capacity remains unchanged.
145+
/// </summary>
146+
public void Clear()
147+
{
148+
_count = 0;
149+
}
150+
151+
/// <summary>
152+
/// Returns a span over the valid elements of the list.
153+
/// Allows fast, safe access to the underlying data.
154+
/// </summary>
155+
/// <returns>A <see cref="Span{Int32}"/> representing the list's contents.</returns>
156+
public Span<int> AsSpan() => new((void*)_buffer, _count);
157+
158+
/// <summary>
159+
/// Finalizes an instance of the <see cref="IntList"/> class, releasing unmanaged resources.
160+
/// </summary>
161+
~IntList()
162+
{
163+
Dispose();
164+
}
165+
166+
/// <summary>
167+
/// Frees unmanaged resources used by the <see cref="IntList"/>.
168+
/// After calling this method, the instance should not be used.
169+
/// </summary>
170+
public void Dispose()
171+
{
172+
if (_buffer != IntPtr.Zero)
173+
{
174+
Marshal.FreeHGlobal(_buffer);
175+
_buffer = IntPtr.Zero;
176+
}
177+
_count = 0;
178+
_capacity = 0;
179+
}
180+
}
181+
}

0 commit comments

Comments
 (0)