A collection of basic/generally desirable code I use across multiple C++ projects.
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.

275 lines
7.4 KiB

1 year ago
  1. #ifndef ARRAY_H
  2. #define ARRAY_H
  3. #include <new> // operator new, operator delete
  4. #include "alloc.h" // allocators...
  5. #include "serialize.h" // serialization
  6. #include "string.h" // String::memcpy
  7. #include "types.h" // type definitions
  8. // this is a dynamic array (grows as needed)
  9. // should work with any data type for T including primitive types
  10. // some initial |capacity| is heap-allocated and a pointer is stored to it as |data|
  11. // the |length| of the array, or number of filled slots is also tracked.
  12. //
  13. // it implements a single constructor, and operator new. no destructor, or operator overloading besides that.
  14. // remember to use ->data or .data to actually access the underlying array.
  15. //
  16. // overhead:
  17. // size of the struct will be 128 bits on 64-bit platforms
  18. // note that if you heap allocate this structure and store it on another struct, you will have to chase two pointers to get at the data.
  19. // to avoid this, I often include this struct in other structs so there's only one pointer dereference,
  20. // just like including a raw pointer array + length in your struct.
  21. // because I like to do this, automatic destructors are not useful.
  22. //
  23. template <typename T>
  24. struct Array {
  25. u32 length;
  26. u32 capacity;
  27. T* data;
  28. Array<T>(u32 _capacity = 8) {
  29. TYPES_H_FTAG;
  30. this->length = 0;
  31. this->capacity = _capacity;
  32. this->data = (T*) pCalloc(sizeof (T), _capacity);
  33. }
  34. void* operator new(size_t size) {
  35. TYPES_H_FTAG;
  36. return pMalloc((u32) size);
  37. }
  38. void checkIfShouldGrow() {
  39. TYPES_H_FTAG;
  40. if (this->isFull()) {
  41. // optimal number as you approach infinite elements approaches PHI, but 1.5 sometimes works better for finite sizes
  42. // more testing is probably needed
  43. this->capacity = (u32) (this->capacity * 1.5);
  44. this->data = (T*) pRealloc(data, sizeof(T) * this->capacity);
  45. }
  46. }
  47. // for when the order in the array doesn't matter, move the end of the array into the removed slot
  48. void removeSwapWithEnd(u32 index) {
  49. TYPES_H_FTAG;
  50. if (this->isEmpty()) return; // overhead, maybe assert instead?
  51. u32 end = this->length - 1;
  52. if (index != end) {
  53. this->data[index] = this->data[end];
  54. }
  55. this->pop();
  56. }
  57. void removeSwapWithEnd(T* addr) {
  58. TYPES_H_FTAG;
  59. for (u32 i = 0; i < this->length; i++) {
  60. if ((this->data + i) == addr) {
  61. removeSwapWithEnd(i);
  62. return;
  63. }
  64. }
  65. }
  66. void removeAndShrink(u32 index) {
  67. TYPES_H_FTAG;
  68. for (u32 i = index + 1; i < this->length; i++) {
  69. String::memcpy(this->data[i - 1], this->data[i], sizeof(T));
  70. }
  71. this->length--;
  72. }
  73. void removeAndShrink(T* elementAddr) {
  74. TYPES_H_FTAG;
  75. s32 index = -1;
  76. for (u32 i = 0; i < this->length; i++) {
  77. if ((this->data + i) == elementAddr) {
  78. index = i;
  79. break;
  80. }
  81. }
  82. if (index == -1) {
  83. return;
  84. }
  85. for (u32 i = index + 1; i < this->length; i++) {
  86. String::memcpy((void*)(this->data + i - 1), (void*)(this->data + i), sizeof(T));
  87. }
  88. this->length--;
  89. }
  90. T pop() {
  91. TYPES_H_FTAG;
  92. if (this->isEmpty()) {
  93. die("empty");
  94. }
  95. return this->data[--this->length];
  96. }
  97. // sometimes, you want to copy some POD data on the stack to the next position in the internal array
  98. // that's what this does
  99. u32 pushCopy(T* e) {
  100. TYPES_H_FTAG;
  101. this->checkIfShouldGrow();
  102. String::memcpy((void*) &this->data[this->length++], e, sizeof(T));
  103. return this->length - 1;
  104. }
  105. // returns the next address into which you can store a T. makes sure there's enough room first.
  106. // it is irresponsible to call this and then not store a T in that address. this increments length,
  107. // reserving the next spot for you.
  108. T* pushNextAddrPromise() {
  109. TYPES_H_FTAG;
  110. this->checkIfShouldGrow();
  111. return &this->data[this->length++];
  112. }
  113. u32 push(T e) {
  114. TYPES_H_FTAG;
  115. this->checkIfShouldGrow();
  116. this->data[this->length++] = e;
  117. return this->length - 1;
  118. }
  119. u32 pushMany(T* elements, u32 count) {
  120. TYPES_H_FTAG;
  121. // ensure we have capacity. if we have to realloc multiple times that can suck,
  122. // but should be avoidable in practice by having an appropriately large initial capacity
  123. while (this->capacity < (this->length + count)) {
  124. this->capacity *= 1.5;
  125. this->data = (T*) pRealloc(data, sizeof (T) * this->capacity);
  126. }
  127. u32 start = this->length;
  128. for (u32 i1 = start, i2 = 0; i1 < count; i1++, i2++) {
  129. this->data[this->length++] = elements[i2];
  130. }
  131. return start;
  132. }
  133. void reverse() {
  134. TYPES_H_FTAG;
  135. u32 count = this->length / 2;
  136. for (u32 i = 0; i < count; i++) {
  137. u32 offset = this->length - 1 - i;
  138. T temp = this->data[i];
  139. this->data[i] = this->data[offset];
  140. this->data[offset] = temp;
  141. }
  142. }
  143. T shift() {
  144. TYPES_H_FTAG;
  145. if (this->length == 0) {
  146. return null;
  147. }
  148. T out = this->data[0];
  149. this->length -= 1;
  150. for (u32 i = 0; i < this->length; i++) {
  151. *(this->data + i) = *(this->data + i + 1);
  152. }
  153. return out;
  154. }
  155. T unshift(T e) {
  156. TYPES_H_FTAG;
  157. this->checkIfShouldGrow();
  158. for (u32 i = 0; i < this->length; i++) {
  159. *(this->data + i + 1) = *(this->data + i);
  160. }
  161. this->data[0] = e;
  162. this->length += 1;
  163. return this->length;
  164. }
  165. T peek() const {
  166. TYPES_H_FTAG;
  167. if (this->isEmpty()) {
  168. return null;
  169. }
  170. return this->data[this->length - 1];
  171. }
  172. bool isEmpty() const {
  173. TYPES_H_FTAG;
  174. return this->length == 0;
  175. }
  176. bool isFull() const {
  177. TYPES_H_FTAG;
  178. return this->length == this->capacity;
  179. }
  180. void clear() {
  181. TYPES_H_FTAG;
  182. this->length = 0;
  183. }
  184. };
  185. template <typename T>
  186. static void serialize(String* str, Array<T> array) {
  187. TYPES_H_FTAG;
  188. serialize(str, array.length);
  189. serialize(str, array.capacity);
  190. for (u32 i = 0; i < array.length; i++) {
  191. serialize(str, array.data[i]);
  192. }
  193. }
  194. template <typename T>
  195. static void serialize(String* str, Array<T>* array) {
  196. TYPES_H_FTAG;
  197. SERIALIZE_HANDLE_NULL(str, array);
  198. serialize(str, array->length);
  199. serialize(str, array->capacity);
  200. for (u32 i = 0; i < array->length; i++) {
  201. serialize(str, array->data[i]);
  202. }
  203. }
  204. template <typename T>
  205. static void deserialize(char** buffer, Array<T>* array) {
  206. TYPES_H_FTAG;
  207. deserialize(buffer, &array->length);
  208. deserialize(buffer, &array->capacity);
  209. for (u32 i = 0; i < array->length; i++) {
  210. deserialize(buffer, array->data + i);
  211. }
  212. }
  213. template <typename T>
  214. static void deserialize(char** buffer, Array<T>** array) {
  215. TYPES_H_FTAG;
  216. DESERIALIZE_HANDLE_NULL(buffer, array);
  217. u32 length, capacity;
  218. deserialize(buffer, &length);
  219. deserialize(buffer, &capacity);
  220. Array<T>* _array = new Array<T>(capacity);
  221. _array->length = length;
  222. for (u32 i = 0; i < _array->length; i++) {
  223. deserialize(buffer, _array->data + i);
  224. }
  225. *array = _array;
  226. }
  227. #endif