core_builtin_soa.odin 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. package runtime
  2. import "base:intrinsics"
  3. _ :: intrinsics
  4. /*
  5. SOA types are implemented with this sort of layout:
  6. SOA Fixed Array
  7. struct {
  8. f0: [N]T0,
  9. f1: [N]T1,
  10. f2: [N]T2,
  11. }
  12. SOA Slice
  13. struct {
  14. f0: ^T0,
  15. f1: ^T1,
  16. f2: ^T2,
  17. len: int,
  18. }
  19. SOA Dynamic Array
  20. struct {
  21. f0: ^T0,
  22. f1: ^T1,
  23. f2: ^T2,
  24. len: int,
  25. cap: int,
  26. allocator: Allocator,
  27. }
  28. A footer is used rather than a header purely to simplify access to the fields internally
  29. i.e. field index of the AOS == SOA
  30. */
  31. Raw_SOA_Footer_Slice :: struct {
  32. len: int,
  33. }
  34. Raw_SOA_Footer_Dynamic_Array :: struct {
  35. len: int,
  36. cap: int,
  37. allocator: Allocator,
  38. }
  39. @(builtin, require_results)
  40. raw_soa_footer_slice :: proc(array: ^$T/#soa[]$E) -> (footer: ^Raw_SOA_Footer_Slice) {
  41. if array == nil {
  42. return nil
  43. }
  44. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  45. footer = (^Raw_SOA_Footer_Slice)(uintptr(array) + field_count*size_of(rawptr))
  46. return
  47. }
  48. @(builtin, require_results)
  49. raw_soa_footer_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) -> (footer: ^Raw_SOA_Footer_Dynamic_Array) {
  50. if array == nil {
  51. return nil
  52. }
  53. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  54. footer = (^Raw_SOA_Footer_Dynamic_Array)(uintptr(array) + field_count*size_of(rawptr))
  55. return
  56. }
  57. raw_soa_footer :: proc{
  58. raw_soa_footer_slice,
  59. raw_soa_footer_dynamic_array,
  60. }
  61. @(builtin, require_results)
  62. make_soa_aligned :: proc($T: typeid/#soa[]$E, length: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
  63. if length <= 0 {
  64. return
  65. }
  66. footer := raw_soa_footer(&array)
  67. if size_of(E) == 0 {
  68. footer.len = length
  69. return
  70. }
  71. max_align := max(alignment, align_of(E))
  72. ti := type_info_of(typeid_of(T))
  73. ti = type_info_base(ti)
  74. si := &ti.variant.(Type_Info_Struct)
  75. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  76. total_size := 0
  77. for i in 0..<field_count {
  78. type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
  79. total_size += type.size * length
  80. total_size = align_forward_int(total_size, max_align)
  81. }
  82. allocator := allocator
  83. if allocator.procedure == nil {
  84. allocator = context.allocator
  85. }
  86. assert(allocator.procedure != nil)
  87. new_bytes: []byte
  88. new_bytes, err = allocator.procedure(
  89. allocator.data, .Alloc, total_size, max_align,
  90. nil, 0, loc,
  91. )
  92. if new_bytes == nil || err != nil {
  93. return
  94. }
  95. new_data := raw_data(new_bytes)
  96. data := uintptr(&array)
  97. offset := 0
  98. for i in 0..<field_count {
  99. type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
  100. offset = align_forward_int(offset, max_align)
  101. (^uintptr)(data)^ = uintptr(new_data) + uintptr(offset)
  102. data += size_of(rawptr)
  103. offset += type.size * length
  104. }
  105. footer.len = length
  106. return
  107. }
  108. @(builtin, require_results)
  109. make_soa_slice :: proc($T: typeid/#soa[]$E, length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
  110. return make_soa_aligned(T, length, align_of(E), allocator, loc)
  111. }
  112. @(builtin, require_results)
  113. make_soa_dynamic_array :: proc($T: typeid/#soa[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
  114. context.allocator = allocator
  115. reserve_soa(&array, 0, loc) or_return
  116. return array, nil
  117. }
  118. @(builtin, require_results)
  119. make_soa_dynamic_array_len :: proc($T: typeid/#soa[dynamic]$E, #any_int length: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
  120. context.allocator = allocator
  121. resize_soa(&array, length, loc) or_return
  122. return array, nil
  123. }
  124. @(builtin, require_results)
  125. make_soa_dynamic_array_len_cap :: proc($T: typeid/#soa[dynamic]$E, #any_int length, capacity: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error {
  126. context.allocator = allocator
  127. reserve_soa(&array, capacity, loc) or_return
  128. resize_soa(&array, length, loc) or_return
  129. return array, nil
  130. }
  131. @builtin
  132. make_soa :: proc{
  133. make_soa_slice,
  134. make_soa_dynamic_array,
  135. make_soa_dynamic_array_len,
  136. make_soa_dynamic_array_len_cap,
  137. }
  138. @builtin
  139. resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
  140. if array == nil {
  141. return nil
  142. }
  143. reserve_soa(array, length, loc) or_return
  144. footer := raw_soa_footer(array)
  145. footer.len = length
  146. return nil
  147. }
  148. @builtin
  149. non_zero_resize_soa :: proc(array: ^$T/#soa[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error {
  150. if array == nil {
  151. return nil
  152. }
  153. non_zero_reserve_soa(array, length, loc) or_return
  154. footer := raw_soa_footer(array)
  155. footer.len = length
  156. return nil
  157. }
  158. @builtin
  159. reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
  160. return _reserve_soa(array, capacity, true, loc)
  161. }
  162. @builtin
  163. non_zero_reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error {
  164. return _reserve_soa(array, capacity, false, loc)
  165. }
  166. _reserve_soa :: proc(array: ^$T/#soa[dynamic]$E, capacity: int, zero_memory: bool, loc := #caller_location) -> Allocator_Error {
  167. if array == nil {
  168. return nil
  169. }
  170. old_cap := cap(array)
  171. if capacity <= old_cap {
  172. return nil
  173. }
  174. if array.allocator.procedure == nil {
  175. array.allocator = context.allocator
  176. }
  177. assert(array.allocator.procedure != nil)
  178. footer := raw_soa_footer(array)
  179. if size_of(E) == 0 {
  180. footer.cap = capacity
  181. return nil
  182. }
  183. ti := type_info_of(typeid_of(T))
  184. ti = type_info_base(ti)
  185. si := &ti.variant.(Type_Info_Struct)
  186. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  187. assert(footer.cap == old_cap)
  188. old_size := 0
  189. new_size := 0
  190. max_align :: align_of(E)
  191. for i in 0..<field_count {
  192. type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
  193. old_size += type.size * old_cap
  194. new_size += type.size * capacity
  195. old_size = align_forward_int(old_size, max_align)
  196. new_size = align_forward_int(new_size, max_align)
  197. }
  198. old_data := (^rawptr)(array)^
  199. new_bytes := array.allocator.procedure(
  200. array.allocator.data, .Alloc if zero_memory else .Alloc_Non_Zeroed, new_size, max_align,
  201. nil, old_size, loc,
  202. ) or_return
  203. new_data := raw_data(new_bytes)
  204. footer.cap = capacity
  205. old_offset := 0
  206. new_offset := 0
  207. for i in 0..<field_count {
  208. type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
  209. old_offset = align_forward_int(old_offset, max_align)
  210. new_offset = align_forward_int(new_offset, max_align)
  211. new_data_elem := rawptr(uintptr(new_data) + uintptr(new_offset))
  212. old_data_elem := rawptr(uintptr(old_data) + uintptr(old_offset))
  213. mem_copy(new_data_elem, old_data_elem, type.size * old_cap)
  214. (^rawptr)(uintptr(array) + i*size_of(rawptr))^ = new_data_elem
  215. old_offset += type.size * old_cap
  216. new_offset += type.size * capacity
  217. }
  218. array.allocator.procedure(
  219. array.allocator.data, .Free, 0, max_align,
  220. old_data, old_size, loc,
  221. ) or_return
  222. return nil
  223. }
  224. @builtin
  225. append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
  226. return _append_soa_elem(array, true, arg, loc)
  227. }
  228. @builtin
  229. non_zero_append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
  230. return _append_soa_elem(array, false, arg, loc)
  231. }
  232. _append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
  233. if array == nil {
  234. return 0, nil
  235. }
  236. if cap(array) <= len(array) + 1 {
  237. // Same behavior as append_soa_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY.
  238. cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY
  239. err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success
  240. }
  241. footer := raw_soa_footer(array)
  242. if size_of(E) > 0 && cap(array)-len(array) > 0 {
  243. ti := type_info_of(T)
  244. ti = type_info_base(ti)
  245. si := &ti.variant.(Type_Info_Struct)
  246. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  247. data := (^rawptr)(array)^
  248. soa_offset := 0
  249. item_offset := 0
  250. arg_copy := arg
  251. arg_ptr := &arg_copy
  252. max_align :: align_of(E)
  253. for i in 0..<field_count {
  254. type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
  255. soa_offset = align_forward_int(soa_offset, max_align)
  256. item_offset = align_forward_int(item_offset, type.align)
  257. dst := rawptr(uintptr(data) + uintptr(soa_offset) + uintptr(type.size * footer.len))
  258. src := rawptr(uintptr(arg_ptr) + uintptr(item_offset))
  259. mem_copy(dst, src, type.size)
  260. soa_offset += type.size * cap(array)
  261. item_offset += type.size
  262. }
  263. footer.len += 1
  264. return 1, err
  265. }
  266. return 0, err
  267. }
  268. @builtin
  269. append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
  270. return _append_soa_elems(array, true, args=args, loc=loc)
  271. }
  272. @builtin
  273. non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
  274. return _append_soa_elems(array, false, args=args, loc=loc)
  275. }
  276. _append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: []E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error {
  277. if array == nil {
  278. return
  279. }
  280. arg_len := len(args)
  281. if arg_len == 0 {
  282. return
  283. }
  284. if cap(array) <= len(array)+arg_len {
  285. cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len)
  286. err = _reserve_soa(array, cap, zero_memory, loc) // do not 'or_return' here as it could be a partial success
  287. }
  288. arg_len = min(cap(array)-len(array), arg_len)
  289. footer := raw_soa_footer(array)
  290. if size_of(E) > 0 && arg_len > 0 {
  291. ti := type_info_of(typeid_of(T))
  292. ti = type_info_base(ti)
  293. si := &ti.variant.(Type_Info_Struct)
  294. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  295. data := (^rawptr)(array)^
  296. soa_offset := 0
  297. item_offset := 0
  298. args_ptr := &args[0]
  299. max_align :: align_of(E)
  300. for i in 0..<field_count {
  301. type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
  302. soa_offset = align_forward_int(soa_offset, max_align)
  303. item_offset = align_forward_int(item_offset, type.align)
  304. dst := uintptr(data) + uintptr(soa_offset) + uintptr(type.size * footer.len)
  305. src := uintptr(args_ptr) + uintptr(item_offset)
  306. for j in 0..<arg_len {
  307. d := rawptr(dst + uintptr(j*type.size))
  308. s := rawptr(src + uintptr(j*size_of(E)))
  309. mem_copy(d, s, type.size)
  310. }
  311. soa_offset += type.size * cap(array)
  312. item_offset += type.size
  313. }
  314. }
  315. footer.len += arg_len
  316. return arg_len, err
  317. }
  318. // The append_soa built-in procedure appends elements to the end of an #soa dynamic array
  319. @builtin
  320. append_soa :: proc{
  321. append_soa_elem,
  322. append_soa_elems,
  323. }
  324. delete_soa_slice :: proc(array: $T/#soa[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
  325. field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
  326. when field_count != 0 {
  327. array := array
  328. ptr := (^rawptr)(&array)^
  329. free(ptr, allocator, loc) or_return
  330. }
  331. return nil
  332. }
  333. delete_soa_dynamic_array :: proc(array: $T/#soa[dynamic]$E, loc := #caller_location) -> Allocator_Error {
  334. field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
  335. when field_count != 0 {
  336. array := array
  337. ptr := (^rawptr)(&array)^
  338. footer := raw_soa_footer(&array)
  339. free(ptr, footer.allocator, loc) or_return
  340. }
  341. return nil
  342. }
  343. @builtin
  344. delete_soa :: proc{
  345. delete_soa_slice,
  346. delete_soa_dynamic_array,
  347. }
  348. clear_soa_dynamic_array :: proc(array: ^$T/#soa[dynamic]$E) {
  349. field_count :: len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E)
  350. when field_count != 0 {
  351. footer := raw_soa_footer(array)
  352. footer.len = 0
  353. }
  354. }
  355. @builtin
  356. clear_soa :: proc{
  357. clear_soa_dynamic_array,
  358. }
  359. // Converts soa slice into a soa dynamic array without cloning or allocating memory
  360. @(require_results)
  361. into_dynamic_soa :: proc(array: $T/#soa[]$E) -> #soa[dynamic]E {
  362. d: #soa[dynamic]E
  363. footer := raw_soa_footer_dynamic_array(&d)
  364. footer^ = {
  365. cap = len(array),
  366. len = 0,
  367. allocator = nil_allocator(),
  368. }
  369. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  370. array := array
  371. dynamic_data := ([^]rawptr)(&d)[:field_count]
  372. slice_data := ([^]rawptr)(&array)[:field_count]
  373. copy(dynamic_data, slice_data)
  374. return d
  375. }
  376. // `unordered_remove_soa` removed the element at the specified `index`. It does so by replacing the current end value
  377. // with the old value, and reducing the length of the dynamic array by 1.
  378. //
  379. // Note: This is an O(1) operation.
  380. // Note: If you the elements to remain in their order, use `ordered_remove_soa`.
  381. // Note: If the index is out of bounds, this procedure will panic.
  382. @builtin
  383. unordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #caller_location) #no_bounds_check {
  384. bounds_check_error_loc(loc, index, len(array))
  385. if index+1 < len(array) {
  386. ti := type_info_of(typeid_of(T))
  387. ti = type_info_base(ti)
  388. si := &ti.variant.(Type_Info_Struct)
  389. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  390. data := uintptr(array)
  391. for i in 0..<field_count {
  392. type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
  393. offset := rawptr((^uintptr)(data)^ + uintptr(index*type.size))
  394. final := rawptr((^uintptr)(data)^ + uintptr((len(array)-1)*type.size))
  395. mem_copy(offset, final, type.size)
  396. data += size_of(rawptr)
  397. }
  398. }
  399. raw_soa_footer_dynamic_array(array).len -= 1
  400. }
  401. // `ordered_remove_soa` removed the element at the specified `index` whilst keeping the order of the other elements.
  402. //
  403. // Note: This is an O(N) operation.
  404. // Note: If you the elements do not have to remain in their order, prefer `unordered_remove_soa`.
  405. // Note: If the index is out of bounds, this procedure will panic.
  406. @builtin
  407. ordered_remove_soa :: proc(array: ^$T/#soa[dynamic]$E, index: int, loc := #caller_location) #no_bounds_check {
  408. bounds_check_error_loc(loc, index, len(array))
  409. if index+1 < len(array) {
  410. ti := type_info_of(typeid_of(T))
  411. ti = type_info_base(ti)
  412. si := &ti.variant.(Type_Info_Struct)
  413. field_count := uintptr(len(E) when intrinsics.type_is_array(E) else intrinsics.type_struct_field_count(E))
  414. data := uintptr(array)
  415. for i in 0..<field_count {
  416. type := si.types[i].variant.(Type_Info_Multi_Pointer).elem
  417. offset := (^uintptr)(data)^ + uintptr(index*type.size)
  418. length := type.size*(len(array) - index - 1)
  419. mem_copy(rawptr(offset), rawptr(offset + uintptr(type.size)), length)
  420. data += size_of(rawptr)
  421. }
  422. }
  423. raw_soa_footer_dynamic_array(array).len -= 1
  424. }