Skip to content

Instantly share code, notes, and snippets.

@st4rdog
Last active July 4, 2024 16:18
Show Gist options
  • Save st4rdog/954506f3f0076b37baac2e21c364bc5a to your computer and use it in GitHub Desktop.
Save st4rdog/954506f3f0076b37baac2e21c364bc5a to your computer and use it in GitHub Desktop.
Unity C# - Garbage-free List with Add/Remove functions
// Drop-in replacement for List<T>. Presized List so no garbage when adding/removing.
// Further reading:
// - Collections without the boxing - https://www.jacksondunstan.com/articles/5148
// - https://stackoverflow.com/questions/3737997/why-implement-ienumerablet-if-i-can-just-define-one-getenumerator
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ListNonAlloc<T> : IEnumerable<T>
{
private T[] _array;
private int _count;
private int _expandSize;
private ListNonAllocEnumerator _enumerator;
private class ListNonAllocEnumerator : IEnumerator<T>
{
private ListNonAlloc<T> _list;
private int _currentIndex = -1;
public ListNonAllocEnumerator(ListNonAlloc<T> list)
{
_list = list;
}
public T Current => _list[_currentIndex];
object IEnumerator.Current => Current;
public bool MoveNext()
{
_currentIndex++;
return _currentIndex < _list.Count;
}
public void Reset()
{
_currentIndex = -1;
}
public void Dispose()
{
// Dispose resources if needed
}
}
public IEnumerator<T> GetEnumerator()
{
_enumerator.Reset();
return _enumerator;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <param name="expandSize">How much the array will expand when adding at max capacity.</param>
public ListNonAlloc(int capacity, int expandSize = 20)
{
_array = new T[capacity];
_count = 0;
_expandSize = expandSize;
_enumerator = new ListNonAllocEnumerator(this);
}
public int Count => _count;
public int Capacity => _array.Length;
public void Add(T item)
{
if (_count < _array.Length)
{
_array[_count] = item;
_count++;
}
else
{
// Auto-expand
var newCapacity = _array.Length + _expandSize;
var newArray = new T[newCapacity];
_array.AsSpan().CopyTo(newArray);
_array = newArray;
_array[_count] = item;
_count++;
}
}
public void Remove(T item)
{
int index = -1;
for (int i = 0; i < _count; i++)
{
if (EqualityComparer<T>.Default.Equals(_array[i], item))
{
index = i;
break;
}
}
if (index >= 0)
{
// Shift elements to fill the gap
for (int i = index; i < _count - 1; i++)
{
_array[i] = _array[i + 1];
}
_count--;
}
}
public void RemoveAt(int index)
{
if (index >= 0 && index < _count)
{
// Shift elements to fill the gap
for (int i = index; i < _count - 1; i++)
{
_array[i] = _array[i + 1];
}
_count--;
}
else
{
// TODO: Optionally handle the case where the index is out of bounds
Debug.LogError("Index out of bounds.");
}
}
public bool Contains(T item)
{
for (int i = 0; i < _count; i++)
{
if (EqualityComparer<T>.Default.Equals(_array[i], item))
{
return true;
}
}
return false;
}
public T this[int index]
{
get
{
if (index >= 0 && index < _count)
{
return _array[index];
}
else
{
// Optionally handle the case where the index is out of bounds
Debug.LogError("Index out of bounds.");
return default;
}
}
set
{
if (index >= 0 && index < _count)
{
_array[index] = value;
}
else
{
// Optionally handle the case where the index is out of bounds
Debug.LogError("Index out of bounds.");
}
}
}
public void Clear()
{
_count = 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment