EntryTable.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Numerics;
  4. namespace ARMeilleure.Common
  5. {
  6. /// <summary>
  7. /// Represents an expandable table of the type <typeparamref name="TEntry"/>, whose entries will remain at the same
  8. /// address through out the table's lifetime.
  9. /// </summary>
  10. /// <typeparam name="TEntry">Type of the entry in the table</typeparam>
  11. class EntryTable<TEntry> : IDisposable where TEntry : unmanaged
  12. {
  13. private bool _disposed;
  14. private int _freeHint;
  15. private readonly int _pageCapacity; // Number of entries per page.
  16. private readonly int _pageLogCapacity;
  17. private readonly Dictionary<int, IntPtr> _pages;
  18. private readonly BitMap _allocated;
  19. /// <summary>
  20. /// Initializes a new instance of the <see cref="EntryTable{TEntry}"/> class with the desired page size in
  21. /// bytes.
  22. /// </summary>
  23. /// <param name="pageSize">Desired page size in bytes</param>
  24. /// <exception cref="ArgumentOutOfRangeException"><paramref name="pageSize"/> is less than 0</exception>
  25. /// <exception cref="ArgumentException"><typeparamref name="TEntry"/>'s size is zero</exception>
  26. /// <remarks>
  27. /// The actual page size may be smaller or larger depending on the size of <typeparamref name="TEntry"/>.
  28. /// </remarks>
  29. public unsafe EntryTable(int pageSize = 4096)
  30. {
  31. if (pageSize < 0)
  32. {
  33. throw new ArgumentOutOfRangeException(nameof(pageSize), "Page size cannot be negative.");
  34. }
  35. if (sizeof(TEntry) == 0)
  36. {
  37. throw new ArgumentException("Size of TEntry cannot be zero.");
  38. }
  39. _allocated = new BitMap(NativeAllocator.Instance);
  40. _pages = new Dictionary<int, IntPtr>();
  41. _pageLogCapacity = BitOperations.Log2((uint)(pageSize / sizeof(TEntry)));
  42. _pageCapacity = 1 << _pageLogCapacity;
  43. }
  44. /// <summary>
  45. /// Allocates an entry in the <see cref="EntryTable{TEntry}"/>.
  46. /// </summary>
  47. /// <returns>Index of entry allocated in the table</returns>
  48. /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
  49. public int Allocate()
  50. {
  51. if (_disposed)
  52. {
  53. throw new ObjectDisposedException(null);
  54. }
  55. lock (_allocated)
  56. {
  57. if (_allocated.IsSet(_freeHint))
  58. {
  59. _freeHint = _allocated.FindFirstUnset();
  60. }
  61. int index = _freeHint++;
  62. var page = GetPage(index);
  63. _allocated.Set(index);
  64. GetValue(page, index) = default;
  65. return index;
  66. }
  67. }
  68. /// <summary>
  69. /// Frees the entry at the specified <paramref name="index"/>.
  70. /// </summary>
  71. /// <param name="index">Index of entry to free</param>
  72. /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
  73. public void Free(int index)
  74. {
  75. if (_disposed)
  76. {
  77. throw new ObjectDisposedException(null);
  78. }
  79. lock (_allocated)
  80. {
  81. if (_allocated.IsSet(index))
  82. {
  83. _allocated.Clear(index);
  84. _freeHint = index;
  85. }
  86. }
  87. }
  88. /// <summary>
  89. /// Gets a reference to the entry at the specified allocated <paramref name="index"/>.
  90. /// </summary>
  91. /// <param name="index">Index of the entry</param>
  92. /// <returns>Reference to the entry at the specified <paramref name="index"/></returns>
  93. /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
  94. /// <exception cref="ArgumentException">Entry at <paramref name="index"/> is not allocated</exception>
  95. public ref TEntry GetValue(int index)
  96. {
  97. if (_disposed)
  98. {
  99. throw new ObjectDisposedException(null);
  100. }
  101. lock (_allocated)
  102. {
  103. if (!_allocated.IsSet(index))
  104. {
  105. throw new ArgumentException("Entry at the specified index was not allocated", nameof(index));
  106. }
  107. var page = GetPage(index);
  108. return ref GetValue(page, index);
  109. }
  110. }
  111. /// <summary>
  112. /// Gets a reference to the entry at using the specified <paramref name="index"/> from the specified
  113. /// <paramref name="page"/>.
  114. /// </summary>
  115. /// <param name="page">Page to use</param>
  116. /// <param name="index">Index to use</param>
  117. /// <returns>Reference to the entry</returns>
  118. private ref TEntry GetValue(Span<TEntry> page, int index)
  119. {
  120. return ref page[index & (_pageCapacity - 1)];
  121. }
  122. /// <summary>
  123. /// Gets the page for the specified <see cref="index"/>.
  124. /// </summary>
  125. /// <param name="index">Index to use</param>
  126. /// <returns>Page for the specified <see cref="index"/></returns>
  127. private unsafe Span<TEntry> GetPage(int index)
  128. {
  129. var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity);
  130. if (!_pages.TryGetValue(pageIndex, out IntPtr page))
  131. {
  132. page = (IntPtr)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity);
  133. _pages.Add(pageIndex, page);
  134. }
  135. return new Span<TEntry>((void*)page, _pageCapacity);
  136. }
  137. /// <summary>
  138. /// Releases all resources used by the <see cref="EntryTable{TEntry}"/> instance.
  139. /// </summary>
  140. public void Dispose()
  141. {
  142. Dispose(true);
  143. GC.SuppressFinalize(this);
  144. }
  145. /// <summary>
  146. /// Releases all unmanaged and optionally managed resources used by the <see cref="EntryTable{TEntry}"/>
  147. /// instance.
  148. /// </summary>
  149. /// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
  150. protected unsafe virtual void Dispose(bool disposing)
  151. {
  152. if (!_disposed)
  153. {
  154. _allocated.Dispose();
  155. foreach (var page in _pages.Values)
  156. {
  157. NativeAllocator.Instance.Free((void*)page);
  158. }
  159. _disposed = true;
  160. }
  161. }
  162. /// <summary>
  163. /// Frees resources used by the <see cref="EntryTable{TEntry}"/> instance.
  164. /// </summary>
  165. ~EntryTable()
  166. {
  167. Dispose(false);
  168. }
  169. }
  170. }