Mirror of Svelto.ECS because we're a fan of it
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
7.8KB

  1. #if DEBUG && !PROFILE_SVELTO
  2. #define ENABLE_DEBUG_CHECKS
  3. #endif
  4. #if DEBUG && !PROFILE_SVELTO
  5. //#define ENABLE_THREAD_SAFE_CHECKS
  6. #endif
  7. using System;
  8. using System.Diagnostics;
  9. using System.Runtime.CompilerServices;
  10. using Svelto.Common;
  11. namespace Svelto.ECS.DataStructures
  12. {
  13. /// <summary>
  14. /// Burst friendly RingBuffer on steroid:
  15. /// it can: Enqueue/Dequeue, it wraps around if there is enough space after dequeuing
  16. /// It resizes if there isn't enough space left.
  17. /// It's a "bag", you can queue and dequeue any type and mix them. Just be sure that you dequeue what you queue! No check on type
  18. /// is done.
  19. /// You can reserve a position in the queue to update it later.
  20. /// The datastructure is a struct and it's "copiable"
  21. /// I eventually decided to call it NativeBag and not NativeBag because it can also be used as
  22. /// a preallocated memory pool where any kind of T can be stored as long as T is unmanaged
  23. /// </summary>
  24. public struct NativeBag : IDisposable
  25. {
  26. public uint count
  27. {
  28. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  29. get
  30. {
  31. unsafe
  32. {
  33. BasicTests();
  34. #if ENABLE_THREAD_SAFE_CHECKS
  35. try
  36. {
  37. #endif
  38. return _queue->size;
  39. #if ENABLE_THREAD_SAFE_CHECKS
  40. }
  41. finally
  42. {
  43. Volatile.Write(ref _threadSentinel, 0);
  44. }
  45. #endif
  46. }
  47. }
  48. }
  49. public uint capacity
  50. {
  51. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  52. get
  53. {
  54. unsafe
  55. {
  56. BasicTests();
  57. #if ENABLE_THREAD_SAFE_CHECKS
  58. try
  59. {
  60. #endif
  61. return _queue->capacity;
  62. #if ENABLE_THREAD_SAFE_CHECKS
  63. }
  64. finally
  65. {
  66. Volatile.Write(ref _threadSentinel, 0);
  67. }
  68. #endif
  69. }
  70. }
  71. }
  72. public NativeBag(Allocator allocator)
  73. {
  74. unsafe
  75. {
  76. var listData = (UnsafeBlob*) MemoryUtilities.Alloc<UnsafeBlob>((uint) 1, allocator);
  77. //clear to nullify the pointers
  78. //MemoryUtilities.MemClear((IntPtr) listData, (uint) sizeOf);
  79. listData->allocator = allocator;
  80. _queue = listData;
  81. #if ENABLE_THREAD_SAFE_CHECKS
  82. _threadSentinel = 0;
  83. #endif
  84. }
  85. }
  86. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  87. public bool IsEmpty()
  88. {
  89. unsafe
  90. {
  91. BasicTests();
  92. #if ENABLE_THREAD_SAFE_CHECKS
  93. try
  94. {
  95. #endif
  96. if (_queue == null || _queue->ptr == null)
  97. return true;
  98. #if ENABLE_THREAD_SAFE_CHECKS
  99. }
  100. finally
  101. {
  102. Volatile.Write(ref _threadSentinel, 0);
  103. }
  104. #endif
  105. }
  106. return count == 0;
  107. }
  108. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  109. public unsafe void Dispose()
  110. {
  111. if (_queue != null)
  112. {
  113. #if ENABLE_THREAD_SAFE_CHECKS
  114. //todo: this must be unit tested
  115. if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0)
  116. throw new Exception("NativeBag is not thread safe, reading and writing operations can happen" +
  117. "on different threads, but not simultaneously");
  118. try
  119. {
  120. #endif
  121. _queue->Dispose();
  122. MemoryUtilities.Free((IntPtr) _queue, _queue->allocator);
  123. _queue = null;
  124. #if ENABLE_THREAD_SAFE_CHECKS
  125. }
  126. finally
  127. {
  128. Volatile.Write(ref _threadSentinel, 0);
  129. }
  130. #endif
  131. }
  132. }
  133. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  134. public ref T ReserveEnqueue<T>(out UnsafeArrayIndex index) where T : struct
  135. {
  136. unsafe
  137. {
  138. BasicTests();
  139. var sizeOf = MemoryUtilities.SizeOf<T>();
  140. if (_queue->availableSpace - sizeOf < 0)
  141. {
  142. _queue->Realloc((_queue->capacity + (uint)sizeOf) << 1);
  143. }
  144. #if ENABLE_THREAD_SAFE_CHECKS
  145. try
  146. {
  147. #endif
  148. return ref _queue->Reserve<T>(out index);
  149. #if ENABLE_THREAD_SAFE_CHECKS
  150. }
  151. finally
  152. {
  153. Volatile.Write(ref _threadSentinel, 0);
  154. }
  155. #endif
  156. }
  157. }
  158. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  159. public void Enqueue<T>(in T item) where T : struct
  160. {
  161. unsafe
  162. {
  163. BasicTests();
  164. #if ENABLE_THREAD_SAFE_CHECKS
  165. try
  166. {
  167. #endif
  168. var sizeOf = MemoryUtilities.SizeOf<T>();
  169. if (_queue->availableSpace - sizeOf < 0)
  170. {
  171. var capacityInBytes = (_queue->capacity + (uint)sizeOf);
  172. _queue->Realloc(capacityInBytes << 1);
  173. }
  174. _queue->Enqueue(item);
  175. #if ENABLE_THREAD_SAFE_CHECKS
  176. }
  177. finally
  178. {
  179. Volatile.Write(ref _threadSentinel, 0);
  180. }
  181. #endif
  182. }
  183. }
  184. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  185. public void Clear()
  186. {
  187. unsafe
  188. {
  189. BasicTests();
  190. #if ENABLE_THREAD_SAFE_CHECKS
  191. try
  192. {
  193. #endif
  194. _queue->Clear();
  195. #if ENABLE_THREAD_SAFE_CHECKS
  196. }
  197. finally
  198. {
  199. Volatile.Write(ref _threadSentinel, 0);
  200. }
  201. #endif
  202. }
  203. }
  204. public T Dequeue<T>() where T : struct
  205. {
  206. unsafe
  207. {
  208. BasicTests();
  209. #if ENABLE_THREAD_SAFE_CHECKS
  210. try
  211. {
  212. #endif
  213. return _queue->Dequeue<T>();
  214. #if ENABLE_THREAD_SAFE_CHECKS
  215. }
  216. finally
  217. {
  218. Volatile.Write(ref _threadSentinel, 0);
  219. }
  220. #endif
  221. }
  222. }
  223. public ref T AccessReserved<T>(UnsafeArrayIndex reservedIndex) where T : struct
  224. {
  225. unsafe
  226. {
  227. BasicTests();
  228. #if ENABLE_THREAD_SAFE_CHECKS
  229. try
  230. {
  231. #endif
  232. return ref _queue->AccessReserved<T>(reservedIndex);
  233. #if ENABLE_THREAD_SAFE_CHECKS
  234. }
  235. finally
  236. {
  237. Volatile.Write(ref _threadSentinel, 0);
  238. }
  239. #endif
  240. }
  241. }
  242. [Conditional("ENABLE_DEBUG_CHECKS")]
  243. unsafe void BasicTests()
  244. {
  245. if (_queue == null)
  246. throw new Exception("SimpleNativeArray: null-access");
  247. #if ENABLE_THREAD_SAFE_CHECKS
  248. todo: this must be unit tested
  249. if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0)
  250. throw new Exception("NativeBag is not thread safe, reading and writing operations can happen"
  251. + "on different threads, but not simultaneously");
  252. #endif
  253. }
  254. #if ENABLE_THREAD_SAFE_CHECKS
  255. int _threadSentinel;
  256. #endif
  257. #if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST
  258. #if UNITY_BURST
  259. [Unity.Burst.NoAlias]
  260. #endif
  261. [Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
  262. #endif
  263. unsafe UnsafeBlob* _queue;
  264. }
  265. }