EntryTable.cs 6.8 KB

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