diff --git a/exe_main.cpp b/exe_main.cpp index 022225a..3dec0a2 100644 --- a/exe_main.cpp +++ b/exe_main.cpp @@ -4,7 +4,7 @@ #include "lib/third_party/dear-imgui/imgui_widgets.cpp" #include "lib/third_party/dear-imgui/imgui_draw.cpp" #include "lib/third_party/dear-imgui/imgui_tables.cpp" -#include "lib/third_party/dear-imgui/imgui_demo.cpp" +// #include "lib/third_party/dear-imgui/imgui_demo.cpp" #include "lib/third_party/dear-imgui/imgui_impl_dx11.cpp" #include "lib/third_party/dear-imgui/imgui_impl_win32.cpp" diff --git a/lib/Base/Allocator.h b/lib/Base/Allocator.h index 4099590..17ee508 100644 --- a/lib/Base/Allocator.h +++ b/lib/Base/Allocator.h @@ -9,7 +9,7 @@ #if ALLOCATOR_POISON_MEMORY_ON_ALLOCATION #define ALLOCATOR_INIT_VALUE 0xCD #else - #define ALLOCATOR_INIT_VALUE 0xCD + #define ALLOCATOR_INIT_VALUE 0 #endif enum class Allocator_Mode: s32 { @@ -107,6 +107,10 @@ template void zero_struct(T* src) { memset(src, 0, sizeof(T)); } +template void poison_struct(T* src) { + memset(src, 0xCD, sizeof(T)); +} + template T* copy_struct(T* src) { T* dst = New(false); memcpy(dst, src, sizeof(T)); diff --git a/lib/Base/Arena.cpp b/lib/Base/Arena.cpp index de38361..196a225 100644 --- a/lib/Base/Arena.cpp +++ b/lib/Base/Arena.cpp @@ -132,7 +132,7 @@ void* arena_alloc (Arena* arena, s64 byte_count) { if (result_end > arena->first_uncommitted_page) { if (result_end > arena_address_limit(arena)) { - // #TODO: Log error here: + printf("[Error] Failed to allocate because Arena is full and cannot expand!\n"); Assert(false); // Failed to allocate because arena is full and cannot expand! } else { extend_committed_pages(arena, result_end); diff --git a/lib/Base/Arena_Array.h b/lib/Base/Arena_Array.h index 9c35b0f..6308297 100644 --- a/lib/Base/Arena_Array.h +++ b/lib/Base/Arena_Array.h @@ -8,9 +8,11 @@ struct ArenaArray { // downcasts to an ArrayView. s64 count; T* data; s64 allocated; - Arena* arena; // We can probably assume arena is &Array-32 + Arena* arena; - ArenaArray() {} + ArenaArray() { + memset(this, 0, sizeof(*this)); + } T& operator[] (s64 index) { #if ARRAY_ENABLE_BOUNDS_CHECKING @@ -23,7 +25,7 @@ struct ArenaArray { // downcasts to an ArrayView. // #NOTE: I am not defining arena_array_init (ArenaArray*), because I do not want to // encourage it's usage! -// #TODO: array_free vs arena_array_destroy or arena_array_delete or +// Use arena_array_free to reset template ArenaArray* arena_array_new (s64 preallocate_count, Arena_Reserve reserve_size) { Arena* arena = next_arena(reserve_size); @@ -37,15 +39,15 @@ ArenaArray* arena_array_new (s64 preallocate_count, Arena_Reserve reserve_siz array->allocated = preallocate_count; } - array.count = 0; - array.arena = new_arena; - array.data = array_start(array); + array->count = 0; + array->arena = arena; + array->data = array_start(*array); return array; } template T* array_start (ArenaArray& array) { - return (array->arena->memory_base + ARRAY_ARENA_START_OFFSET); + return (array.arena->memory_base + ARRAY_ARENA_START_OFFSET); } template bool is_empty (ArenaArray& array) { @@ -57,11 +59,11 @@ template s64 memory_usage (ArenaArray& array) { return arena_usage_committed_bytes(array.arena); } -template void array_free (ArenaArray& array) { - array.count = 0; - array.allocated = 0; - - release_arena(array.arena, delete_extra_pages=true); +template void arena_array_free (ArenaArray& array) { + release_arena(array.arena, true); +#if BUILD_DEBUG + poison_struct(&array); +#endif } template ArrayView array_view (ArenaArray array) { @@ -97,6 +99,19 @@ template ArrayView to_view (ArenaArray& array, s64 start_offs return av; } +template void array_add (ArenaArray& array, ArrayView items) { + T* current_point = &array.data[array.count]; + s64 final_count = array.allocated + items.count; + + if (array.allocated < final_count) { + array_reserve(array, final_count); + } + + memcpy(current_point, items.data, items.count * sizeof(T)); + + array.count += items.count; +} + template void array_add (ArenaArray& array, T item) { maybe_grow(array); array.data[array.count] = item; @@ -149,7 +164,7 @@ void array_arena_realloc (ArenaArray& array, s64 new_size, s64 old_size) { if (result_end > array.arena->first_uncommitted_page) { // Critical error if we run out of address space! if (result_end > arena_address_limit(array.arena)) { - // #TODO Log error. + printf("[Error] Failed to allocate because Arena is full and cannot expand!\n"); Assert(false); // Failed to allocate because Arena is full and cannot expand return; } @@ -173,6 +188,14 @@ template void init_range (T* ptr, s64 start_offset, s64 end_offset) } } +template void poison_range (ArenaArray& array, s64 start, s64 count) { + Assert(start >= 0 && start < array.count); + Assert(start + count <= array.count); + // Check that these ranges make sense + T* start_address = &array[start]; + memset(start_address, 0xCD, count * sizeof(T)); +} + template force_inline void array_reset (ArenaArray& array) { // reset backing array: arena_reset(array.arena); diff --git a/lib/Base/Arena_Table.cpp b/lib/Base/Arena_Table.cpp index 3816935..a23280b 100644 --- a/lib/Base/Arena_Table.cpp +++ b/lib/Base/Arena_Table.cpp @@ -1,3 +1,8 @@ +// #TODO: #Arena_Table #garbage_collection in `release_arena` +// [ ] Garbage collection if we have >> 64 in a particular table for a while. +// There should be some parameters regarding what the upper limit for idle +// committed pages should be and a heuristic for maximum number of arenas waiting + // API in Arena.h #include // #TODO: Replace with Mutex (see OS_Win32.cpp) @@ -55,10 +60,13 @@ void release_arena (Arena* arena, bool delete_extra_pages) { arenas_in_flight_count[reserve_index] -= 1; - // #TODO: Garbage collection if we have >> 64 in a particular table for a while. - // + // #garbage_collection if (arena_free_table[reserve_index].count > 64) { - // release some arenas if required - // arena_delete(...) + // s64 arenas_to_delete_count = arena_free_table[reserve_index].count - 64. + // while (arenas_to_delete_count > 0) { + // arena_delete(arena_free_table[arena_free_table.count-1]); + // array_unordered_remove_by_index(..); + // arenas_to_delete_count -= 1; + // } } } diff --git a/lib/Base/Arena_Windows.cpp b/lib/Base/Arena_Windows.cpp index ed0da42..ae92b64 100644 --- a/lib/Base/Arena_Windows.cpp +++ b/lib/Base/Arena_Windows.cpp @@ -9,10 +9,9 @@ void platform_init (Arena* arena, s64 new_reserve) { VirtualAlloc(nullptr, (u64)page_aligned_reserve_size, MEM_RESERVE, PAGE_READWRITE); if (address_start == nullptr) { - // get error value and string? s32 error_code = GetLastError(); + printf("In Arena:platform_init, VirtualAlloc failed with code %d\n", error_code); return; - // #TODO(LOG) log_error("In Arena:platform_init, VirtualAlloc failed with code %d\n", error_code) } arena->memory_base = (u8*)address_start; @@ -34,7 +33,7 @@ void free_pages_down_to (Arena* arena, s64 pages_to_keep) { if (arena == nullptr) return; Assert(pages_to_keep >= 1); // Always keep one page because we bootstrap a lot. s64 bytes_to_keep = pages_to_keep * PLATFORM_MEMORY_PAGE_SIZE; - if (bytes_to_keep >= reserve_size(arena)) { + if (bytes_to_keep > reserve_size(arena)) { Assert(false); // Break in debug builds, but release we just do nothing. return; // just do nothing here. Maybe we should assert? } diff --git a/lib/Base/Array.h b/lib/Base/Array.h index fcd68c1..0504e27 100644 --- a/lib/Base/Array.h +++ b/lib/Base/Array.h @@ -53,7 +53,7 @@ template bool is_resizable (Array& src) { } template -bool is_valid(Array src) { +bool is_valid (Array src) { if (src.count == 0) return true; if (src.count < 0) return false; if (src.data == nullptr) return false; @@ -67,7 +67,7 @@ void array_zero (const Array& src) { } template -Array array_copy_zero(const Array& src) { +Array array_copy_zero (const Array& src) { if (!src.data || src.count == 0) { return Array(); // Return an empty array } @@ -79,7 +79,7 @@ Array array_copy_zero(const Array& src) { } template -Array array_copy(const Array& src) { +Array array_copy (const Array& src) { if (!src.data || src.count == 0) { return Array(); // Return an empty array } @@ -91,12 +91,12 @@ Array array_copy(const Array& src) { } template -void array_reset_keeping_memory(Array& src) { +void array_reset_keeping_memory (Array& src) { src.count = 0; } template -void array_free(Array& src) { +void array_free (Array& src) { if (!src.data) return; if (src.allocated == 0) return; if (src.allocator.proc != nullptr) { @@ -110,7 +110,7 @@ void array_free(Array& src) { } template -void array_initialize(Array& src, s64 start, s64 end) { +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. @@ -119,7 +119,7 @@ void array_initialize(Array& src, s64 start, s64 end) { } template -void array_reserve(Array& src, s64 desired_items) { +void array_reserve (Array& src, s64 desired_items) { if (desired_items <= src.allocated) return; src.data = nullptr; @@ -137,7 +137,7 @@ void array_reserve(Array& src, s64 desired_items) { } template -void array_resize(Array& src, s64 new_count, bool initialize=true) { +void array_resize (Array& src, s64 new_count, bool initialize=true) { if (src.count == new_count) return; s64 old_count = src.count; @@ -149,7 +149,7 @@ void array_resize(Array& src, s64 new_count, bool initialize=true) { } template -force_inline void array_maybe_grow(Array& src) { +force_inline void array_maybe_grow (Array& src) { if (src.count >= src.allocated) { // Replace with Basic.max(8, 2 * src.count). s64 reserve = 8; @@ -159,14 +159,14 @@ force_inline void array_maybe_grow(Array& src) { } template -T pop(Array& src) { +T pop (Array& src) { auto result = src[src.count-1]; src.count -= 1; return result; } template -void array_add(Array& src, U new_item) { +void array_add (Array& src, U new_item) { static_assert(sizeof(U) <= sizeof(T)); auto new_count = src.count + 1; array_maybe_grow(src); @@ -178,7 +178,7 @@ void array_add(Array& src, U new_item) { } template -void array_add(Array& src, T new_item) { +void array_add (Array& src, T new_item) { auto new_count = src.count + 1; array_maybe_grow(src); @@ -187,7 +187,7 @@ void array_add(Array& src, T new_item) { } template -s64 array_find(Array& src, T item) { +s64 array_find (Array& src, T item) { ForArray(i, src) { if (src[i] == item) return i; } @@ -195,7 +195,7 @@ s64 array_find(Array& src, T item) { } template -void array_ordered_remove_by_index(Array& src, s64 index) { +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) { @@ -206,13 +206,13 @@ void array_ordered_remove_by_index(Array& src, s64 index) { } template -void array_ordered_remove_by_value(Array& src, T item) { +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) { +void array_unordered_remove_by_index (Array& src, s64 index) { Assert(index >= 0); Assert(index < src.count); auto last_index = src.count - 1; @@ -225,7 +225,7 @@ void array_unordered_remove_by_index(Array& src, s64 index) { } template -s64 array_unordered_remove_by_value(Array& src, T item, s64 max_count_to_remove) { +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) { @@ -247,6 +247,11 @@ struct ArrayView { s64 count; T* data; + ArrayView(Array array) { + count = array.count; + data = array.data; + } + ArrayView() { count = 0; data = nullptr; } ArrayView(s64 new_count, bool initialize=true) { @@ -269,7 +274,7 @@ struct ArrayView { }; template -bool is_zero(ArrayView src) { +bool is_empty (ArrayView src) { if (src.count == 0) return true; return false; } @@ -278,26 +283,17 @@ bool is_zero(ArrayView src) { // 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) { +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; } +// can also use ArrayView(count, data) for initialization! 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 array_view (Array array) { ArrayView av; av.count = array.count; av.data = array.data; @@ -305,7 +301,7 @@ ArrayView array_view(Array array) { } template -ArrayView array_view(ArrayView array, s64 start_index, s64 view_count) { +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); @@ -314,7 +310,7 @@ ArrayView array_view(ArrayView array, s64 start_index, s64 view_count) { } template -ArrayView array_view(Array array, s64 start_index, s64 view_count) { +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); @@ -323,13 +319,13 @@ ArrayView array_view(Array array, s64 start_index, s64 view_count) { } template -void array_reset_keeping_memory(ArrayView& src) { +void array_reset_keeping_memory (ArrayView& src) { src.count = 0; } template -ArrayView array_copy(const ArrayView& src) { +ArrayView array_copy (const ArrayView& src) { if (!src.data || src.count == 0) { return ArrayView(); // Return an empty array } @@ -341,17 +337,18 @@ ArrayView array_copy(const ArrayView& src) { } template -void array_free(ArrayView& src) { +void array_free (ArrayView& src) { if (!src.data || src.count == 0) { return; } - + // Use with caution! internal_free(src.data); // we just have to trust that the context.allocator is correct for this guy! src.count = 0; src.data = nullptr; } +// Usage: `auto array = array_from_values(6,7,8,9,10,51);` template -Array NewArrayFromValues(ArgValues... args) { +Array array_from_values (ArgValues... args) { constexpr s64 N = sizeof...(ArgValues); auto array = Array(N, /*initialize:*/false); T values[] = {args...}; @@ -362,8 +359,10 @@ Array NewArrayFromValues(ArgValues... args) { return array; } + +// Usage `auto view = array_view_from_values(1,2,3,4,5);` template -ArrayView NewArrayViewFromValues(ArgValues... args) { +ArrayView array_view_from_values (ArgValues... args) { constexpr s64 N = sizeof...(ArgValues); auto array = ArrayView(N, /*initialize:*/false); T values[] = {args...}; diff --git a/lib/Base/Base.h b/lib/Base/Base.h index 38e634b..0f021f6 100644 --- a/lib/Base/Base.h +++ b/lib/Base/Base.h @@ -13,6 +13,10 @@ #error "CPU not supported (yet)!" #endif +#include // vsnprintf +#include // va_list, ... + + #if OS_WINDOWS #define WIN32_LEAN_AND_MEAN #include @@ -100,8 +104,6 @@ force_inline T Align (T value, s64 alignment) { return (T)intermediate; } -// #TODO: template this so it works with any pointer type -// force_inline u8* Align_To_Cache_Line(u8* address) /* force_inline s64 Align_Forwards(s64 size, s64 alignment) { return (((size + alignment - 1) / alignment) * alignment); @@ -167,9 +169,7 @@ force_inline s64 Next_Power_Of_Two(s64 v) { #endif // ForExpansions. Not sure if this is a good idea... -// #TODO: Maybe remove these. I prefer verbose and clear over this. -#define For(_idx_, _until_) for (s64 _idx_ = 0; _idx_ < _until_; ++_idx_) -#define ForBetween(_idx_, _start_, _until_) for (s64 _idx_ = _start_; _idx_ < _until_; ++_idx_) +// ↓TODO(Low priority): Maybe remove these. I prefer verbose and clear over this. #define ForArray(_idx_, _array_) for (s64 _idx_ = 0; _idx_ < (_array_).count; ++_idx_) #define ForArrayStartingAt(_it_, _array_, _start_) for (s64 _it_ = _start_; _it_ < (_array_).count; _it_ += 1) #define ForUpTo(_it_, _end_) for (s64 _it_ = 0; _it_ < _end_; _it_ += 1) diff --git a/lib/Base/Base_Thread_Context.h b/lib/Base/Base_Thread_Context.h index 2dd6a9f..790ae16 100644 --- a/lib/Base/Base_Thread_Context.h +++ b/lib/Base/Base_Thread_Context.h @@ -1,3 +1,5 @@ +struct Thread; // hacky fwd declare + struct Thread_Context { ExpandableArena* temp; // Used for temporary allocations, scratch space. ExpandableArena* arena; // general purpose local arena @@ -8,11 +10,9 @@ struct Thread_Context { u16 GPAllocator_alignment = 16; // Logger logger; // Stack_Trace* stack_trace; - // #TODO: other debug information - // #TODO: - // Array threads_created; // maybe should be linked-list? - // Thread* thread_that_created_me = nullptr; // so we can remove from above array - // Mutex thread_context_mutex; + + Array child_threads; // maybe should be linked-list? + Thread* thread_that_created_me = nullptr; // so we can remove from above array string thread_name; }; diff --git a/lib/Base/Basic.cpp b/lib/Base/Basic.cpp deleted file mode 100644 index b993e51..0000000 --- a/lib/Base/Basic.cpp +++ /dev/null @@ -1,483 +0,0 @@ -#include "Basic.h" - -#include -#include // isnan, floor -#include // qsort -#include // assert - - -Native_Error* Basic_Difference2 (ArrayView input, ArrayView& output) { - Array_Check(input); - Array_Check(output); - - // ensure enough room. Note output.count = input.count - Assert(output.count >= input.count - 1); - - ForUpTo(i, input.count-1) { - output[i] = input[i + 1] - input[i]; - } - - output.count = input.count - 1; - - return nullptr; -} - -Native_Error* Basic_Mean2 (ArrayView input, f64* mean) { - Array_Check(input); - Null_Pointer_Check(mean); - - f64 sum = 0; - ForArray(i, input) { - sum += input[i]; - } - - (*mean) = (sum / (f64)input.count); - - return nullptr; -} - -Native_Error* Basic_QuickSortInPlace (ArrayView input) { - Array_Check(input); - - qsort(input.data, input.count, sizeof(double), qsort_doubles_comparator_nonnan); - - return nullptr; -} - -Native_Error* Basic_Median2 (ArrayView unsorted_input, f64* median) { - Array_Check(unsorted_input); - Null_Pointer_Check(median); - auto input_sorted = array_copy(unsorted_input); - qsort(input_sorted.data, (u64)input_sorted.count, sizeof(f64), qsort_doubles_comparator_nonnan); - - s64 middle_element_index = unsorted_input.count / 2; - - if (unsorted_input.count % 2 == 1) { - (*median) = input_sorted[middle_element_index]; - } else { - (*median) = (input_sorted[middle_element_index - 1] + input_sorted[middle_element_index]) / 2.0; - } - - array_free(input_sorted); - - return nullptr; -} - -Native_Error* Basic_RescaleInPlace (ArrayView input, double min, double max) { - Array_Check(input); - if (max < min || max == min) { return New_Error("Min or max inputs are not valid!"); } - - f64 smallest_element; f64 largest_element; - auto error = Basic_Min2(input, &smallest_element); - if (error != nullptr) return error; - - error = Basic_Max2(input, &largest_element); - if (error != nullptr) return error; - - if (largest_element == smallest_element) - return nullptr; - - ForArray(i, input) { - input[i] = (input[i] - smallest_element) / (largest_element - smallest_element) * (max - min) + min; - } - - return nullptr; -} - -Native_Error* Basic_Min2 (ArrayView input, f64* min_out) { - Array_Check(input); - Null_Pointer_Check(min_out); - - f64 min = input[0]; - ForArrayStartingAt(i, input, 1) { - if (input[i] < min) { - min = input[i]; - } - } - - (*min_out) = min; - return nullptr; -} - -Native_Error* Basic_Max2 (ArrayView input, f64* max_out) { - Array_Check(input); - Null_Pointer_Check(max_out); - - f64 max = input[0]; - ForArrayStartingAt(i, input, 1) { - if (input[i] > max) { - max = input[i]; - } - } - - (*max_out) = max; - return nullptr; -} - -double Basic_Max (double input1, double input2) { - if (input1 > input2) return input1; - else return input2; -} - -bool Basic_Is_Positive_Real (f32 input) { - return (!(input <= 0.0 || isnan(input) || isinf(input))); -} - -bool Basic_Is_Positive_Real (f64 input) { - return (!(input <= 0.0 || isnan(input) || isinf(input))); -} - -Native_Error* Basic_Standard_Deviation2 (ArrayView input, f64* stddev) { - Array_Check(input); - Null_Pointer_Check(stddev); - - f64 mean = 0.0; - Basic_Mean2(input, &mean); - - f64 sum_of_squared_differences = 0; - ForArray(i, input) { - sum_of_squared_differences += (input[i] - mean) * (input[i] - mean); - } - - (*stddev) = sqrt(sum_of_squared_differences / (f64)input.count); - - return nullptr; -} - -Native_Error* Basic_Variance2 (ArrayView input, f64* variance) { - Array_Check(input); - Null_Pointer_Check(variance); - - f64 mean = 0.0; - Basic_Mean2(input, &mean); - - f64 sum_of_squared_differences = 0; - ForArray(i, input) { - sum_of_squared_differences += (input[i] - mean) * (input[i] - mean); - } - - f64 sample = 1; - - (*variance) = (sum_of_squared_differences / (f64)(sample ? (input.count - 1) : input.count)); - return nullptr; -} - -Native_Error* Basic_Root_Mean_Squared2 (ArrayView input, f64* rms) { - Array_Check(input); - Null_Pointer_Check(rms); - - f64 square = 0; - ForArray(i, input) { - square += pow(input[i], 2); - } - f64 mean = (square / ((f64)input.count)); - - (*rms) = sqrt(mean); - return nullptr; -} - -Native_Error* Basic_IndexSort2 (ArrayView input, ArrayView output) { - Array_Check(input); - Array_Check(output); - - ForArray(i, input) { output[i] = i; } - ForArray(i, input) { - for (s64 j = i; j > 0; j -= 1) { - if (input[output[j]] > input[output[j-1]]) { - s64 temp = output[j]; - output[j] = output[j - 1]; - output[j - 1] = temp; - } - } - } - - return nullptr; -} - -Native_Error* Basic_Count_Non_Nan2 (ArrayView input, s64* non_nan_count) { - Array_Check(input); - Null_Pointer_Check(non_nan_count); - - s64 count = 0; - - ForArray(i, input) { - if (!isnan(input[i])) { - count += 1; - } - } - - (*non_nan_count) = count; - return nullptr; -} - -Native_Error* Basic_Calculate_Percentile_New (ArrayView input, f64 percentile, f64* percentile_value_out) { - Array_Check(input); - Null_Pointer_Check(percentile_value_out); - - Assert(percentile >= 0.0 && percentile <= 1.0); - - qsort(input.data, input.count, sizeof(f64), qsort_doubles_comparator); - s64 non_nan_count = 0; - Assert(Basic_Count_Non_Nan2(input, &non_nan_count) == nullptr); - - if (non_nan_count == 0) { - (*percentile_value_out) = NAN; - return New_Warning("All values in the input array are `NAN`!"); - } - - auto r = percentile * non_nan_count; - auto k = floor(r + 0.5); - - auto kp1 = k + 1; - - // Ratio between the K and K+1 rows: - r = r - k; - - // Find indices that are out of the range 1 to n and cap them: - if (k < 1 || isnan(k)) { - k = 1; - } - - // kp1 = min( kp1, n ); - if (non_nan_count < kp1) { kp1 = (f64)non_nan_count; } - - // Use simple linear interpolation for the valid percentages: - // y = (0.5+r).*x(kp1,:)+(0.5-r).*x(k,:); // yuck. - s64 kp1_i = static_cast(kp1); - s64 k_i = static_cast(k); - - f64 y_first_part = (0.5 + r) * input[kp1_i - 1]; - f64 y_second_part = (0.5 - r) * input[k_i - 1]; - auto y = y_first_part + y_second_part; - - // Make sure that values we hit exactly are copied rather than interpolated: - if (r == -0.5) { - (*percentile_value_out) = input[k_i - 1]; - return nullptr; - } - - // Make sure that identical values are copied rather than interpolated: - if (input[k_i-1] == input[kp1_i-1]) { - (*percentile_value_out) = input[k_i - 1]; - return nullptr; - } - - (*percentile_value_out) = y; - - return nullptr; -} - -Native_Error* Basic_ReverseArrayInPlace (ArrayView input) { - Array_Check(input); - ForUpTo(i, input.count/2) { - f64 temp = input[i]; - input[i] = input[input.count - i - 1]; - input[input.count - i - 1] = temp; - } - - return nullptr; -} - -// Native_Error* Basic_Reverse_Array (int* input, int input_length) { -// for (int i = 0; i < input_length / 2; i++) { -// // Swap the ith and (input_length - i - 1)th elements -// int temp = input[i]; -// input[i] = input[input_length - i - 1]; -// input[input_length - i - 1] = temp; -// } - -// return nullptr; -// } - -// Native_Error* Basic_Reverse_Array (double* input, int input_length) { -// for (int i = 0; i < input_length / 2; i++) { -// // Swap the ith and (input_length - i - 1)th elements -// double temp = input[i]; -// input[i] = input[input_length - i - 1]; -// input[input_length - i - 1] = temp; -// } - -// return nullptr; -// } - -// #TODO: This should be for NDArray or 2DArray. idk. -Native_Error* Basic_2DArrayInvertMemoryOrder (ArrayView input, s64 first_dimension, s64 second_dimension, ArrayView output) { - Array_Check(input); - Array_Check(output); - if (output.count < input.count) { return New_Error("`input.count` should not exceed `output.count`!"); } - Assert(first_dimension * second_dimension == input.count); - Assert(input.count == output.count); - - ForUpTo(i, first_dimension) { - ForUpTo(j, second_dimension) { - output[j + second_dimension * i] = input[i + first_dimension * j]; - } - } - - return nullptr; -} - -bool sort_doubles_comparator(double a, double b) { - if (isnan(a)) return false; // NaN values are considered greater - if (isnan(b)) return true; // Non-NaN values are considered smaller - return a < b; // Normal comparison for non-NaN values -} - -int qsort_doubles_comparator_nonnan(const void* a, const void* b) { - double val1 = (*(const double*)a); - double val2 = (*(const double*)b); - - if (val1 < val2) return -1; - if (val1 > val2) return 1; - - return 0; -} - -int qsort_doubles_comparator(const void* a, const void* b) { - double val1 = (*(const double*)a); - double val2 = (*(const double*)b); - if (isnan(val1)) return 1; // NaN values are considered greater - if (isnan(val2)) return -1; // Non-NaN values are considered smaller - - if (val1 < val2) return -1; - if (val1 > val2) return 1; - - return 0; -} - -Native_Error* Basic_CalculatePercentileNoSort (ArrayView input, f64 percentile, f64* percentile_value_out) { - Array_Check(input); - Null_Pointer_Check(percentile_value_out); - - Assert(percentile >= 0.0 && percentile <= 1.0); - - s64 non_nan_count = input.count; - - auto r = percentile * non_nan_count; - auto k = floor(r + 0.5); - - auto kp1 = k + 1; - - // Ratio between the K and K+1 rows: - r = r - k; - - // Find indices that are out of the range 1 to n and cap them: - if (k < 1 || isnan(k)) { - k = 1; - } - - // kp1 = min( kp1, n ); - if (non_nan_count < kp1) { kp1 = (f64)non_nan_count; } - - // Use simple linear interpolation for the valid percentages: - // y = (0.5+r).*x(kp1,:)+(0.5-r).*x(k,:); // yuck. - s64 kp1_i = static_cast(kp1); - s64 k_i = static_cast(k); - - f64 y_first_part = (0.5 + r) * input[kp1_i - 1]; - f64 y_second_part = (0.5 - r) * input[k_i - 1]; - auto y = y_first_part + y_second_part; - - // Make sure that values we hit exactly are copied rather than interpolated: - if (r == -0.5) { - (*percentile_value_out) = input[k_i - 1]; - return nullptr; - } - - // Make sure that identical values are copied rather than interpolated: - if (input[k_i-1] == input[kp1_i-1]) { - (*percentile_value_out) = input[k_i - 1]; - return nullptr; - } - - (*percentile_value_out) = y; - - return nullptr; -} - -Native_Error* Basic_Replace_Outliers2 (ArrayView input, f64 outlier_threshold) { - Array_Check(input); - Assert(outlier_threshold > 0); - - auto input_copy = array_copy(input); - - qsort(input_copy.data, input_copy.count, sizeof(f64), qsort_doubles_comparator_nonnan); - f64 Q1 = 0.0; - f64 Q3 = 0.0; - - Assert(Basic_CalculatePercentileNoSort(input_copy, 0.25, &Q1) == nullptr); - Assert(Basic_CalculatePercentileNoSort(input_copy, 0.75, &Q3) == nullptr); - - f64 IQR = Q3 - Q1; - f64 iqr_outlier_threshold = IQR * outlier_threshold; - - // Identify points below Q1 - outlier_threshold, and above Q3 + outlier_threshold - auto low_threshold = Q1 - iqr_outlier_threshold; - auto high_threshold = Q3 + iqr_outlier_threshold; - - ForArrayStartingAt(i, input, 1) { - if (input[i] < low_threshold || input[i] > high_threshold) { - input[i] = input[i-1]; - } - } - - array_free(input_copy); - - return nullptr; -} - -Native_Error* Basic_Replace_Values_Beyond_Threshold2 (ArrayView input, f64 low_threshold, f64 high_threshold, f64 replacement_value) { - Array_Check(input); - - ForArray(i, input) { - if (input[i] < low_threshold || input[i] > high_threshold) { - input[i] = replacement_value; - } - } - - return nullptr; -} - - -/* // #TODO: Replace with version that doesn't use Eigen -Native_Error* Basic_Roots_To_Polynomials2 (ArrayView roots, ArrayView polynomials) { - Array_Check(roots); - - s64 root_count = roots.count; - - if (root_count == 0) { return New_Error("`roots.count` is zero!"); } - if (polynomials.count < root_count + 1) { - return New_Error("`polynomials.count` should be roots.count + 1!"); - } - - // For real roots - Eigen::VectorXd roots_vec = Eigen::Map(roots.data, root_count); - // c = [1 zeros(1,n,class(x))]; - Eigen::VectorXd c = Eigen::VectorXd::Zero(root_count + 1); - c[0] = 1.0; - // for j = 1:n - // c[1] = c[1] - roots_vec[0] * c[0]; // Extract first index - ForArray(i, roots) { - // c(2:(j+1)) = c(2:(j+1)) - e(j).*c(1:j); - Eigen::VectorXd val_temp = c.segment(1, i + 1) - roots_vec[i] * c.segment(0, i + 1); - c.segment(1, i + 1) = val_temp; - } - // The result should be real if the roots are complex conjugates. - memcpy(polynomials.data, c.data(), (root_count + 1) * sizeof(f64)); - - return nullptr; -} -*/ - -Complex exponential (Complex cx) { - f64 e = std::exp(cx.real); - return Complex(e * std::cos(cx.imag), e * std::sin(cx.imag)); -} - -Complex conjugate (Complex cx) { - return Complex(cx.real, -cx.imag); -} - -f64 fabs(Complex cx) { - return sqrt(cx.real * cx.real + cx.imag * cx.imag); -} \ No newline at end of file diff --git a/lib/Base/Basic.h b/lib/Base/Basic.h deleted file mode 100644 index 3cf6f88..0000000 --- a/lib/Base/Basic.h +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif -#ifdef __cplusplus -} -#endif - -int qsort_doubles_comparator_nonnan(const void* a, const void* b); -int qsort_doubles_comparator(const void* a, const void* b); - -// @brief Calculates difference and approximate derivative for 1-dimensional data -// Caller needs to supply memory for output and understand that output_length = input_length - 1 -PROTOTYPING_API Native_Error* Basic_Difference2 (ArrayView input, ArrayView& output); - -PROTOTYPING_API Native_Error* Basic_Mean2 (ArrayView input, f64* mean); - -PROTOTYPING_API Native_Error* Basic_QuickSortInPlace (ArrayView input); - -PROTOTYPING_API Native_Error* Basic_Median2 (ArrayView unsorted_input, f64* median); - -PROTOTYPING_API Native_Error* Basic_RescaleInPlace (ArrayView input, double min, double max); - -PROTOTYPING_API Native_Error* Basic_Min2 (ArrayView input, f64* min_out); - -PROTOTYPING_API Native_Error* Basic_Max2 (ArrayView input, f64* max_out); - -double Basic_Max (double input1, double input2); - -bool Basic_Is_Positive_Real (f32 input); -bool Basic_Is_Positive_Real (f64 input); - -PROTOTYPING_API Native_Error* Basic_Standard_Deviation2 (ArrayView input, f64* stddev); - -PROTOTYPING_API Native_Error* Basic_Variance2 (ArrayView input, f64* variance); - -PROTOTYPING_API Native_Error* Basic_Root_Mean_Squared2 (ArrayView input, f64* rms); - -// Sorts an array from largest to smallest, returning the indices of the sorted array -PROTOTYPING_API Native_Error* Basic_IndexSort2 (ArrayView input, ArrayView output); - -PROTOTYPING_API Native_Error* Basic_Count_Non_Nan2 (ArrayView input, s64* non_nan_count); - -PROTOTYPING_API Native_Error* Basic_Calculate_Percentile_New (ArrayView input, f64 percentile, f64* percentile_value_out); - -// Does not include sort, because sorting is slow, and we may need to call this multiple -// times with the same sorted input. -PROTOTYPING_API Native_Error* Basic_CalculatePercentileNoSort (ArrayView input, f64 percentile, f64* percentile_value_out); - -PROTOTYPING_API Native_Error* Basic_ReverseArrayInPlace (ArrayView input); -// Native_Error* Basic_Reverse_Array (double* input, int input_length); -// Native_Error* Basic_Reverse_Array (int* input, int input_length); - -// Switches from row-order to column-order or vice-versa. #NOTE: you must know what the order -// and dimensions of the data are to begin with!! -PROTOTYPING_API Native_Error* Basic_2DArrayInvertMemoryOrder (ArrayView input, s64 first_dimension, s64 second_dimension, ArrayView output); - -// In-place replacement of outliers (using interquartile method, with threshold of 1.5) with nearest values. -PROTOTYPING_API Native_Error* Basic_Replace_Outliers2 (ArrayView input, f64 outlier_threshold=1.5); - -PROTOTYPING_API Native_Error* Basic_Replace_Values_Beyond_Threshold2 (ArrayView input, f64 low_threshold, f64 high_threshold, f64 replacement_value); - -PROTOTYPING_API Native_Error* Basic_Roots_To_Polynomials2 (ArrayView roots, ArrayView polynomials); - -// #TODO: Basic_Find (returns indices of non-zero elements). -// Need to make this generic, maybe using templates? -// PROTOTYPING_API ArrayView Basic_Find(ArrayView x, void* condition); - -// Add parameters for peak prominence, height, etc. -// PROTOTYPING_API Native_Error* Basic_Find_Peaks (double* input, int input_length, int* peak_indices, int* peak_count); - -struct Complex { - f64 real; f64 imag; - - Complex() { real = 0; imag = 0; } - Complex(f64 _real) { real = _real; imag = 0; } - Complex(f64 _real, f64 _imag) { real = _real; imag = _imag; } - - Complex operator+(const Complex& other) const { - return Complex(real + other.real, imag + other.imag); - } - - Complex operator-(const Complex& other) const { - return Complex(real - other.real, imag - other.imag); - } - - Complex operator*(const Complex& other) const { - return Complex( - real * other.real - imag * other.imag, - real * other.imag + imag * other.real - ); - } - - Complex operator/(const Complex& other) const { - f64 denom = other.real * other.real + other.imag * other.imag; - return Complex( - (real * other.real + imag * other.imag) / denom, - (imag * other.real - real * other.imag) / denom - ); - } - - Complex& operator+=(const Complex& other) { - real += other.real; - imag += other.imag; - return *this; - } - - Complex& operator-=(const Complex& other) { - real -= other.real; - imag -= other.imag; - return *this; - } - - Complex& operator*=(const Complex& other) { - f64 r = real * other.real - imag * other.imag; - f64 i = real * other.imag + imag * other.real; - real = r; - imag = i; - return *this; - } - - Complex& operator/=(const Complex& other) { - f64 denom = other.real * other.real + other.imag * other.imag; - f64 r = (real * other.real + imag * other.imag) / denom; - f64 i = (imag * other.real - real * other.imag) / denom; - real = r; - imag = i; - return *this; - } - - bool operator==(const Complex& other) const { - return real == other.real && imag == other.imag; - } - - bool operator!=(const Complex& other) const { - return !(*this == other); - } -}; - -struct Complex32 { f32 real; f32 imag; }; - -Complex exponential (Complex cx); -Complex conjugate (Complex cx); -f64 fabs (Complex cx); diff --git a/lib/Base/ErrorCodes.cpp b/lib/Base/ErrorCodes.cpp deleted file mode 100644 index d128eeb..0000000 --- a/lib/Base/ErrorCodes.cpp +++ /dev/null @@ -1,166 +0,0 @@ -enum ErrorSeverity: s32 { - SEVERITY_WARNING = 0, - SEVERITY_NON_FATAL = 1, - SEVERITY_FATAL = 2 -}; - -// typedef struct string Native_Error; -// Note: Native_Error should down-cast to a string. -struct Native_Error { - s64 count; - u8* data; - ErrorSeverity severity = SEVERITY_WARNING; -}; - -#define Null_Pointer_Check(arg) \ - if (arg == nullptr) { \ - return New_Fatal_Error_Internal("%s:%d\n[%s] Error: %s is a null pointer.", __FILE__, __LINE__, __FUNCTION__, Stringify(arg)); \ - } - -#define Array_Check(arg) \ - if (!is_valid(arg)) { \ - return New_Fatal_Error_Internal("%s:%d\n[%s] Error: %s is not a valid array.", __FILE__, __LINE__, __FUNCTION__, Stringify(arg)); \ - } - -#define String_Check(arg) \ - if (!Is_Valid(arg)) { return New_Fatal_Error_Internal("%s:%d\n[%s] Error: %s is not a valid string.", __FILE__, __LINE__, __FUNCTION__, Stringify(arg)); } - -#define Error_Check(error) \ - if (error != nullptr) { \ - return error; \ - } - -// An error from which the program cannot continue (e.g. a segmentation fault) -#define New_Fatal_Error(message) \ - New_Fatal_Error_Internal("%s:%d\n[%s] Error: %s.", __FILE__, __LINE__, __FUNCTION__, message) - -#define New_Error(message) \ - New_Error_Internal("%s:%d\n[%s] Error: %s.", __FILE__, __LINE__, __FUNCTION__, message) - -#define New_Warning(message) \ - New_Warning_Internal("%s:%d\n[%s] Warning: %s.", __FILE__, __LINE__, __FUNCTION__, message) - -Native_Error* New_Fatal_Error_Internal(char* raw_message, ...); - -Native_Error* New_Error_Internal(char* raw_message, ...); - -Native_Error* New_Warning_Internal(char* raw_message, ...); - -Native_Error* Native_Error_Callstack(Native_Error* new_error, Native_Error* old_error, ErrorSeverity severity); - -PROTOTYPING_API C_API Native_Error* Cleanup_Error(Native_Error* error); - -PROTOTYPING_API C_API Native_Error* Native_Error_Test(); - - -#include "General_Purpose_Allocator.h" -#include // vsnprintf, printf -#include // va_list... - -#define BREAK_ON_WARNINGS 0 -#define BREAK_ON_ERRORS 0 -#define BREAK_ON_FATAL_ERROR BUILD_DEBUG -#define ALWAYS_PRINT_ERROR_MESSAGES BUILD_DEBUG - -Native_Error* Create_New_Native_Error_Internal(char* format, va_list args) { - constexpr s64 ERROR_BUFFER_COUNT = 512; - - // push_allocator(GPAllocator()); - - auto error = New(false); - error->data = (u8*)GPAllocator_New(ERROR_BUFFER_COUNT); - - // You MUST copy the va_list before using it more than once - va_list args_copy; - va_copy(args_copy, args); - error->count = (s64)vsnprintf((char*)error->data, (size_t)ERROR_BUFFER_COUNT, format, args_copy); - va_end(args_copy); - - return error; -} - -Native_Error* New_Fatal_Error_Internal(char* format, ...) { - va_list args; - va_start(args, format); - auto error = Create_New_Native_Error_Internal(format, args); - va_end(args); - - error->severity = SEVERITY_FATAL; -#if BUILD_DEBUG && ALWAYS_PRINT_ERROR_MESSAGES - printf("[FATAL ERROR] %.*s\n", (s32)error->count, (char*)error->data); -#endif -#if BREAK_ON_FATAL_ERROR - debug_break(); -#endif - - return error; -} - -Native_Error* Native_Error_Callstack(Native_Error* new_error, Native_Error* old_error, ErrorSeverity severity) { - // push_allocator(GPAllocator()); - - auto error_message = format_string("%s\n > %s", new_error->data, old_error->data).data; - - Cleanup_Error(new_error); - Cleanup_Error(old_error); - - Native_Error* error_merged = New(false); - error_merged->data = (u8*)error_message; - error_merged->count = strlen((char*)error_merged->data); - error_merged->severity = severity; - - return error_merged; -} - -Native_Error* Native_Error_Test() { - // This is quite verbose, but w/e - auto old_error = New_Error("Original error..."); - auto new_message = format_string("Failed to start stream. Error Code: %d", -1).data; - auto new_error = New_Error(new_message); - GPAllocator_Delete(new_message); - - return Native_Error_Callstack(new_error, old_error, SEVERITY_NON_FATAL); -} - -Native_Error* New_Error_Internal(char* format, ...) { - va_list args; - va_start(args, format); - auto error = Create_New_Native_Error_Internal(format, args); - va_end(args); - - error->severity = SEVERITY_NON_FATAL; -#if BUILD_DEBUG && ALWAYS_PRINT_ERROR_MESSAGES - printf("[ERROR (NON-FATAL)] %.*s\n", (s32)error->count, (char*)error->data); -#endif -#if BREAK_ON_ERRORS - debug_break(); -#endif - - return error; -} - -Native_Error* New_Warning_Internal(char* format, ...) { - va_list args; - va_start(args, format); - auto error = Create_New_Native_Error_Internal(format, args); - va_end(args); - - error->severity = SEVERITY_WARNING; -#if BUILD_DEBUG && ALWAYS_PRINT_ERROR_MESSAGES - printf("[WARNING] %.*s\n", (s32)error->count, (char*)error->data); -#endif -#if BREAK_ON_WARNINGS - debug_break(); -#endif - - return error; -} - -Native_Error* Cleanup_Error(Native_Error* error) { - if (error == nullptr) return nullptr; - - GPAllocator_Delete(error->data); - GPAllocator_Delete(error); - - return nullptr; -} \ No newline at end of file diff --git a/lib/Base/ErrorType.cpp b/lib/Base/ErrorType.cpp new file mode 100644 index 0000000..dbb4f2c --- /dev/null +++ b/lib/Base/ErrorType.cpp @@ -0,0 +1,30 @@ +// #NOTE: To keep things simple, all allocations for Error should be via GPAllocator. +// We really allocate two things: the Error struct and the error string copy. + +enum class ErrorClass: s32 { + NONE = 0, // should not be used, just to avoid a default value being assigned. + WARNING = 1, + ERROR = 2, + FATAL = 3 +}; + +// #downcasts to string +struct Error { + s64 count; + u8* data; + ErrorClass severity = ErrorClass::NONE; + Error* previous_error; // if we're passing errors up the callstack. + + Arena* arena; +}; + +string to_string (Error error) { + return { error.count, error.data }; +} + +// Will need to use __FILE__ and __LINE__ macros + +// Error* new_error (string error_message, ErrorClass severity, Error* previous_error=nullptr); +// Error* append_error (Error* old_error, Error* new_error); +// void context_report_error (Error* error); +// void cleanup_error (Error* error); diff --git a/lib/Base/Logger.h b/lib/Base/Logger.h new file mode 100644 index 0000000..8f41526 --- /dev/null +++ b/lib/Base/Logger.h @@ -0,0 +1,16 @@ +// #TODO #Logger module +// [ ] Add colored prints (See: Print_Color.jai) + +// See Logger.jai in our jiim-dev-gui project for how to do fancy colored text. +enum class Log_Level : s32 { + TODO = -2, + Trace = -1; + None = 0, + Info = 1, + Warning = 2, + Error = 3, + Fatal_Error = 4, +}; + +// log_function pointer +typedef void (*Logger_Proc)(string log_message, ...); \ No newline at end of file diff --git a/lib/Base/String.cpp b/lib/Base/String.cpp index 42a3ad2..1d77e5b 100644 --- a/lib/Base/String.cpp +++ b/lib/Base/String.cpp @@ -1,127 +1,89 @@ -// #TODO: Integrate Allocator / context.allocator +#include "String.h" - -// Need to sort out how formatted strings and string builders are allocated -// Maybe just use context.allocator? -// What about temp strings? use context.temp? - -struct string { - s64 count; - u8* data; - // Construct from a string literal or C-string - string () { // default constructor - count = 0; - data = nullptr; - } - - string (char* cstr) { - count = strlen(cstr); - data = (u8*)cstr; - } - - string (s64 _count, char* str) { count = _count; data = (u8*)str; } - string (s64 _count, u8* str) { count = _count; data = str; } -}; - -// ~ API ~ #TODO -string copy_string (string str); -bool strings_match(string first_string, string second_string); - -// Unicode stuff -string wide_to_utf8 (u16* source, s32 length); - -// string string_view(string n_string, int start_index, int view_count); -// string copy_string(char* c_string); -// void free(string& n_string); - -bool is_valid(string n_string); -bool is_c_string(string n_string); - -char* to_c_string(string n_string); - -string format_string(char* format, ...); -string string_from_literal(char* literal); - -#include "General_Purpose_Allocator.h" -#include // vsnprintf -#include // va_list, ... - -bool is_c_string(string n_string) { - return (n_string.data && n_string.data[n_string.count] == '\0'); +// #TODO #string module +// [ ] I'm debating if string type should automatically null-terminate. +// I personally do not like it, and think we should temp-copy c-strings as they're needed. +bool is_valid (string s) { + return (s.data != nullptr && s.count > 0); } -bool is_valid(string n_string) { - return (n_string.data != nullptr && n_string.count > 0); +bool is_c_string (string s) { + return (s.data && s.data[s.count] == '\0'); } -string copy_string (string str) { - string new_str = {}; +u8* to_c_string (string s) { + u8* result = (u8*)internal_alloc(s.count + 1); - new_str.count = str.count; - new_str.data = (u8*)internal_alloc(str.count); + memcpy(result, s.data, s.count); + result[s.count] = '\0'; - memcpy(new_str.data, str.data, str.count); - - return new_str; + return result; } -string format_string (char* format, ...) { - constexpr s64 BUFFER_SIZE = 4096; +string copy_string (string s) { + Assert(s.count > 0); + if (s.count <= 0) + return ""; + string str = {}; - string str = {0}; + str.count = s.count; + str.data = (u8*)internal_alloc(s.count + 1); - str.data = NewArray(BUFFER_SIZE); + memcpy(str.data, s.data, s.count); + + str.data[str.count] = '\0'; // null-terminate for backwards compatibility? - va_list args; - va_start(args, format); - // Note that this *is* null-terminated for compatibility. - str.count = (s64)vsnprintf((char*)str.data, (size_t)BUFFER_SIZE, format, args); - va_end(args); - return str; } -string copy_string(char* c_string) { +string copy_string (char* c_string) { string str = {0}; s64 string_length = strlen(c_string); + if (string_length == 0) + return ""; str.data = NewArray(string_length + 1); memcpy(str.data, c_string, string_length); str.count = string_length; + str.data[str.count] = '\0'; // null-terminate for backwards compatibility? + return str; } -bool strings_match(string first_string, string second_string) { - if (first_string.count != second_string.count) { - return false; +string to_string (ArrayView str) { + return {str.count, str.data}; +} + +void free (string& s) { + internal_free(s.data); + + s.data = nullptr; + s.count = 0; +} + +force_inline string string_view (string s, s64 start_index, s64 view_count) { + Assert(view_count >= 0); Assert(start_index >= 0); + if (view_count < 0 || start_index < 0 || start_index >= s.count) return ""; + + s64 new_count = view_count; + if (start_index + view_count > s.count) { + new_count = s.count - start_index; } - - for (s64 i = 0; i < first_string.count; i += 1) { - if (first_string.data[i] != second_string.data[i]) { - return false; - } - } - - return true; + + return { new_count, s.data + start_index }; } -string string_from_literal(char* literal) { - string new_string; - new_string.count = strlen(literal); - new_string.data = (u8*) literal; - - return new_string; +string copy_string_view (string s, s64 start_index, s64 view_count) { + // maybe redundant... + return copy_string(string_view(s, start_index, view_count)); } -void free(string& n_string) { - internal_free(n_string.data); - - n_string.data = nullptr; - n_string.count = 0; +bool strings_match (string first_string, string second_string) { + return (first_string == second_string); } -// Unicode nonsense +// #Unicode string wide_to_utf8 (u16* source, s32 length) { if (length == 0) return { }; @@ -149,3 +111,93 @@ string wide_to_utf8 (u16* source, s32 length) { return utf8_string; } +string format_string (char* format, ...) { + constexpr s64 BUFFER_SIZE = 4096; + + string str = {0}; + + str.data = NewArray(BUFFER_SIZE); + + va_list args; + va_start(args, format); + // Note that this *is* null-terminated for compatibility. + str.count = (s64)vsnprintf((char*)str.data, (size_t)BUFFER_SIZE, format, args); + va_end(args); + + return str; +} + +force_inline String_Builder* new_string_builder (Arena_Reserve new_reserve) { + return arena_array_new(1, new_reserve); +} + +force_inline void append (String_Builder* sb, string s) { + array_add(*sb, ArrayView(s.count, s.data)); +} + +void append (String_Builder* sb, ArrayView strings) { + s64 combined_length = 0; + for (s64 i = 0; i < strings.count; i += 1) { + combined_length += strings[i].count; + } + + s64 final_length = sb->count + combined_length; + + if (sb->allocated < final_length) { + array_reserve(*sb, final_length); + } + + for (s64 i = 0; i < strings.count; i += 1) { + string s = strings[i]; + array_add(*sb, ArrayView(s.count, s.data)); + } +} + +force_inline void append_no_add (String_Builder* sb, string s) { + array_add(*sb, ArrayView(s.count, s.data)); + sb->count -= s.count; +} + +// Unfortunately this follows the printf format, which is annoying. +// I'd rather have something like fmt:: +void print_to_builder (String_Builder* sb, string format, ...) { + s64 expected_final_count = sb->count + format.count + 4096; + + if (sb->allocated < expected_final_count) { + array_reserve(*sb, expected_final_count); + } + + s64 buffer_size = sb->allocated - sb->count; // available space + u8* current_point = &sb->data[sb->count]; + + va_list args; + va_start(args, format); + s64 print_count = (s64)vsnprintf((char*)current_point, (size_t)buffer_size, (char*)format.data, args); + va_end(args); + + sb->count += print_count; +} + +string string_view (String_Builder* sb) { + // should probably ensure final byte is null terminated... + append_no_add(sb, "\0"); // doesn't increment sb.count + return to_string(to_view(*sb)); +} + +// for when we want to keep the string builder around and recycle the memory. +internal force_inline void reset (String_Builder* sb) { + poison_range(*sb, 0, sb->count); + reset_keeping_memory(*sb); +} + +force_inline string builder_to_string (String_Builder* sb) { + string final_string = copy_string(to_string(to_view(*sb))); + + free(sb); + + return final_string; +} + +internal force_inline void free (String_Builder* sb) { + arena_array_free(*sb); +} diff --git a/lib/Base/String.h b/lib/Base/String.h new file mode 100644 index 0000000..3ef0fac --- /dev/null +++ b/lib/Base/String.h @@ -0,0 +1,100 @@ +// #TODO: #strings: + // [ ] Always null-terminate strings! + // [ ] How do I accept variadic arguments of any type to my print function? + // [ ] Need to sort out how formatted strings and string builders are allocated + // [ ] Separate functions for temp alloc (tprint??) + // [ ] API needs to be completely overhauled + // [ ] I should also put path manipulation here or in a separate file. + +struct string { + s64 count; + u8* data; + // Construct from a string literal or C-string + string () { // default constructor + count = 0; + data = nullptr; + } + + string (char* cstr) { + count = strlen(cstr); + data = (u8*)cstr; + } + + string (s64 _count, char* str) { count = _count; data = (u8*)str; } + string (s64 _count, u8* str) { count = _count; data = str; } + + bool operator==(const string& other) const { + string first_string = *this; + string second_string = other; + // return strings_match(*this, other); + if (first_string.count != second_string.count) { + return false; + } + + for (s64 i = 0; i < first_string.count; i += 1) { + if (first_string.data[i] != second_string.data[i]) { + return false; + } + } + + return true; + } +}; + +struct wstring { + s64 count; + u16* data; + + wstring () { // default constructor + count = 0; + data = nullptr; + } +}; + +// ~Keep these API +bool is_valid (string s); +bool is_c_string (string s); +u8* to_c_string (string s); // #allocates +string copy_string (string s); // #allocates, returned string is #null-terminated. +string copy_string (char* c_string); // #allocates, returned string is #null-terminated. +string to_string (ArrayView str); +void free(string& s); + +// String manipulation & comparison +force_inline string string_view (string s, s64 start_index, s64 view_count); +string copy_string_view (string s, s64 start_index, s64 view_count); +bool strings_match (string first_string, string second_string); + +// #Unicode +string wide_to_utf8 (u16* source, s32 length); +// wstring utf8_to_wide (string source); TODO. + +string format_string (char* format, ...); + +// Parsing stuff: +// is_white_space(char: u8) +// advance +// eat_spaces + +// Print stuff +// s64 string_to_int (string v, s32 base = 10, s64* remainder=nullptr); +// + +// #string_builder +// #limitations This won't be as fast as Jon's String_Builder in jai because we're backing it with an +// Arena, which requires a variable number of cycles depending on if our process has +// memory available already. It also has a max capacity depending on what Arena_Reserve we choose. +// That being said, the implementation is much simpler. +typedef ArenaArray String_Builder; // struct String_Builder + +force_inline String_Builder* new_string_builder (Arena_Reserve new_reserve=Arena_Reserve::Size_64K); +force_inline void append (String_Builder* sb, string s); +void append (String_Builder* sb, ArrayView strings); +internal force_inline void append_no_add (String_Builder* sb, string s); // for appending null terminators, does not increment count. +void print_to_builder (String_Builder* sb, string format, ...); +string string_view (String_Builder* sb); +internal force_inline void reset (String_Builder* sb); + +force_inline string builder_to_string (String_Builder* sb); // returns string view +internal force_inline void free (String_Builder* sb); + diff --git a/lib/Base/Thread_Group.cpp b/lib/Base/Thread_Group.cpp index 5489e3e..2795c02 100644 --- a/lib/Base/Thread_Group.cpp +++ b/lib/Base/Thread_Group.cpp @@ -1,4 +1,6 @@ // Thread_Group Internal Procedures +// #NOTE: There is no logging in this implementation! + void init(Work_List* list) { Assert(list != nullptr); @@ -76,8 +78,6 @@ s64 thread_group_run (Thread* thread) { entry->thread_index = thread->index; entry->next = nullptr; - // #TODO(Log) - Thread_Continue_Status should_continue = Thread_Continue_Status::THREAD_CONTINUE; if (group->proc) { should_continue = group->proc(group, thread, entry->work); @@ -103,8 +103,7 @@ s64 thread_group_run (Thread* thread) { for (s64 i = 0; i < info->work_steal_indices.count; i += 1) { entry = get_work(&group->worker_info[i].available); if (entry) { - // #TODO(Log) - break; // for + break; } } } @@ -227,7 +226,7 @@ bool shutdown (Thread_Group* group, s32 timeout_milliseconds = -1) { // Should have a shutdown_and_reset option too (see how I did it in prototyping-main) -void add_work (Thread_Group* group, void* work) { // string logging_name +void add_work (Thread_Group* group, void* work) { Assert(group->worker_info.count > 0); push_allocator(group->allocator); @@ -235,7 +234,6 @@ void add_work (Thread_Group* group, void* work) { // string logging_name // Make a work entry, a linked list node that lets us queue and unqueue Work_Entry* entry = New(); entry->work = work; - // entry->logging_name = ""; entry->issue_time = GetUnixTimestamp(); // Choose which thread will run this work. @@ -251,8 +249,6 @@ void add_work (Thread_Group* group, void* work) { // string logging_name // Add this node to the linked list of available work for that thread: Work_List* list = &group->worker_info[thread_index].available; add_work(list, entry); - - // #TODO: Log if necessary. } ArrayView get_completed_work (Thread_Group* group) { @@ -290,14 +286,13 @@ ArrayView get_completed_work (Thread_Group* group) { if (!completed) continue; - // Reserve the output array. Probably doesn't help much. Note that - // we are maybe adding small numbers of results over a larger number - // of cores. Really if we want to be efficient here, we can build + // #TODO: #Thread_Group #array_reserve - try to do this in two passes: + // Note that we are maybe adding small numbers of results over a larger + // number of cores. Really, if we want to be efficient here, we can build // a larger linked list out of the mini-lists we gather, and accumulate // the counts, then do the reserve all in one batch when we are done // looking at the threads. For simplicity this has not yet been done, // but it may not be much more complicated, actually. - // #TODO(Musa) - do this^ array_reserve(results, results.count + new_count); s64 old_count = results.count; @@ -305,8 +300,6 @@ ArrayView get_completed_work (Thread_Group* group) { array_add(results, completed->work); Work_Entry* next = completed->next; - // #TODO(Log) - internal_free(completed); completed = next; } @@ -314,6 +307,6 @@ ArrayView get_completed_work (Thread_Group* group) { Assert(results.count == old_count + new_count); } - return {}; + return ArrayView(results); } diff --git a/lib/Base/Threads.cpp b/lib/Base/Threads.cpp index f290cf3..3fff079 100644 --- a/lib/Base/Threads.cpp +++ b/lib/Base/Threads.cpp @@ -35,7 +35,6 @@ }; #endif -struct Thread; // really hacky forward declares. struct Work_Entry; struct Worker_Info; @@ -108,5 +107,4 @@ struct Thread_Group { bool initialized = false; bool started = false; bool should_exit = false; - // bool enable_logging; }; diff --git a/lib/OS/OS_Win32.cpp b/lib/OS/OS_Win32.cpp index 6586f18..42d91d6 100644 --- a/lib/OS/OS_Win32.cpp +++ b/lib/OS/OS_Win32.cpp @@ -1,12 +1,17 @@ +// #TODO: #OS_Win32 +// [ ] #Thread cleanup: in `thread_deinit` is there any requirement to cleanup child threads? +// [ ] #Exception handling code in `Win32_Exception_Filter` +// [ ] #cpuid - enumerate CPUs and Thread count (current implementation doesn't work) + #if OS_WINDOWS constexpr s64 FILETIME_TO_UNIX = 116444736000000000i64; -f64 GetUnixTimestamp() { +f64 GetUnixTimestamp () { FILETIME fileTime; GetSystemTimePreciseAsFileTime(&fileTime); s64 ticks = ((s64)fileTime.dwHighDateTime << (s64)32) | (s64)fileTime.dwLowDateTime; return (ticks - FILETIME_TO_UNIX) / (10.0 * 1000.0 * 1000.0); } -s64 GetUnixTimestampNanoseconds() { +s64 GetUnixTimestampNanoseconds () { FILETIME fileTime; GetSystemTimePreciseAsFileTime(&fileTime); @@ -54,7 +59,7 @@ internal b32 win32_g_is_quiet = 0; // No console output internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs) { if (win32_g_is_quiet) { ExitProcess(1); } - static volatile LONG first = 0; + local_persist volatile LONG first = 0; if(InterlockedCompareExchange(&first, 1, 0) != 0) { // prevent failures in other threads to popup same message box // this handler just shows first thread that crashes @@ -62,7 +67,7 @@ internal LONG WINAPI Win32_Exception_Filter (EXCEPTION_POINTERS* exception_ptrs) for (;;) Sleep(1000); } - // #TODO: Exception handling code. + // #Exception handling code (TODO) return 0; } @@ -98,7 +103,6 @@ internal void Win32_Entry_Point (int argc, WCHAR **argv) { push_arena(get_thread_context()->arena); - // #TODO: Need to write Win32 abstraction layer first. { OS_System_Info* info = &os_state_w32.system_info; info->logical_processor_count = (s32)sysinfo.dwNumberOfProcessors; info->page_size = sysinfo.dwPageSize; @@ -109,6 +113,7 @@ internal void Win32_Entry_Point (int argc, WCHAR **argv) { info->large_pages_allowed = false; info->process_id = GetCurrentProcessId(); } + // #cpuid /*{ OS_System_Info* info = &os_state_w32.system_info; // [ ] Extract input args u32 length; @@ -148,7 +153,7 @@ internal void Win32_Entry_Point (int argc, WCHAR **argv) { info->physical_core_count = (s32)all_cpus_count; info->primary_core_count = (s32)performance_core_count; } - // info->secondary_core_count = #TODO; + // info->secondary_core_count = ; */ { OS_System_Info* info = &os_state_w32.system_info; u8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0}; @@ -224,7 +229,6 @@ internal void thread_deinit (Thread* thread) { } thread->os_thread.windows_thread = nullptr; - // #TODO: Thread cleanup: arena_delete(thread->context->temp); arena_delete(thread->context->arena); } @@ -253,7 +257,7 @@ internal void lock (Mutex* mutex) { internal void unlock (Mutex* mutex) { LeaveCriticalSection(&mutex->csection); } -internal void semaphore_init (Semaphore* sem, s32 initial_value = 0) { +internal void semaphore_init (Semaphore* sem, s32 initial_value) { Assert(initial_value >= 0); sem->event = CreateSemaphoreW(nullptr, initial_value, 0x7fffffff, nullptr); } @@ -264,13 +268,7 @@ internal void signal (Semaphore* sem) { ReleaseSemaphore(sem->event, 1, nullptr); } -enum class Wait_For_Result : s32 { - SUCCESS = 0, - TIMEOUT = 1, - ERROR = 2 // can't use ERROR because of Windows.h *sigh* -}; - -internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds = -1) { +internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds) { DWORD res = 0; if (milliseconds < 0) { res = WaitForSingleObject(sem->event, INFINITE); @@ -292,7 +290,7 @@ internal void condition_variable_init (Condition_Variable* cv) { internal void condition_variable_destroy (Condition_Variable* cv) { // No action required. } -internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms = -1) { +internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms) { SleepConditionVariableCS(&cv->condition_variable, &mutex->csection, (DWORD)wait_time_ms); } internal void wake (Condition_Variable* cv) { @@ -301,3 +299,18 @@ internal void wake (Condition_Variable* cv) { internal void wake_all (Condition_Variable* cv) { WakeAllConditionVariable(&cv->condition_variable); } + +// #window_creation +Window_Type create_window (string new_window_name) { + return 0; +} + +// #TODO: #window_creation +// [ ] resize_window +// [ ] position_window +// [ ] toggle_fullscreen +// [ ] get_dimensions + +// #TODO: #window_interaction (mouse/keyboard) +// [ ] get_mouse_pointer_position +// [ ] ... What APIs do I need for Keyboard \ No newline at end of file diff --git a/lib/OS/OS_Win32.h b/lib/OS/OS_Win32.h new file mode 100644 index 0000000..3cfaca0 --- /dev/null +++ b/lib/OS/OS_Win32.h @@ -0,0 +1,32 @@ +f64 GetUnixTimestamp (); +s64 GetUnixTimestampNanoseconds (); + +struct Condition_Variable; +struct Semaphore; +struct Mutex; +struct OS_Thread; + +enum class Wait_For_Result : s32 { + SUCCESS = 0, + TIMEOUT = 1, + ERROR = 2 // can't use ERROR because of Windows.h *sigh* +}; + +internal void mutex_init (Mutex* mutex); +internal void mutex_destroy (Mutex* mutex); +internal void lock (Mutex* mutex); +internal void unlock (Mutex* mutex); + +internal void semaphore_init (Semaphore* sem, s32 initial_value = 0); +internal void semaphore_destroy (Semaphore* sem); +internal void signal (Semaphore* sem); +internal Wait_For_Result wait_for (Semaphore* sem, s32 milliseconds = -1); + +internal void condition_variable_init (Condition_Variable* cv); +internal void condition_variable_destroy (Condition_Variable* cv); +internal void wait (Condition_Variable* cv, Mutex* mutex, s32 wait_time_ms = -1); +internal void wake (Condition_Variable* cv); +internal void wake_all (Condition_Variable* cv); + +// #window_creation +typedef HWND Window_Type; diff --git a/lib_main.cpp b/lib_main.cpp index d72e565..0521533 100644 --- a/lib_main.cpp +++ b/lib_main.cpp @@ -2,29 +2,34 @@ // translation unit. // lib_main.cpp can be treated as a single-header library and added to a project like that. -// #TODO: This is quite disorganized. There must be a better way to do this by moving the +// #TODO: #Library This is quite disorganized. There must be a better way to do this by moving the // typedefs and procedures that require forward declaration to the top with a metaprogram. +// [ ] Linux / MacOS Ports #include "lib/meta_generated.h" #include "lib/Base/Base.h" #include "lib/Base/Allocator.h" #include "lib/Base/Array.h" -#include "lib/Base/String.cpp" #include "lib/Base/General_Purpose_Allocator.h" + +#if OS_WINDOWS +# include "lib/OS/OS_Win32.h" +#endif + #include "lib/Base/Arena.h" #include "lib/Base/Arena_Array.h" +#include "lib/Base/String.cpp" +#include "lib/Base/ErrorType.cpp" #include "lib/Base/Arena_Table.cpp" #include "lib/Base/Base_Thread_Context.h" #include "lib/Base/Expandable_Arena.h" -#include "lib/Base/ErrorCodes.cpp" #include "lib/Base/Arena.cpp" #include "lib/Base/Base_Thread_Context.cpp" #include "lib/Base/Expandable_Arena.cpp" #include "lib/Base/Allocator.cpp" #include "lib/Base/General_Purpose_Allocator.cpp" -#include "lib/Base/Basic.cpp" // OS-Abstraction Layer #include "lib/Base/Threads.cpp" @@ -38,7 +43,7 @@ // #include "imgui-docking.cpp" // #if OS_LINUX.. -// #include "src/OS_Linux.cpp" // #TODO: Future. +// #include "src/OS_Linux.cpp" // #if OS_MACOS.. -// #include "src/OS_MacOS.cpp" // #TODO: Future. +// #include "src/OS_MacOS.cpp" diff --git a/src/Base_Entry_Point.cpp b/src/Base_Entry_Point.cpp index 184fcd1..f095b5a 100644 --- a/src/Base_Entry_Point.cpp +++ b/src/Base_Entry_Point.cpp @@ -20,7 +20,6 @@ internal void Main_Entry_Point (int argc, WCHAR **argv); #include #include - #include "ImGui_Supplementary.cpp" void ImGui_Application () { @@ -80,7 +79,7 @@ void ImGui_Application () { ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); - // #TODO: Load fonts: + // #TODO: #ImGUI - Load fonts: // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). @@ -135,16 +134,30 @@ void ImGui_Application () { ImGui_ImplDX11_NewFrame(); ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); + + // Simple dockspace: + ImGui::DockSpaceOverViewport(); - // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). - if (show_demo_window) { - // ImGui::ShowDemoWindow(&show_demo_window); + { + ImGui::Begin("Hello, world!"); + + if (ImGui::Button("Create New Window")) { + // I think that create_window should take few parameters, and we have other APIs for + // styling, positioning, etc. I just call this and want to get a window. + // auto new_window = create_window(window_name); + } + + if (ImGui::Button("Position recently created Window")) { + + } + + ImGui::End(); } // Rendering ImGui::Render(); - const float clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; + const f32 clear_color_with_alpha[4] = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr); g_pd3dDeviceContext->ClearRenderTargetView(g_mainRenderTargetView, clear_color_with_alpha); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); @@ -158,7 +171,7 @@ void ImGui_Application () { // Present HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync - //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync + // HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); } // while (!done) @@ -172,13 +185,38 @@ void ImGui_Application () { ::UnregisterClassW(wc.lpszClassName, wc.hInstance); } +string string_literal_example = "Hello, I am a string literal."; + internal void Main_Entry_Point (int argc, WCHAR **argv) { temp_reset(); // tip: use auto_reset or auto_release with `get_thread_context()->arena` - ImGui_Application(); - // #TODO + debug_break(); + + // String builder example: + // OK. I can work with this. + /* + auto sb = new_string_builder(Arena_Reserve::Size_64K); + append(sb, "string_literal_example"); + append(sb, " "); + print_to_builder(sb, "There are %d cats in the %s", 64, "house.\n"); + append(sb, " > "); + print_to_builder(sb, "some size_t: %u", (u64)3982739867); + append(sb, "\n"); + + // auto result = string_view(sb); + auto result = builder_to_string(sb); + printf((char*)result.data); + + auto array = array_from_values(6,7,8,9,10,51); + auto view = array_view_from_values(1,2,3,4,5); + */ + + // ImGui_Application(); + + + // #TODO: #Main - `Main_Entry_Point` // [ ] Setup Mouse and Keyboard Inputs // [ ] Launch second thread - // OS_Create_Window(); } +