// Strongly influenced by Array.jai in Basic module. #pragma once #include "Base.h" #include "Allocator.h" // #TODO: Array.h // [x] Set allocations to use context.allocator interface // For now, I'm just disabling alignment: // [ ] Add back alignment, and make sure there's a way to pass alignment to NewArray, which gets passed to allocator.proc. // [ ] Make versions of ArrayView initializer that takes allocator as a param // [ ] Make version of array_free (ArrayView&) that takes allocator as a param // For Arena-Backed arrays use ArenaArray // #define DEFAULT_ARRAY_ALIGNMENT 16 MSVC_RUNTIME_CHECKS_OFF template struct Array { // downcasts to an ArrayView. using ValueType = T; s64 count; T* data; s64 allocated; Allocator allocator; // s64 alignment = DEFAULT_ARRAY_ALIGNMENT; Array() { memset(this, 0, sizeof(*this)); } Array(s64 new_count, bool initialize=false) { // old: NewArray ::, array_new : count = new_count; allocator = get_context_allocator(); data = NewArray(new_count, initialize); allocated = new_count; } // initializer-list type instantiation: `Array new_array = {count, data}` // (Musa) This array cannot then be resized. Why do I even have this? Do I need it? // Array(s64 new_count, void* new_data) { // count = new_count; // data = (T*)new_data; // allocator = { nullptr, nullptr }; // NOT RESIZABLE. // allocated = new_count; // } // Used by array_zero, array_copy, etc. Array(s64 new_count, void* new_data, s64 _allocated) { count = new_count; data = (T*)new_data; allocated = _allocated; allocator = get_context_allocator(); } Array(s64 new_count, void* new_data, s64 _allocated, Allocator _allocator) { count = new_count; data = (T*)new_data; allocated = _allocated; allocator = _allocator; } T& operator[](s64 index) { #if ARRAY_ENABLE_BOUNDS_CHECKING if (index < 0 || index >= count) { debug_break(); } // index out of bounds #endif return static_cast(data)[index]; } }; template bool is_resizable (Array& src) { // If we have a valid allocator, we assume this is resizeable. return src.allocator.proc != nullptr; } template bool is_valid(Array 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; return true; } template Array array_copy_zero(const Array& src) { if (!src.data || src.count == 0) { return Array(); // Return an empty array } T* new_data = NewArray(src.count, false); memset(new_data, 0, src.count * sizeof(T)); return Array(src.count, new_data, src.allocated); } template Array array_copy(const Array& src) { if (!src.data || src.count == 0) { return Array(); // Return an empty array } T* new_data = NewArray(src.count, false); memcpy(new_data, src.data, src.count * sizeof(T)); return Array(src.count, new_data, src.allocated); } template void array_reset_keeping_memory(Array& src) { src.count = 0; } template void array_free(Array& src) { if (!src.data) return; if (src.allocated == 0) return; if (src.allocator.proc != nullptr) { src.allocator.proc(Allocator_Mode::DEALLOCATE, 0, 0, src.data, src.allocator.data); } else { internal_free(src.data); } src.count = 0; src.data = nullptr; src.allocated = 0; } template void array_initialize(Array& 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 void array_reserve(Array& src, s64 desired_items) { if (desired_items <= src.allocated) return; src.data = nullptr; if (src.allocator.proc == nullptr) { src.allocator = get_context_allocator(); } Assert(src.allocator.proc != nullptr); src.data = (T*)src.allocator.proc(Allocator_Mode::RESIZE, desired_items * sizeof(T), src.allocated * sizeof(T), nullptr, src.allocator.data); Assert(src.data != nullptr); src.allocated = desired_items; } template void array_resize(Array& 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 force_inline void array_maybe_grow(Array& 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 T pop(Array& src) { auto result = src[src.count-1]; src.count -= 1; return result; } template void array_add(Array& 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 void array_add(Array& 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 s64 array_find(Array& src, T item) { ForArray(i, src) { if (src[i] == item) return i; } return -1; } template void array_ordered_remove_by_index(Array& 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 void array_ordered_remove_by_value(Array& src, T item) { auto index = array_find(src, item); if (index != -1) { array_ordered_remove_by_index(src, index); } } template void array_unordered_remove_by_index(Array& 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 s64 array_unordered_remove_by_value(Array& 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 struct ArrayView { using ValueType = T; s64 count; T* data; ArrayView() { count = 0; data = nullptr; } ArrayView(s64 new_count, bool initialize=true) { count = new_count; data = NewArray(new_count, initialize); } // #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(data)[index]; } }; template bool is_zero(ArrayView 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 bool is_valid(ArrayView 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 ArrayView array_view(s64 view_count, T* view_data) { ArrayView av; av.count = view_count; av.data = view_data; return av; } // #unsafe, no abc template ArrayView array_view(Array array) { ArrayView av; av.count = array.count; av.data = array.data; return av; } template ArrayView array_view(ArrayView array, s64 start_index, s64 view_count) { ArrayView av; av.count = view_count; // check if count exceeds Assert(start_index + view_count <= array.count); av.data = &array[start_index]; return av; } template ArrayView array_view(Array array, s64 start_index, s64 view_count) { ArrayView av; av.count = view_count; // check if count exceeds Assert(start_index + view_count <= array.count); av.data = &array[start_index]; return av; } template void array_reset_keeping_memory(ArrayView& src) { src.count = 0; } template ArrayView array_copy(const ArrayView& src) { if (!src.data || src.count == 0) { return ArrayView(); // Return an empty array } T* new_data = NewArray(src.count); memcpy(new_data, src.data, src.count * sizeof(T)); return ArrayView(src.count, (T*)new_data); } template void array_free(ArrayView& src) { if (!src.data || src.count == 0) { return; } internal_free(src.data); // we just have to trust that the context.allocator is correct for this guy! src.count = 0; src.data = nullptr; } template Array NewArrayFromValues(ArgValues... args) { constexpr s64 N = sizeof...(ArgValues); auto array = Array(N, /*initialize:*/false); T values[] = {args...}; for (s64 i = 0; i < N; i += 1) { array[i] = values[i]; } return array; } template ArrayView NewArrayViewFromValues(ArgValues... args) { constexpr s64 N = sizeof...(ArgValues); auto array = ArrayView(N, /*initialize:*/false); T values[] = {args...}; for (s64 i = 0; i < N; i += 1) { array[i] = values[i]; } return array; } MSVC_RUNTIME_CHECKS_RESTORE