using System;
namespace ARMeilleure.Common
{
///
/// Represents a fixed size table of the type , whose entries will remain at the same
/// address through out the table's lifetime.
///
/// Type of the entry in the table
class EntryTable where TEntry : unmanaged
{
private int _freeHint;
private readonly TEntry[] _table;
private readonly BitMap _allocated;
///
/// Initializes a new instance of the class with the specified capacity.
///
/// Capacity of the table
/// is less than 0
public EntryTable(int capacity)
{
if (capacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity));
}
_freeHint = 0;
_allocated = new BitMap();
_table = GC.AllocateArray(capacity, pinned: true);
}
///
/// Tries to allocate an entry in the . Returns if
/// success; otherwise returns .
///
/// Index of entry allocated in the table
/// if success; otherwise
public bool TryAllocate(out int index)
{
lock (_allocated)
{
if (_allocated.IsSet(_freeHint))
{
_freeHint = _allocated.FindFirstUnset();
}
if (_freeHint >= 0 && _freeHint < _table.Length)
{
index = _freeHint++;
_allocated.Set(index);
GetValue(index) = default;
return true;
}
}
index = 0;
return false;
}
///
/// Frees the entry at the specified .
///
/// Index of entry to free
public void Free(int index)
{
lock (_allocated)
{
_allocated.Clear(index);
}
}
///
/// Gets a reference to the entry at the specified allocated .
///
/// Index of the entry
/// Reference to the entry at the specified index
/// Entry at is not allocated
/// is outside of the table
public ref TEntry GetValue(int index)
{
if (index < 0 || index >= _table.Length)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
lock (_allocated)
{
if (!_allocated.IsSet(index))
{
throw new ArgumentException("Entry at the specified index was not allocated", nameof(index));
}
}
return ref _table[index];
}
}
}