EntryTable.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. ObjectDisposedException.ThrowIf(_disposed, this);
  52. lock (_allocated)
  53. {
  54. if (_allocated.IsSet(_freeHint))
  55. {
  56. _freeHint = _allocated.FindFirstUnset();
  57. }
  58. int index = _freeHint++;
  59. var page = GetPage(index);
  60. _allocated.Set(index);
  61. GetValue(page, index) = default;
  62. return index;
  63. }
  64. }
  65. /// <summary>
  66. /// Frees the entry at the specified <paramref name="index"/>.
  67. /// </summary>
  68. /// <param name="index">Index of entry to free</param>
  69. /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
  70. public void Free(int index)
  71. {
  72. ObjectDisposedException.ThrowIf(_disposed, this);
  73. lock (_allocated)
  74. {
  75. if (_allocated.IsSet(index))
  76. {
  77. _allocated.Clear(index);
  78. _freeHint = index;
  79. }
  80. }
  81. }
  82. /// <summary>
  83. /// Gets a reference to the entry at the specified allocated <paramref name="index"/>.
  84. /// </summary>
  85. /// <param name="index">Index of the entry</param>
  86. /// <returns>Reference to the entry at the specified <paramref name="index"/></returns>
  87. /// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
  88. /// <exception cref="ArgumentException">Entry at <paramref name="index"/> is not allocated</exception>
  89. public ref TEntry GetValue(int index)
  90. {
  91. ObjectDisposedException.ThrowIf(_disposed, this);
  92. lock (_allocated)
  93. {
  94. if (!_allocated.IsSet(index))
  95. {
  96. throw new ArgumentException("Entry at the specified index was not allocated", nameof(index));
  97. }
  98. var page = GetPage(index);
  99. return ref GetValue(page, index);
  100. }
  101. }
  102. /// <summary>
  103. /// Gets a reference to the entry at using the specified <paramref name="index"/> from the specified
  104. /// <paramref name="page"/>.
  105. /// </summary>
  106. /// <param name="page">Page to use</param>
  107. /// <param name="index">Index to use</param>
  108. /// <returns>Reference to the entry</returns>
  109. private ref TEntry GetValue(Span<TEntry> page, int index)
  110. {
  111. return ref page[index & (_pageCapacity - 1)];
  112. }
  113. /// <summary>
  114. /// Gets the page for the specified <see cref="index"/>.
  115. /// </summary>
  116. /// <param name="index">Index to use</param>
  117. /// <returns>Page for the specified <see cref="index"/></returns>
  118. private unsafe Span<TEntry> GetPage(int index)
  119. {
  120. var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity);
  121. if (!_pages.TryGetValue(pageIndex, out IntPtr page))
  122. {
  123. page = (IntPtr)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity);
  124. _pages.Add(pageIndex, page);
  125. }
  126. return new Span<TEntry>((void*)page, _pageCapacity);
  127. }
  128. /// <summary>
  129. /// Releases all resources used by the <see cref="EntryTable{TEntry}"/> instance.
  130. /// </summary>
  131. public void Dispose()
  132. {
  133. Dispose(true);
  134. GC.SuppressFinalize(this);
  135. }
  136. /// <summary>
  137. /// Releases all unmanaged and optionally managed resources used by the <see cref="EntryTable{TEntry}"/>
  138. /// instance.
  139. /// </summary>
  140. /// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
  141. protected unsafe virtual void Dispose(bool disposing)
  142. {
  143. if (!_disposed)
  144. {
  145. _allocated.Dispose();
  146. foreach (var page in _pages.Values)
  147. {
  148. NativeAllocator.Instance.Free((void*)page);
  149. }
  150. _disposed = true;
  151. }
  152. }
  153. /// <summary>
  154. /// Frees resources used by the <see cref="EntryTable{TEntry}"/> instance.
  155. /// </summary>
  156. ~EntryTable()
  157. {
  158. Dispose(false);
  159. }
  160. }
  161. }