Remove Basic, ErrorCodes, add String_Builder, and fix some issues with String module

This commit is contained in:
Musa Mahmood 2025-11-25 13:05:14 -05:00
parent 6c3d77d58b
commit 1a2de6e2f8
22 changed files with 522 additions and 1006 deletions

View File

@ -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"

View File

@ -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 <typename T> void zero_struct(T* src) {
memset(src, 0, sizeof(T));
}
template <typename T> void poison_struct(T* src) {
memset(src, 0xCD, sizeof(T));
}
template <typename T> T* copy_struct(T* src) {
T* dst = New<T>(false);
memcpy(dst, src, sizeof(T));

View File

@ -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);

View File

@ -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<T>*), 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 <typename T>
ArenaArray<T>* arena_array_new (s64 preallocate_count, Arena_Reserve reserve_size) {
Arena* arena = next_arena(reserve_size);
@ -37,15 +39,15 @@ ArenaArray<T>* 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<T>(*array);
return array;
}
template <typename T> T* array_start (ArenaArray<T>& array) {
return (array->arena->memory_base + ARRAY_ARENA_START_OFFSET);
return (array.arena->memory_base + ARRAY_ARENA_START_OFFSET);
}
template <typename T> bool is_empty (ArenaArray<T>& array) {
@ -57,11 +59,11 @@ template <typename T> s64 memory_usage (ArenaArray<T>& array) {
return arena_usage_committed_bytes(array.arena);
}
template <typename T> void array_free (ArenaArray<T>& array) {
array.count = 0;
array.allocated = 0;
release_arena(array.arena, delete_extra_pages=true);
template <typename T> void arena_array_free (ArenaArray<T>& array) {
release_arena(array.arena, true);
#if BUILD_DEBUG
poison_struct(&array);
#endif
}
template <typename T> ArrayView<T> array_view (ArenaArray<T> array) {
@ -97,6 +99,19 @@ template <typename T> ArrayView<T> to_view (ArenaArray<T>& array, s64 start_offs
return av;
}
template <typename T> void array_add (ArenaArray<T>& array, ArrayView<T> 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 <typename T> void array_add (ArenaArray<T>& array, T item) {
maybe_grow(array);
array.data[array.count] = item;
@ -149,7 +164,7 @@ void array_arena_realloc (ArenaArray<u8>& 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 <typename T> void init_range (T* ptr, s64 start_offset, s64 end_offset)
}
}
template <typename T> void poison_range (ArenaArray<T>& 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 <typename T> force_inline void array_reset (ArenaArray<T>& array) {
// reset backing array:
arena_reset(array.arena);

View File

@ -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 <mutex> // #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;
// }
}
}

View File

@ -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?
}

View File

@ -53,7 +53,7 @@ template <typename T> bool is_resizable (Array<T>& src) {
}
template <typename T>
bool is_valid(Array<T> src) {
bool is_valid (Array<T> 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<T>& src) {
}
template <typename T>
Array<T> array_copy_zero(const Array<T>& src) {
Array<T> array_copy_zero (const Array<T>& src) {
if (!src.data || src.count == 0) {
return Array<T>(); // Return an empty array
}
@ -79,7 +79,7 @@ Array<T> array_copy_zero(const Array<T>& src) {
}
template <typename T>
Array<T> array_copy(const Array<T>& src) {
Array<T> array_copy (const Array<T>& src) {
if (!src.data || src.count == 0) {
return Array<T>(); // Return an empty array
}
@ -91,12 +91,12 @@ Array<T> array_copy(const Array<T>& src) {
}
template <typename T>
void array_reset_keeping_memory(Array<T>& src) {
void array_reset_keeping_memory (Array<T>& src) {
src.count = 0;
}
template <typename T>
void array_free(Array<T>& src) {
void array_free (Array<T>& src) {
if (!src.data) return;
if (src.allocated == 0) return;
if (src.allocator.proc != nullptr) {
@ -110,7 +110,7 @@ void array_free(Array<T>& src) {
}
template <typename T>
void array_initialize(Array<T>& src, s64 start, s64 end) {
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.
@ -119,7 +119,7 @@ void array_initialize(Array<T>& src, s64 start, s64 end) {
}
template <typename T>
void array_reserve(Array<T>& src, s64 desired_items) {
void array_reserve (Array<T>& src, s64 desired_items) {
if (desired_items <= src.allocated) return;
src.data = nullptr;
@ -137,7 +137,7 @@ void array_reserve(Array<T>& src, s64 desired_items) {
}
template <typename T>
void array_resize(Array<T>& src, s64 new_count, bool initialize=true) {
void array_resize (Array<T>& 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<T>& src, s64 new_count, bool initialize=true) {
}
template <typename T>
force_inline void array_maybe_grow(Array<T>& src) {
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;
@ -159,14 +159,14 @@ force_inline void array_maybe_grow(Array<T>& src) {
}
template <typename T>
T pop(Array<T>& src) {
T pop (Array<T>& src) {
auto result = src[src.count-1];
src.count -= 1;
return result;
}
template <typename T, typename U>
void array_add(Array<T>& src, U new_item) {
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);
@ -178,7 +178,7 @@ void array_add(Array<T>& src, U new_item) {
}
template <typename T>
void array_add(Array<T>& src, T new_item) {
void array_add (Array<T>& src, T new_item) {
auto new_count = src.count + 1;
array_maybe_grow(src);
@ -187,7 +187,7 @@ void array_add(Array<T>& src, T new_item) {
}
template <typename T>
s64 array_find(Array<T>& src, T item) {
s64 array_find (Array<T>& src, T item) {
ForArray(i, src) {
if (src[i] == item) return i;
}
@ -195,7 +195,7 @@ s64 array_find(Array<T>& src, T item) {
}
template <typename T>
void array_ordered_remove_by_index(Array<T>& src, s64 index) {
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) {
@ -206,13 +206,13 @@ void array_ordered_remove_by_index(Array<T>& src, s64 index) {
}
template <typename T>
void array_ordered_remove_by_value(Array<T>& src, T item) {
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) {
void array_unordered_remove_by_index (Array<T>& 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<T>& src, s64 index) {
}
template <typename T>
s64 array_unordered_remove_by_value(Array<T>& src, T item, s64 max_count_to_remove) {
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) {
@ -247,6 +247,11 @@ struct ArrayView {
s64 count;
T* data;
ArrayView(Array<T> 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 <typename T>
bool is_zero(ArrayView<T> src) {
bool is_empty (ArrayView<T> src) {
if (src.count == 0) return true;
return false;
}
@ -278,26 +283,17 @@ bool is_zero(ArrayView<T> src) {
// 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) {
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;
}
// can also use ArrayView<T>(count, data) for initialization!
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> array_view (Array<T> array) {
ArrayView<T> av;
av.count = array.count;
av.data = array.data;
@ -305,7 +301,7 @@ ArrayView<T> array_view(Array<T> array) {
}
template <typename T>
ArrayView<T> array_view(ArrayView<T> array, s64 start_index, s64 view_count) {
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);
@ -314,7 +310,7 @@ ArrayView<T> array_view(ArrayView<T> array, s64 start_index, s64 view_count) {
}
template <typename T>
ArrayView<T> array_view(Array<T> array, s64 start_index, s64 view_count) {
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);
@ -323,13 +319,13 @@ ArrayView<T> array_view(Array<T> array, s64 start_index, s64 view_count) {
}
template <typename T>
void array_reset_keeping_memory(ArrayView<T>& src) {
void array_reset_keeping_memory (ArrayView<T>& src) {
src.count = 0;
}
template <typename T>
ArrayView<T> array_copy(const ArrayView<T>& src) {
ArrayView<T> array_copy (const ArrayView<T>& src) {
if (!src.data || src.count == 0) {
return ArrayView<T>(); // Return an empty array
}
@ -341,17 +337,18 @@ ArrayView<T> array_copy(const ArrayView<T>& src) {
}
template <typename T>
void array_free(ArrayView<T>& src) {
void array_free (ArrayView<T>& 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<s32>(6,7,8,9,10,51);`
template <typename T, typename... ArgValues>
Array<T> NewArrayFromValues(ArgValues... args) {
Array<T> array_from_values (ArgValues... args) {
constexpr s64 N = sizeof...(ArgValues);
auto array = Array<T>(N, /*initialize:*/false);
T values[] = {args...};
@ -362,8 +359,10 @@ Array<T> NewArrayFromValues(ArgValues... args) {
return array;
}
// Usage `auto view = array_view_from_values<s32>(1,2,3,4,5);`
template <typename T, typename... ArgValues>
ArrayView<T> NewArrayViewFromValues(ArgValues... args) {
ArrayView<T> array_view_from_values (ArgValues... args) {
constexpr s64 N = sizeof...(ArgValues);
auto array = ArrayView<T>(N, /*initialize:*/false);
T values[] = {args...};

View File

@ -13,6 +13,10 @@
#error "CPU not supported (yet)!"
#endif
#include <stdio.h> // vsnprintf
#include <cstdarg> // va_list, ...
#if OS_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@ -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)

View File

@ -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<Thread*> 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<Thread*> child_threads; // maybe should be linked-list?
Thread* thread_that_created_me = nullptr; // so we can remove from above array
string thread_name;
};

View File

@ -1,483 +0,0 @@
#include "Basic.h"
#include <cmath>
#include <math.h> // isnan, floor
#include <stdlib.h> // qsort
#include <cassert> // assert
Native_Error* Basic_Difference2 (ArrayView<f64> input, ArrayView<f64>& 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<f64> 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<f64> input) {
Array_Check(input);
qsort(input.data, input.count, sizeof(double), qsort_doubles_comparator_nonnan);
return nullptr;
}
Native_Error* Basic_Median2 (ArrayView<f64> 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<f64> 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<f64> 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<f64> 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<f64> 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<f64> 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<f64> 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<f64> input, ArrayView<s64> 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<f64> 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<f64> 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<s64>(kp1);
s64 k_i = static_cast<s64>(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<f64> 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<f64> input, s64 first_dimension, s64 second_dimension, ArrayView<f64> 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<f64> 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<s64>(kp1);
s64 k_i = static_cast<s64>(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<f64> 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<f64> 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<f64> roots, ArrayView<f64> 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<Eigen::VectorXd>(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);
}

View File

@ -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<f64> input, ArrayView<f64>& output);
PROTOTYPING_API Native_Error* Basic_Mean2 (ArrayView<f64> input, f64* mean);
PROTOTYPING_API Native_Error* Basic_QuickSortInPlace (ArrayView<f64> input);
PROTOTYPING_API Native_Error* Basic_Median2 (ArrayView<f64> unsorted_input, f64* median);
PROTOTYPING_API Native_Error* Basic_RescaleInPlace (ArrayView<f64> input, double min, double max);
PROTOTYPING_API Native_Error* Basic_Min2 (ArrayView<f64> input, f64* min_out);
PROTOTYPING_API Native_Error* Basic_Max2 (ArrayView<f64> 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<f64> input, f64* stddev);
PROTOTYPING_API Native_Error* Basic_Variance2 (ArrayView<f64> input, f64* variance);
PROTOTYPING_API Native_Error* Basic_Root_Mean_Squared2 (ArrayView<f64> input, f64* rms);
// Sorts an array from largest to smallest, returning the indices of the sorted array
PROTOTYPING_API Native_Error* Basic_IndexSort2 (ArrayView<f64> input, ArrayView<s64> output);
PROTOTYPING_API Native_Error* Basic_Count_Non_Nan2 (ArrayView<f64> input, s64* non_nan_count);
PROTOTYPING_API Native_Error* Basic_Calculate_Percentile_New (ArrayView<f64> 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<f64> input, f64 percentile, f64* percentile_value_out);
PROTOTYPING_API Native_Error* Basic_ReverseArrayInPlace (ArrayView<f64> 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<f64> input, s64 first_dimension, s64 second_dimension, ArrayView<f64> 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<f64> input, f64 outlier_threshold=1.5);
PROTOTYPING_API Native_Error* Basic_Replace_Values_Beyond_Threshold2 (ArrayView<f64> input, f64 low_threshold, f64 high_threshold, f64 replacement_value);
PROTOTYPING_API Native_Error* Basic_Roots_To_Polynomials2 (ArrayView<f64> roots, ArrayView<f64> polynomials);
// #TODO: Basic_Find (returns indices of non-zero elements).
// Need to make this generic, maybe using templates?
// PROTOTYPING_API ArrayView<s32> Basic_Find(ArrayView<f64> 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);

View File

@ -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 <stdio.h> // vsnprintf, printf
#include <cstdarg> // 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<Native_Error>(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<Native_Error>(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;
}

30
lib/Base/ErrorType.cpp Normal file
View File

@ -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);

16
lib/Base/Logger.h Normal file
View File

@ -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, ...);

View File

@ -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 <stdio.h> // vsnprintf
#include <cstdarg> // 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<u8>(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<u8>(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<u8> 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<u8>(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<u8>(1, new_reserve);
}
force_inline void append (String_Builder* sb, string s) {
array_add(*sb, ArrayView<u8>(s.count, s.data));
}
void append (String_Builder* sb, ArrayView<string> 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<u8>(s.count, s.data));
}
}
force_inline void append_no_add (String_Builder* sb, string s) {
array_add(*sb, ArrayView<u8>(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);
}

100
lib/Base/String.h Normal file
View File

@ -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<u8> 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<u8> 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<string> 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);

View File

@ -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<Work_Entry>();
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<void*> get_completed_work (Thread_Group* group) {
@ -290,14 +286,13 @@ ArrayView<void*> 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<void*> 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<void*> get_completed_work (Thread_Group* group) {
Assert(results.count == old_count + new_count);
}
return {};
return ArrayView<void*>(results);
}

View File

@ -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;
};

View File

@ -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

32
lib/OS/OS_Win32.h Normal file
View File

@ -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;

View File

@ -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"

View File

@ -20,7 +20,6 @@ internal void Main_Entry_Point (int argc, WCHAR **argv);
#include <d3d11.h>
#include <tchar.h>
#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<s32>(6,7,8,9,10,51);
auto view = array_view_from_values<s32>(1,2,3,4,5);
*/
// ImGui_Application();
// #TODO: #Main - `Main_Entry_Point`
// [ ] Setup Mouse and Keyboard Inputs
// [ ] Launch second thread
// OS_Create_Window();
}