Skip to content

Instantly share code, notes, and snippets.

@okankayhan
Last active September 14, 2025 14:44
Show Gist options
  • Save okankayhan/2582516e5b7541b152194698daa5e262 to your computer and use it in GitHub Desktop.
Save okankayhan/2582516e5b7541b152194698daa5e262 to your computer and use it in GitHub Desktop.
A fixed size list for deterministic games that uses MemoryPack. Can be modified for any other serializer
[MemoryPackable(GenerateType.CircularReference, SerializeLayout.Explicit)]
public partial class DList<T> : IList<T>
{
[MemoryPackInclude][MemoryPackOrder(0)] private int _count;
[MemoryPackInclude][MemoryPackOrder(1)] private T[] _buffer;
/// <summary>
/// Current number of elements in the list
/// </summary>
[MemoryPackIgnore] public int Count => _count;
[MemoryPackIgnore] public bool IsReadOnly => false;
/// <summary>
/// Current capacity of the internal buffer
/// </summary>
[MemoryPackIgnore] public int Capacity => _buffer.Length;
/// <summary>
/// Whether the list is empty (no elements)
/// </summary>
[MemoryPackIgnore] public bool IsEmpty => _count == 0;
/// <summary>
/// Whether the list is full (no more space for new elements)
/// </summary>
[MemoryPackIgnore] public bool IsFull => _count >= _buffer.Length;
[MemoryPackConstructor]
private DList()
{
throw new NotImplementedException();
}
/// <summary>
/// Initializes a new instance with specified capacity
/// </summary>
/// <param name="capacity">Fixed capacity</param>
public DList(int capacity)
{
if (capacity < 0)
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity cannot be negative");
_buffer = new T[capacity];
_count = 0;
}
/// <summary>
/// Initializes a new instance with elements from a collection
/// </summary>
/// <param name="collection">Collection to copy elements from</param>
/// <param name="capacity">Fixed capacity</param>
public DList(IEnumerable<T> collection, int capacity)
{
if (collection == null)
throw new ArgumentNullException(nameof(collection));
if (capacity < 0)
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity cannot be negative");
_buffer = new T[capacity];
_count = 0;
foreach (var item in collection)
{
if (item != null && _count < capacity)
{
_buffer[_count] = item;
_count++;
}
}
}
/// <summary>
/// Gets or sets the element at the specified index
/// </summary>
/// <param name="index">Zero-based index</param>
/// <returns>Element at the specified index</returns>
public T this[int index]
{
get
{
if (index < 0 || index >= _count)
throw new ArgumentOutOfRangeException(nameof(index));
return _buffer[index];
}
set
{
if (index < 0 || index >= _count)
throw new ArgumentOutOfRangeException(nameof(index));
if (value == null)
throw new ArgumentNullException(nameof(value), "Cannot set null value directly. Use Remove.");
_buffer[index] = value;
}
}
/// <summary>
/// Adds an element to the end of the list if there's space
/// </summary>
/// <param name="item">Item to add</param>
public void Add(T item)
{
if (item == null || _count >= _buffer.Length)
{
throw new InvalidOperationException("Cannot add item: either item is null or list is full.");
}
_buffer[_count] = item;
_count++;
}
/// <summary>
/// Inserts an element at the specified index if there's space
/// </summary>
/// <param name="index">Zero-based index</param>
/// <param name="item">Item to insert</param>
public void Insert(int index, T item)
{
if (item == null || index < 0 || index > _count || _count >= _buffer.Length)
{
throw new InvalidOperationException("Cannot insert item: either item is null, index is out of range, or list is full.");
}
;
// Shift elements to the right
for (int i = _count; i > index; i--)
{
_buffer[i] = _buffer[i - 1];
}
_buffer[index] = item;
_count++;
}
/// <summary>
/// Removes the first occurrence of the specified item
/// </summary>
/// <param name="item">Item to remove</param>
/// <returns>True if item was found and removed</returns>
public bool Remove(T item)
{
int index = IndexOf(item);
if (index >= 0)
{
RemoveAt(index);
return true;
}
return false;
}
/// <summary>
/// Removes the element at the specified index
/// </summary>
/// <param name="index">Zero-based index</param>
public void RemoveAt(int index)
{
if (index < 0 || index >= _count)
throw new ArgumentOutOfRangeException(nameof(index));
// Shift elements to the left
for (int i = index; i < _count - 1; i++)
{
_buffer[i] = _buffer[i + 1];
}
_count--;
}
/// <summary>
/// Removes all elements that match the predicate
/// </summary>
/// <param name="predicate">Predicate to test elements</param>
/// <returns>Number of elements removed</returns>
public int RemoveAll(Predicate<T> predicate)
{
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
int removedCount = 0;
int writeIndex = 0;
for (int readIndex = 0; readIndex < _count; readIndex++)
{
if (!predicate(_buffer[readIndex]))
{
if (writeIndex != readIndex)
{
_buffer[writeIndex] = _buffer[readIndex];
}
writeIndex++;
}
else
{
removedCount++;
}
}
_count = writeIndex;
return removedCount;
}
/// <summary>
/// Removes all elements from the list
/// </summary>
public void Clear()
{
_count = 0;
}
/// <summary>
/// Determines whether the list contains the specified item
/// </summary>
/// <param name="item">Item to locate</param>
/// <returns>True if item is found</returns>
public bool Contains(T item)
{
return IndexOf(item) >= 0;
}
/// <summary>
/// Returns the zero-based index of the first occurrence of the item
/// </summary>
/// <param name="item">Item to locate</param>
/// <returns>Zero-based index, or -1 if not found</returns>
public int IndexOf(T item)
{
for (int i = 0; i < _count; i++)
{
if (EqualityComparer<T>.Default.Equals(_buffer[i], item))
return i;
}
return -1;
}
/// <summary>
/// Returns the zero-based index of the last occurrence of the item
/// </summary>
/// <param name="item">Item to locate</param>
/// <returns>Zero-based index, or -1 if not found</returns>
public int LastIndexOf(T item)
{
for (int i = _count - 1; i >= 0; i--)
{
if (EqualityComparer<T>.Default.Equals(_buffer[i], item))
return i;
}
return -1;
}
public ReadOnlySpan<T> AsReadOnlySpan()
{
return _buffer.AsSpan(0, _count);
}
/// <summary>
/// Copies the elements to a new array (allocates new array)
/// </summary>
/// <returns>New array containing all elements</returns>
public T[] ToArray()
{
var result = new T[_count];
Array.Copy(_buffer, 0, result, 0, _count);
return result;
}
/// <summary>
/// Copies elements to an existing array (no allocation)
/// </summary>
/// <param name="array">Destination array</param>
/// <param name="arrayIndex">Starting index in destination array</param>
public void CopyTo(T[] array, int arrayIndex = 0)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
if (arrayIndex < 0 || arrayIndex > array.Length)
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
if (array.Length - arrayIndex < _count)
throw new ArgumentException("Destination array is too small");
Array.Copy(_buffer, 0, array, arrayIndex, _count);
}
/// <summary>
/// Finds the first element that matches the predicate
/// </summary>
/// <param name="predicate">Predicate to test elements</param>
/// <returns>First matching element, or default(T) if not found</returns>
public T Find(Predicate<T> predicate)
{
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
for (int i = 0; i < _count; i++)
{
if (predicate(_buffer[i]))
return _buffer[i];
}
return default(T);
}
/// <summary>
/// Sorts the elements using the default comparer
/// </summary>
public void Sort()
{
Sort(0, _count, Comparer<T>.Default);
}
/// <summary>
/// Sorts the elements using a custom comparer
/// </summary>
/// <param name="comparer">Comparer to use for sorting</param>
public void Sort(IComparer<T> comparer)
{
Sort(0, _count, comparer);
}
/// <summary>
/// Sorts a range of elements
/// </summary>
/// <param name="index">Starting index</param>
/// <param name="count">Number of elements to sort</param>
/// <param name="comparer">Comparer to use for sorting</param>
public void Sort(int index, int count, IComparer<T> comparer)
{
if (index < 0 || count < 0 || index + count > _count)
throw new ArgumentOutOfRangeException();
if (comparer == null)
comparer = Comparer<T>.Default;
InsertionSort(index, count, comparer);
}
private void InsertionSort(int index, int count, IComparer<T> comparer)
{
for (int i = index + 1; i < index + count; i++)
{
T key = _buffer[i];
int j = i - 1;
// Move elements greater than key one position ahead
while (j >= index && comparer.Compare(_buffer[j], key) > 0)
{
_buffer[j + 1] = _buffer[j];
j--;
}
_buffer[j + 1] = key;
}
}
#region IEnumerable<T> Implementation
/// <summary>
/// Returns an enumerator that iterates through the collection
/// </summary>
/// <returns>Enumerator for the collection</returns>
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < _count; i++)
{
yield return _buffer[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment