Musa-Cpp-Lib-V2/lib/Base/Array.h
2025-11-19 22:00:36 -05:00

367 lines
9.6 KiB
C++

// Strongly influenced by Array.jai in Basic module.
#pragma once
#include "Base.h"
#include "Allocator.h"
#define DEFAULT_ARRAY_ALIGNMENT 16
// #NOTE: This uses `General_Purpose_Allocator` for simplicity.
// For Arena-Backed arrays use ArenaArray
MSVC_RUNTIME_CHECKS_OFF
template <typename T>
struct Array { // downcasts to an ArrayView.
using ValueType = T;
s64 count;
T* data;
s64 allocated;
s64 alignment = DEFAULT_ARRAY_ALIGNMENT;
Array() {
memset(this, 0, sizeof(*this));
alignment = DEFAULT_ARRAY_ALIGNMENT;
}
Array(s64 new_count, s64 _alignment, bool zero_memory=true) {
count = new_count;
data = (T*)GPAllocator_New(new_count * sizeof(T), _alignment);
if (zero_memory) { memset(data, 0, new_count * sizeof(T)); }
alignment = _alignment;
allocated = new_count;
}
// Use constructor delegation to pass params to above constructor
Array(s64 new_count, bool zero_memory=true)
: Array(new_count, DEFAULT_ARRAY_ALIGNMENT, zero_memory) {}
// initializer-list type instantiation: `Array<T> new_array = {count, data}`
// This is essentially an arrayview.
// (Musa) Ok, but this array cannot then be resized.
Array(s64 new_count, void* new_data) {
count = new_count;
data = (T*)new_data;
allocated = new_count;
alignment = DEFAULT_ARRAY_ALIGNMENT;
}
// Used by array_zero, array_copy, etc.
Array(s64 new_count, void* new_data, s64 _allocated, s64 _alignment) {
count = new_count; data = (T*)new_data; allocated = _allocated; alignment = _alignment;
}
T& operator[](s64 index) {
#if ARRAY_ENABLE_BOUNDS_CHECKING
if (index < 0 || index >= count) { debug_break(); } // index out of bounds
#endif
return static_cast<T*>(data)[index];
}
};
template <typename T>
bool is_valid(Array<T> src) {
if (src.count == 0) return true;
if (src.count < 0) return false;
if (src.data == nullptr) return false;
if (src.allocated < src.count) return false;
// if ((src.alignment % 8) != 0) return false; Dubious - we could want an alignment of 1
return true;
}
template <typename T>
Array<T> array_copy_zero(const Array<T>& src) {
if (!src.data || src.count == 0) {
return Array<T>(); // Return an empty array
}
void* new_data = GPAllocator_New(src.count * sizeof(T), src.alignment);
memset(new_data, 0, src.count * sizeof(T));
return Array<T>(src.count, new_data, src.allocated, src.alignment);
}
template <typename T>
Array<T> array_copy(const Array<T>& src) {
if (!src.data || src.count == 0) {
return Array<T>(); // Return an empty array
}
void* new_data = GPAllocator_New(src.count * sizeof(T), src.alignment);
memcpy(new_data, src.data, src.count * sizeof(T));
return Array<T>(src.count, new_data, src.allocated, src.alignment);
}
template <typename T>
void array_reset_count(Array<T>& src) {
src.count = 0;
}
template <typename T>
void array_free(Array<T>& src) {
GPAllocator_Delete(src.data);
src.count = 0;
src.data = nullptr;
src.allocated = 0;
}
template <typename T>
void array_initialize(Array<T>& src, s64 start, s64 end) {
for (s64 i = start; i < end; i += 1) {
// Really this can be one ini followed by a bunch of memcpy.
// For long arrays we could power-of-two double the copy out, etc.
src[i] = T(); // `new (&src[i]) T();` also works.
}
}
template <typename T>
void array_reserve(Array<T>& src, s64 desired_items) {
if (desired_items <= src.allocated) return;
src.data = (T*)GPAllocator_Resize(src.allocated * sizeof(T), src.data, desired_items * sizeof(T), src.alignment);
Assert(src.data != nullptr);
src.allocated = desired_items;
}
template <typename T>
void array_resize(Array<T>& src, s64 new_count, bool initialize=true) {
if (src.count == new_count) return;
s64 old_count = src.count;
array_reserve(src, new_count);
src.count = new_count;
if (initialize) { array_initialize(src, old_count, new_count); }
}
template <typename T>
force_inline void array_maybe_grow(Array<T>& src) {
if (src.count >= src.allocated) {
// Replace with Basic.max(8, 2 * src.count).
s64 reserve = 8;
if (src.count * 2 > reserve) { reserve = src.count * 2; }
array_reserve(src, reserve);
}
}
template <typename T>
T pop(Array<T>& src) {
auto result = src[src.count-1]; // how do I dereference?
src.count -= 1;
return result;
}
template <typename T, typename U>
void array_add(Array<T>& src, U new_item) {
static_assert(sizeof(U) <= sizeof(T));
auto new_count = src.count + 1;
array_maybe_grow(src);
T new_item_casted = (T)new_item;
src.count += 1;
memcpy(&src[src.count-1], &new_item_casted, sizeof(T));
}
template <typename T>
void array_add(Array<T>& src, T new_item) {
auto new_count = src.count + 1;
array_maybe_grow(src);
src.count += 1;
memcpy(&src[src.count-1], &new_item, sizeof(T));
}
template <typename T>
s64 array_find(Array<T>& src, T item) {
ForArray(i, src) {
if (src[i] == item) return i;
}
return -1;
}
template <typename T>
void array_ordered_remove_by_index(Array<T>& src, s64 index) {
Assert(index >= 0); Assert(index < src.count);
for (s64 i = index; i < src.count-1; i += 1) {
src[i] = src[i + 1];
}
src.count -= 1;
}
template <typename T>
void array_ordered_remove_by_value(Array<T>& src, T item) {
auto index = array_find(src, item);
if (index != -1) { array_ordered_remove_by_index(src, index); }
}
template <typename T>
void array_unordered_remove_by_index(Array<T>& src, s64 index) {
Assert(index >= 0); Assert(index < src.count);
auto last_index = src.count - 1;
if (index != last_index) { // Copy back item:
memcpy(&src[index], &src[last_index], sizeof(T));
}
src.count -= 1;
}
template <typename T>
s64 array_unordered_remove_by_value(Array<T>& src, T item, s64 max_count_to_remove) {
s64 removed_count = 0;
for (s64 i = 0; i < src.count; i += 1) {
if (src[i] == item) {
removed_count += 1;
array_unordered_remove_by_index(src, i);
debug_break(); // haven't quite figured this one out yet.
i -= 1; // check this element again??
if (max_count_to_remove == removed_count) { break; }
}
}
return removed_count;
}
template <typename T>
struct ArrayView {
using ValueType = T;
s64 count;
T* data;
ArrayView() { count = 0; data = nullptr; }
// If we don't need reallocation or alignments
ArrayView(s64 new_count, s64 alignment=DEFAULT_ARRAY_ALIGNMENT, bool zero_memory=true) {
count = new_count;
data = (T*)GPAllocator_New(new_count * sizeof(T), alignment);
if (zero_memory) { memset(data, 0, new_count * sizeof(T)); }
}
// #Note: use array_view to create slices or to downcast to ArrayView!
ArrayView(s64 _count, T* _data) {
count = _count;
data = _data;
}
T& operator[](s64 index) {
#if ARRAY_ENABLE_BOUNDS_CHECKING
if (index < 0 || index >= count) { debug_break(); } // index out of bounds
#endif
return static_cast<T*>(data)[index];
}
};
template <typename T>
bool is_zero(ArrayView<T> src) {
if (src.count == 0) return true;
return false;
}
// #NOTE: procedures should be robust to arrays with count of zero!
// Whether or not this is an error is procedure specific, but for most
// things, there is a default behavior that is expected.
template <typename T>
bool is_valid(ArrayView<T> src) {
if (src.count < 0) return false;
if (src.count == 0) return true;
if (src.data == nullptr) return false;
// #TODO: For debug builds we can use VirtualQuery to check if
// all pages are writable, but that seems excessive for now.
return true;
}
template <typename T>
ArrayView<T> array_view(s64 view_count, T* view_data) {
ArrayView<T> av;
av.count = view_count;
av.data = view_data;
return av;
} // #unsafe, no abc
template <typename T>
ArrayView<T> array_view(Array<T> array) {
ArrayView<T> av;
av.count = array.count;
av.data = array.data;
return av;
}
template <typename T>
ArrayView<T> array_view(ArrayView<T> array, s64 start_index, s64 view_count) {
ArrayView<T> av;
av.count = view_count; // check if count exceeds
Assert(start_index + view_count <= array.count);
av.data = &array[start_index];
return av;
}
template <typename T>
ArrayView<T> array_view(Array<T> array, s64 start_index, s64 view_count) {
ArrayView<T> av;
av.count = view_count; // check if count exceeds
Assert(start_index + view_count <= array.count);
av.data = &array[start_index];
return av;
}
template <typename T>
void array_reset_count(ArrayView<T>& src) {
src.count = 0;
}
template <typename T>
ArrayView<T> array_copy(const ArrayView<T>& src) {
if (!src.data || src.count == 0) {
return ArrayView<T>(); // Return an empty array
}
void* new_data = GPAllocator_New(src.count * sizeof(T), DEFAULT_ARRAY_ALIGNMENT);
memcpy(new_data, src.data, src.count * sizeof(T));
return ArrayView<T>(src.count, (T*)new_data);
}
template <typename T>
void array_free(ArrayView<T>& src) {
if (!src.data || src.count == 0) { return; }
GPAllocator_Delete(src.data);
src.count = 0;
src.data = nullptr;
}
template <typename T, typename... ArgValues>
Array<T> NewArrayFromValues(ArgValues... args) {
constexpr s64 N = sizeof...(ArgValues);
auto array = Array<T>(N, /*initialize:*/false);
T values[] = {args...};
for (s64 i = 0; i < N; i += 1) {
array[i] = values[i];
}
return array;
}
template <typename T, typename... ArgValues>
ArrayView<T> NewArrayViewFromValues(ArgValues... args) {
constexpr s64 N = sizeof...(ArgValues);
auto array = ArrayView<T>(N, /*initialize:*/false);
T values[] = {args...};
for (s64 i = 0; i < N; i += 1) {
array[i] = values[i];
}
return array;
}
MSVC_RUNTIME_CHECKS_RESTORE