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.

281 lines
8.2KB

  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 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 T. 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 "copyable"
  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->space - sizeOf < 0)
  141. //Todo: NativeBag is very complicated. At the time of writing of this comment I don't remember if the sizeof really needs to be aligned by 4. To check and change this comment
  142. _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f));
  143. #if ENABLE_THREAD_SAFE_CHECKS
  144. try
  145. {
  146. #endif
  147. return ref _queue->Reserve<T>(out index);
  148. #if ENABLE_THREAD_SAFE_CHECKS
  149. }
  150. finally
  151. {
  152. Volatile.Write(ref _threadSentinel, 0);
  153. }
  154. #endif
  155. }
  156. }
  157. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  158. public void Enqueue<T>(in T item) where T : struct
  159. {
  160. unsafe
  161. {
  162. BasicTests();
  163. #if ENABLE_THREAD_SAFE_CHECKS
  164. try
  165. {
  166. #endif
  167. var sizeOf = MemoryUtilities.SizeOf<T>();
  168. if (_queue->space - sizeOf < 0)
  169. //Todo: NativeBag is very complicated. At the time of writing of this comment I don't remember if the sizeof really needs to be aligned by 4. To check and change this comment
  170. _queue->Realloc((uint) ((_queue->capacity + MemoryUtilities.Align4((uint) sizeOf)) * 2.0f));
  171. _queue->Write(item);
  172. #if ENABLE_THREAD_SAFE_CHECKS
  173. }
  174. finally
  175. {
  176. Volatile.Write(ref _threadSentinel, 0);
  177. }
  178. #endif
  179. }
  180. }
  181. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  182. public void Clear()
  183. {
  184. unsafe
  185. {
  186. BasicTests();
  187. #if ENABLE_THREAD_SAFE_CHECKS
  188. try
  189. {
  190. #endif
  191. _queue->Clear();
  192. #if ENABLE_THREAD_SAFE_CHECKS
  193. }
  194. finally
  195. {
  196. Volatile.Write(ref _threadSentinel, 0);
  197. }
  198. #endif
  199. }
  200. }
  201. public T Dequeue<T>() where T : struct
  202. {
  203. unsafe
  204. {
  205. BasicTests();
  206. #if ENABLE_THREAD_SAFE_CHECKS
  207. try
  208. {
  209. #endif
  210. return _queue->Read<T>();
  211. #if ENABLE_THREAD_SAFE_CHECKS
  212. }
  213. finally
  214. {
  215. Volatile.Write(ref _threadSentinel, 0);
  216. }
  217. #endif
  218. }
  219. }
  220. internal ref T AccessReserved<T>(UnsafeArrayIndex reserverIndex) where T : struct
  221. {
  222. unsafe
  223. {
  224. BasicTests();
  225. #if ENABLE_THREAD_SAFE_CHECKS
  226. try
  227. {
  228. #endif
  229. return ref _queue->AccessReserved<T>(reserverIndex);
  230. #if ENABLE_THREAD_SAFE_CHECKS
  231. }
  232. finally
  233. {
  234. Volatile.Write(ref _threadSentinel, 0);
  235. }
  236. #endif
  237. }
  238. }
  239. [Conditional("ENABLE_DEBUG_CHECKS")]
  240. unsafe void BasicTests()
  241. {
  242. if (_queue == null)
  243. throw new Exception("SimpleNativeArray: null-access");
  244. #if ENABLE_THREAD_SAFE_CHECKS
  245. todo: this must be unit tested
  246. if (Interlocked.CompareExchange(ref _threadSentinel, 1, 0) != 0)
  247. throw new Exception("NativeBag is not thread safe, reading and writing operations can happen"
  248. + "on different threads, but not simultaneously");
  249. #endif
  250. }
  251. #if ENABLE_THREAD_SAFE_CHECKS
  252. int _threadSentinel;
  253. #endif
  254. #if UNITY_COLLECTIONS || UNITY_JOBS || UNITY_BURST
  255. [global::Unity.Collections.LowLevel.Unsafe.NativeDisableUnsafePtrRestriction]
  256. #endif
  257. unsafe UnsafeBlob* _queue;
  258. }
  259. }