using System; using System.Collections.Generic; using System.Numerics; 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 : IDisposable 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 in /// bytes. /// /// Desired page size in bytes /// 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(NativeAllocator.Instance); _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() { ObjectDisposedException.ThrowIf(_disposed, this); 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) { ObjectDisposedException.ThrowIf(_disposed, this); 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) { ObjectDisposedException.ThrowIf(_disposed, this); lock (_allocated) { if (!_allocated.IsSet(index)) { throw new ArgumentException("Entry at the specified index was not allocated", nameof(index)); } var 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 IntPtr page)) { page = (IntPtr)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity); _pages.Add(pageIndex, page); } return new Span((void*)page, _pageCapacity); } /// /// Releases all resources used by the instance. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases all unmanaged and optionally managed resources used by the /// instance. /// /// to dispose managed resources also; otherwise just unmanaged resouces protected unsafe virtual void Dispose(bool disposing) { if (!_disposed) { _allocated.Dispose(); foreach (var page in _pages.Values) { NativeAllocator.Instance.Free((void*)page); } _disposed = true; } } /// /// Frees resources used by the instance. /// ~EntryTable() { Dispose(false); } } }