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.
286 lines
8.2 KiB
286 lines
8.2 KiB
|
|
#ifndef ULE_ARRAY_H
|
|
#define ULE_ARRAY_H
|
|
|
|
#include <new> // operator new, operator delete
|
|
|
|
#include "config.h"
|
|
#include "alloc.h" // allocators...
|
|
#include "serialize.h" // serialization
|
|
#include "string.h" // String::memcpy
|
|
#include "types.h" // type definitions
|
|
|
|
|
|
// this is a dynamic array (grows as needed)
|
|
// should work with any data type for T including primitive types
|
|
// some initial |capacity| is heap-allocated and a pointer is stored to it as |data|
|
|
// the |length| of the array, or number of filled slots is also tracked.
|
|
//
|
|
// it implements a single constructor, and operator new. no destructor, or operator overloading besides that.
|
|
// remember to use ->data or .data to actually access the underlying array.
|
|
//
|
|
// overhead:
|
|
// size of the struct will be 128 bits on 64-bit platforms
|
|
// 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.
|
|
// to avoid this, I often include this struct in other structs so there's only one pointer dereference,
|
|
// just like including a raw pointer array + length in your struct.
|
|
// because I like to do this, automatic destructors are not useful.
|
|
//
|
|
template <typename T>
|
|
struct Array {
|
|
u32 length;
|
|
u32 capacity;
|
|
T* data;
|
|
|
|
Array<T>(u32 _capacity = 8) {
|
|
ULE_TYPES_H_FTAG;
|
|
this->length = 0;
|
|
this->capacity = _capacity;
|
|
this->data = (T*) pCalloc(sizeof (T), _capacity);
|
|
}
|
|
void* operator new(size_t size) {
|
|
ULE_TYPES_H_FTAG;
|
|
return pMalloc((u32) size);
|
|
}
|
|
|
|
void checkIfShouldGrow() {
|
|
ULE_TYPES_H_FTAG;
|
|
if (this->isFull()) {
|
|
// optimal number as you approach infinite elements approaches PHI, but 1.5 sometimes works better for finite sizes
|
|
//
|
|
// it seems, that a commonly chosen growth rate of '2' is perhaps the worst possible choice.
|
|
// if you grow at a rate of 2x, you end up (likely) never being able to re-use the freed 'hole' in the heap
|
|
// for a future allocation of the same kind.
|
|
// useful reading for those interested in their own dynamic array implementations:
|
|
// (facebook's vector impl, a strictly better std::vector)
|
|
// https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md
|
|
//
|
|
this->capacity = (u32) (this->capacity * 1.5);
|
|
this->data = (T*) pRealloc(data, sizeof(T) * this->capacity);
|
|
}
|
|
}
|
|
|
|
// for when the order in the array doesn't matter, move the end of the array into the removed slot
|
|
void removeSwapWithEnd(u32 index) {
|
|
ULE_TYPES_H_FTAG;
|
|
if (this->isEmpty()) return; // overhead, maybe assert instead?
|
|
|
|
u32 end = this->length - 1;
|
|
if (index != end) {
|
|
this->data[index] = this->data[end];
|
|
}
|
|
this->pop();
|
|
}
|
|
|
|
void removeSwapWithEnd(T* addr) {
|
|
ULE_TYPES_H_FTAG;
|
|
for (u32 i = 0; i < this->length; i++) {
|
|
if ((this->data + i) == addr) {
|
|
removeSwapWithEnd(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void removeAndShrink(u32 index) {
|
|
ULE_TYPES_H_FTAG;
|
|
for (u32 i = index + 1; i < this->length; i++) {
|
|
String::memcpy(this->data[i - 1], this->data[i], sizeof(T));
|
|
}
|
|
this->length--;
|
|
}
|
|
|
|
void removeAndShrink(T* elementAddr) {
|
|
ULE_TYPES_H_FTAG;
|
|
s32 index = -1;
|
|
for (u32 i = 0; i < this->length; i++) {
|
|
if ((this->data + i) == elementAddr) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index == -1) {
|
|
return;
|
|
}
|
|
|
|
for (u32 i = index + 1; i < this->length; i++) {
|
|
String::memcpy((void*)(this->data + i - 1), (void*)(this->data + i), sizeof(T));
|
|
}
|
|
this->length--;
|
|
}
|
|
|
|
T pop() {
|
|
ULE_TYPES_H_FTAG;
|
|
if (this->isEmpty()) {
|
|
die("empty");
|
|
}
|
|
|
|
return this->data[--this->length];
|
|
}
|
|
|
|
// sometimes, you want to copy some POD data on the stack to the next position in the internal array
|
|
// that's what this does
|
|
u32 pushCopy(T* e) {
|
|
ULE_TYPES_H_FTAG;
|
|
this->checkIfShouldGrow();
|
|
|
|
String::memcpy((void*) &this->data[this->length++], e, sizeof(T));
|
|
|
|
return this->length - 1;
|
|
}
|
|
|
|
// returns the next address into which you can store a T. makes sure there's enough room first.
|
|
// it is irresponsible to call this and then not store a T in that address. this increments length,
|
|
// reserving the next spot for you.
|
|
T* pushNextAddrPromise() {
|
|
ULE_TYPES_H_FTAG;
|
|
this->checkIfShouldGrow();
|
|
|
|
return &this->data[this->length++];
|
|
}
|
|
|
|
u32 push(T e) {
|
|
ULE_TYPES_H_FTAG;
|
|
this->checkIfShouldGrow();
|
|
|
|
this->data[this->length++] = e;
|
|
|
|
return this->length - 1;
|
|
}
|
|
|
|
u32 pushMany(T* elements, u32 count) {
|
|
ULE_TYPES_H_FTAG;
|
|
// ensure we have capacity. if we have to realloc multiple times that can suck,
|
|
// but should be avoidable in practice by having an appropriately large initial capacity
|
|
while (this->capacity < (this->length + count)) {
|
|
this->capacity *= 1.5;
|
|
this->data = (T*) pRealloc(data, sizeof (T) * this->capacity);
|
|
}
|
|
|
|
u32 start = this->length;
|
|
for (u32 i1 = start, i2 = 0; i1 < count; i1++, i2++) {
|
|
this->data[this->length++] = elements[i2];
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
void reverse() {
|
|
ULE_TYPES_H_FTAG;
|
|
u32 count = this->length / 2;
|
|
|
|
for (u32 i = 0; i < count; i++) {
|
|
u32 offset = this->length - 1 - i;
|
|
|
|
T temp = this->data[i];
|
|
this->data[i] = this->data[offset];
|
|
this->data[offset] = temp;
|
|
}
|
|
}
|
|
|
|
T shift() {
|
|
ULE_TYPES_H_FTAG;
|
|
if (this->length == 0) {
|
|
return null;
|
|
}
|
|
|
|
T out = this->data[0];
|
|
this->length -= 1;
|
|
|
|
for (u32 i = 0; i < this->length; i++) {
|
|
*(this->data + i) = *(this->data + i + 1);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
T unshift(T e) {
|
|
ULE_TYPES_H_FTAG;
|
|
this->checkIfShouldGrow();
|
|
|
|
for (u32 i = 0; i < this->length; i++) {
|
|
*(this->data + i + 1) = *(this->data + i);
|
|
}
|
|
|
|
this->data[0] = e;
|
|
this->length += 1;
|
|
|
|
return this->length;
|
|
}
|
|
|
|
T peek() const {
|
|
ULE_TYPES_H_FTAG;
|
|
if (this->isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
return this->data[this->length - 1];
|
|
}
|
|
|
|
bool isEmpty() const {
|
|
ULE_TYPES_H_FTAG;
|
|
return this->length == 0;
|
|
}
|
|
|
|
bool isFull() const {
|
|
ULE_TYPES_H_FTAG;
|
|
return this->length == this->capacity;
|
|
}
|
|
|
|
void clear() {
|
|
ULE_TYPES_H_FTAG;
|
|
this->length = 0;
|
|
}
|
|
};
|
|
|
|
#ifdef ULE_CONFIG_OPTION_SERIALIZATION
|
|
extern template <typename T>
|
|
static void serialize(String* str, Array<T> array) {
|
|
ULE_TYPES_H_FTAG;
|
|
serialize(str, array.length);
|
|
serialize(str, array.capacity);
|
|
for (u32 i = 0; i < array.length; i++) {
|
|
serialize(str, array.data[i]);
|
|
}
|
|
}
|
|
|
|
extern template <typename T>
|
|
static void serialize(String* str, Array<T>* array) {
|
|
ULE_TYPES_H_FTAG;
|
|
SERIALIZE_HANDLE_NULL(str, array);
|
|
serialize(str, array->length);
|
|
serialize(str, array->capacity);
|
|
for (u32 i = 0; i < array->length; i++) {
|
|
serialize(str, array->data[i]);
|
|
}
|
|
}
|
|
|
|
extern template <typename T>
|
|
static void deserialize(char** buffer, Array<T>* array) {
|
|
ULE_TYPES_H_FTAG;
|
|
deserialize(buffer, &array->length);
|
|
deserialize(buffer, &array->capacity);
|
|
for (u32 i = 0; i < array->length; i++) {
|
|
deserialize(buffer, array->data + i);
|
|
}
|
|
}
|
|
|
|
extern template <typename T>
|
|
static void deserialize(char** buffer, Array<T>** array) {
|
|
ULE_TYPES_H_FTAG;
|
|
DESERIALIZE_HANDLE_NULL(buffer, array);
|
|
u32 length, capacity;
|
|
deserialize(buffer, &length);
|
|
deserialize(buffer, &capacity);
|
|
Array<T>* _array = new Array<T>(capacity);
|
|
_array->length = length;
|
|
for (u32 i = 0; i < _array->length; i++) {
|
|
deserialize(buffer, _array->data + i);
|
|
}
|
|
*array = _array;
|
|
}
|
|
#endif // ULE_CONFIG_OPTION_SERIALIZATION
|
|
|
|
#endif
|
|
|