using System; using System.Collections.Generic; using System.Numerics; using System.Runtime.InteropServices; namespace ARMeilleure.Common { /// /// Represents an expandable 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 bool _disposed; private int _freeHint; private readonly int _pageCapacity; // Number of entries per page. private readonly int _pageLogCapacity; private readonly Dictionary _pages; private readonly BitMap _allocated; /// /// Initializes a new instance of the class with the desired page size. /// /// Desired page size /// is less than 0 /// 's size is zero /// /// The actual page size may be smaller or larger depending on the size of . /// public unsafe EntryTable(int pageSize = 4096) { if (pageSize < 0) { throw new ArgumentOutOfRangeException(nameof(pageSize), "Page size cannot be negative."); } if (sizeof(TEntry) == 0) { throw new ArgumentException("Size of TEntry cannot be zero."); } _allocated = new BitMap(); _pages = new Dictionary(); _pageLogCapacity = BitOperations.Log2((uint)(pageSize / sizeof(TEntry))); _pageCapacity = 1 << _pageLogCapacity; } /// /// Allocates an entry in the . /// /// Index of entry allocated in the table /// instance was disposed public int Allocate() { if (_disposed) { throw new ObjectDisposedException(null); } lock (_allocated) { if (_allocated.IsSet(_freeHint)) { _freeHint = _allocated.FindFirstUnset(); } int index = _freeHint++; var page = GetPage(index); _allocated.Set(index); GetValue(page, index) = default; return index; } } /// /// Frees the entry at the specified . /// /// Index of entry to free /// instance was disposed public void Free(int index) { if (_disposed) { throw new ObjectDisposedException(null); } lock (_allocated) { if (_allocated.IsSet(index)) { _allocated.Clear(index); _freeHint = index; } } } /// /// Gets a reference to the entry at the specified allocated . /// /// Index of the entry /// Reference to the entry at the specified /// instance was disposed /// Entry at is not allocated public ref TEntry GetValue(int index) { if (_disposed) { throw new ObjectDisposedException(null); } Span page; lock (_allocated) { if (!_allocated.IsSet(index)) { throw new ArgumentException("Entry at the specified index was not allocated", nameof(index)); } page = GetPage(index); } return ref GetValue(page, index); } /// /// Gets a reference to the entry at using the specified from the specified /// . /// /// Page to use /// Index to use /// Reference to the entry private ref TEntry GetValue(Span page, int index) { return ref page[index & (_pageCapacity - 1)]; } /// /// Gets the page for the specified . /// /// Index to use /// Page for the specified private unsafe Span GetPage(int index) { var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity); if (!_pages.TryGetValue(pageIndex, out TEntry[] page)) { page = GC.AllocateUninitializedArray(_pageCapacity, pinned: true); _pages.Add(pageIndex, page); } return page; } } }