- File IO

- os_file_open(string path, Os_Io_Open_Flags flags)
	- os_file_close(File f)
	- os_file_delete(string path)
	- os_file_write_string(File f, string s)
	- os_file_write_bytes(File f, void *buffer, u64 size_in_bytes)
	- os_file_read(File f, void* buffer, u64 bytes_to_read, u64 *actual_read_bytes)
	- os_write_entire_file_handle(File f, string data)
	- os_write_entire_file(string path, string data)
	- os_read_entire_file_handle(File f, string *result)
	- os_read_entire_file(string path, string *result)
	- fprint(File, string/char*, ...)
	- Buncha tests

- os_high_precision_sleep
- talloc_string
- Program memory is touched on VirtualAlloc so it actually allocates physical memory (and we can tell when an address is untouched)
This commit is contained in:
Charlie 2024-06-29 20:55:43 +02:00
parent 8a0fc81576
commit 6879130bb0
12 changed files with 486 additions and 143 deletions

View file

@ -2,7 +2,7 @@
///
// Build config stuff
#define RUN_TESTS 0
#define RUN_TESTS 1
// When we need very debug
// #define CONFIGURATION VERY_DEBUG

View file

@ -4,6 +4,6 @@ mkdir build
pushd build
clang -g -o cgame.exe ../build.c -O0 -std=c11 -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -lgdi32 -luser32 -lopengl32
clang -g -o cgame.exe ../build.c -O0 -std=c11 -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -lgdi32 -luser32 -lopengl32 -lwinmm
popd

View file

@ -7,7 +7,7 @@ pushd build
mkdir release
pushd release
clang -o cgame.exe ../../build.c -Ofast -std=c11 -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -lgdi32 -luser32 -lopengl32
clang -o cgame.exe ../../build.c -Ofast -std=c11 -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -lgdi32 -luser32 -lopengl32 -lwinmm
popd
popd

View file

@ -10,6 +10,9 @@ int start(int argc, char **argv) {
seed_for_random = os_get_current_cycle_count();
const float64 fps_limit = 420;
const float64 min_frametime = 1.0 / fps_limit;
float64 last_time = os_get_current_time_in_seconds();
while (!window.should_close) {
reset_temporary_storage();
@ -19,6 +22,11 @@ int start(int argc, char **argv) {
float64 now = os_get_current_time_in_seconds();
float64 delta = now - last_time;
if (delta < min_frametime) {
os_high_precision_sleep((min_frametime-delta)*1000.0);
now = os_get_current_time_in_seconds();
delta = now - last_time;
}
last_time = now;
if (is_key_just_released(KEY_ESCAPE)) {
@ -27,6 +35,7 @@ int start(int argc, char **argv) {
if (is_key_just_released('E')) {
print("Mouse pos: %f, %f\n", input_frame.mouse_x, input_frame.mouse_y);
print("FPS: %.2f\n", 1.0 / delta);
}
for (u64 i = 0; i < input_frame.number_of_events; i++) {

View file

@ -23,6 +23,8 @@ typedef u8 bool;
#define local_persist static
#define forward_global extern
#define ifnt(x) if (!(x))
#ifdef _MSC_VER
@ -42,7 +44,7 @@ void printf(const char* fmt, ...);
#define assert_line(line, cond, ...) if (!(cond)) { printf("Assertion failed in file " __FILE__ " on line " STR(line) "\nFailed Condition: " #cond ". Message: " __VA_ARGS__); os_break(); }
#define assert(cond, ...) assert_line(__LINE__, cond, __VA_ARGS__);
#if CONFIGRATION == RELEASE
#if CONFIGURATION == RELEASE
#undef assert
#define assert(...)
#endif

View file

@ -298,7 +298,7 @@ void *heap_alloc(u64 size) {
meta->size = size;
meta->block = best_fit_block;
#if VERY_DEBUG
#if CONFIGURATION == VERY_DEBUG
santiy_check_free_node_tree(meta->block);
#endif
@ -368,7 +368,7 @@ void heap_dealloc(void *p) {
}
}
#if VERY_DEBUG
#if CONFIGURATION == VERY_DEBUG
santiy_check_free_node_tree(block);
#endif

View file

@ -1,4 +1,26 @@
#define DEBUG 0
#define VERY_DEBUG 1
#define RELEASE 2
#if !defined(CONFIGURATION)
#if defined(NDEBUG)
#define CONFIGURATION RELEASE
#else
#define CONFIGURATION DEBUG
#endif
#endif
#ifndef ENTRY_PROC
#define ENTRY_PROC entry
#endif
#define WINDOWS 0
#define LINUX 1
#define MACOS 2
// This needs to be included before dependencies
#include "base.c"
@ -25,34 +47,18 @@ void lodepng_free(void* ptr) {
/////
#define DEBUG 0
#define VERY_DEBUG 1
#define RELEASE 2
#if !defined(CONFIGURATION)
#ifdef _DEBUG
#define CONFIGURATION DEBUG
#elif defined(NDEBUG)
#define CONFIGURATION RELEASE
#endif
#endif
#ifndef ENTRY_PROC
#define ENTRY_PROC entry
#endif
#ifdef _WIN32
#include <Windows.h>
#define OS_WINDOWS
#define TARGET_OS WINDOWS
#elif defined(__linux__)
// Include whatever #Incomplete #Portability
#define OS_LINUX
#define TARGET_OS LINUX
#error "Linux is not supported yet";
#elif defined(__APPLE__) && defined(__MACH__)
// Include whatever #Incomplete #Portability
#define OS_MAC
#define TARGET_OS MACOS
#error "Mac is not supported yet";
#else
#error "Current OS not supported!";
@ -69,11 +75,11 @@ void lodepng_free(void* ptr) {
#ifndef GFX_RENDERER
// #Portability
#ifdef OS_WINDOWS
#if TARGET_OS == WINDOWS
#define GFX_RENDERER GFX_RENDERER_D3D11
#elif defined (OS_LINUX)
#elif TARGET_OS == LINUX
#define GFX_RENDERER GFX_RENDERER_VULKAN
#elif defined (OS_MAC)
#elif TARGET_OS == MACOS
#define GFX_RENDERER GFX_RENDERER_METAL
#endif
#endif
@ -107,11 +113,11 @@ void lodepng_free(void* ptr) {
#error "Unknown renderer defined in GFX_RENDERER"
#endif
#ifdef OS_WINDOWS
#if TARGET_OS == WINDOWS
#include "os_impl_windows.c"
#elif defined (OS_LINUX)
#elif TARGET_OS == LINUX
#elif defined (OS_MAC)
#elif TARGET_OS == MACOS
#endif

View file

@ -264,6 +264,8 @@ bool os_grow_program_memory(u64 new_size) {
return false;
}
program_memory_size = aligned_size;
memset(program_memory, 0xBA, program_memory_size);
} else {
void* tail = (u8*)program_memory + program_memory_size;
u64 m = ((u64)program_memory_size % os.granularity);
@ -276,6 +278,7 @@ bool os_grow_program_memory(u64 new_size) {
assert(m == 0, "amount_to_allocate is not aligned to granularity!");
// Just keep allocating at the tail of the current chunk
void* result = VirtualAlloc(tail, amount_to_allocate, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
memset(result, 0xBA, amount_to_allocate);
if (result == 0) {
os_unlock_mutex(program_memory_mutex); // #Sync
return false;
@ -294,31 +297,15 @@ bool os_grow_program_memory(u64 new_size) {
return true;
}
Mutex_Handle os_make_mutex() {
return CreateMutex(0, FALSE, 0);
}
void os_destroy_mutex(Mutex_Handle m) {
CloseHandle(m);
}
void os_lock_mutex(Mutex_Handle m) {
DWORD wait_result = WaitForSingleObject(m, INFINITE);
switch (wait_result) {
case WAIT_OBJECT_0:
break;
///
///
// Threading
///
case WAIT_ABANDONED:
break;
default:
assert(false, "Unexpected mutex lock result");
break;
}
}
void os_unlock_mutex(Mutex_Handle m) {
BOOL result = ReleaseMutex(m);
assert(result, "Unlock mutex failed");
}
///
// Thread primitive
DWORD WINAPI win32_thread_invoker(LPVOID param) {
Thread *t = (Thread*)param;
@ -353,56 +340,37 @@ void os_join_thread(Thread *t) {
CloseHandle(t->os_handle);
}
void os_sleep(u32 ms) {
Sleep(ms);
///
// Mutex primitive
Mutex_Handle os_make_mutex() {
return CreateMutex(0, FALSE, 0);
}
void os_destroy_mutex(Mutex_Handle m) {
CloseHandle(m);
}
void os_lock_mutex(Mutex_Handle m) {
DWORD wait_result = WaitForSingleObject(m, INFINITE);
switch (wait_result) {
case WAIT_OBJECT_0:
break;
case WAIT_ABANDONED:
break;
default:
assert(false, "Unexpected mutex lock result");
break;
}
}
void os_unlock_mutex(Mutex_Handle m) {
BOOL result = ReleaseMutex(m);
assert(result, "Unlock mutex failed");
}
void os_yield_thread() {
SwitchToThread();
}
#include <intrin.h>
u64 os_get_current_cycle_count() {
return __rdtsc();
}
float64 os_get_current_time_in_seconds() {
LARGE_INTEGER frequency, counter;
if (!QueryPerformanceFrequency(&frequency) || !QueryPerformanceCounter(&counter)) {
return -1.0;
}
return (double)counter.QuadPart / (double)frequency.QuadPart;
}
Dynamic_Library_Handle os_load_dynamic_library(string path) {
return LoadLibraryA(temp_convert_to_null_terminated_string(path));
}
void *os_dynamic_library_load_symbol(Dynamic_Library_Handle l, string identifier) {
return GetProcAddress(l, temp_convert_to_null_terminated_string(identifier));
}
void os_unload_dynamic_library(Dynamic_Library_Handle l) {
FreeLibrary(l);
}
void os_write_string_to_stdout(string s) {
HANDLE win32_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
if (win32_stdout == INVALID_HANDLE_VALUE) return;
WriteFile(win32_stdout, s.data, s.count, 0, NULL);
}
void* os_get_stack_base() {
NT_TIB* tib = (NT_TIB*)NtCurrentTeb();
return tib->StackBase;
}
void* os_get_stack_limit() {
NT_TIB* tib = (NT_TIB*)NtCurrentTeb();
return tib->StackLimit;
}
///
// Spinlock "primitive"
Spinlock *os_make_spinlock() {
Spinlock *l = cast(Spinlock*)alloc(sizeof(Spinlock));
@ -427,6 +395,10 @@ void os_spinlock_unlock(Spinlock *l) {
assert(success, "This thread should have acquired the spinlock but compare_and_swap failed");
}
///
// Concurrency utilities
bool os_compare_and_swap_8(u8 *a, u8 b, u8 old) {
// #Portability not sure how portable this is.
return _InterlockedCompareExchange8((volatile CHAR*)a, (CHAR)b, (CHAR)old) == (CHAR)old;
@ -449,6 +421,226 @@ bool os_compare_and_swap_bool(bool *a, bool b, bool old) {
}
void os_sleep(u32 ms) {
Sleep(ms);
}
void os_yield_thread() {
SwitchToThread();
}
void os_high_precision_sleep(f64 ms) {
const f64 s = ms/1000.0;
f64 start = os_get_current_time_in_seconds();
f64 end = start + (f64)s;
u32 sleep_time = (u32)((end-start)-1.0);
bool do_sleep = sleep_time >= 1;
timeBeginPeriod(1); // I don't see a reason to reset this
if (do_sleep) os_sleep(sleep_time);
while (os_get_current_time_in_seconds() < end) {
os_yield_thread();
}
}
///
///
// Time
///
#include <intrin.h> // #Cdep
u64 os_get_current_cycle_count() {
return __rdtsc();
}
float64 os_get_current_time_in_seconds() {
LARGE_INTEGER frequency, counter;
if (!QueryPerformanceFrequency(&frequency) || !QueryPerformanceCounter(&counter)) {
return -1.0;
}
return (double)counter.QuadPart / (double)frequency.QuadPart;
}
///
///
// Dynamic Libraries
///
Dynamic_Library_Handle os_load_dynamic_library(string path) {
return LoadLibraryA(temp_convert_to_null_terminated_string(path));
}
void *os_dynamic_library_load_symbol(Dynamic_Library_Handle l, string identifier) {
return GetProcAddress(l, temp_convert_to_null_terminated_string(identifier));
}
void os_unload_dynamic_library(Dynamic_Library_Handle l) {
FreeLibrary(l);
}
///
///
// IO
///
const File OS_INVALID_FILE = INVALID_HANDLE_VALUE;
void os_write_string_to_stdout(string s) {
HANDLE win32_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
if (win32_stdout == INVALID_HANDLE_VALUE) return;
WriteFile(win32_stdout, s.data, s.count, 0, NULL);
}
// context.allocator
u16 *win32_fixed_utf8_to_null_terminated_wide(string utf8) {
u64 utf16_length = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)utf8.data, (int)utf8.count, 0, 0);
u16 *utf16_str = (u16 *)alloc((utf16_length + 1) * sizeof(u16));
int result = MultiByteToWideChar(CP_UTF8, 0, (LPCCH)utf8.data, (int)utf8.count, utf16_str, utf16_length);
if (result == 0) {
dealloc(utf16_str);
return NULL;
}
utf16_str[utf16_length] = 0;
return utf16_str;
}
u16 *temp_win32_fixed_utf8_to_null_terminated_wide(string utf8) {
push_temp_allocator();
u16 *result = win32_fixed_utf8_to_null_terminated_wide(utf8);
pop_allocator();
return result;
}
File os_file_open(string path, Os_Io_Open_Flags flags) {
DWORD access = GENERIC_READ;
DWORD creation = 0;
if (flags & O_WRITE) {
access |= GENERIC_WRITE;
}
if (flags & O_CREATE) {
creation = CREATE_ALWAYS;
} else {
creation = OPEN_EXISTING;
}
u16 *wide = temp_win32_fixed_utf8_to_null_terminated_wide(path);
return CreateFileW(wide, access, 0, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
}
void os_file_close(File f) {
CloseHandle(f);
}
bool os_file_delete(string path) {
u16 *path_wide = temp_win32_fixed_utf8_to_null_terminated_wide(path);
return (bool)DeleteFileW(path_wide);
}
bool os_file_write_string(File f, string s) {
DWORD written;
BOOL result = WriteFile(f, s.data, s.count, &written, NULL);
return result && (written == s.count);
}
bool os_file_write_bytes(File f, void *buffer, u64 size_in_bytes) {
DWORD written;
BOOL result = WriteFile(f, buffer, (DWORD)size_in_bytes, &written, NULL);
return result && (written == size_in_bytes);
}
bool os_file_read(File f, void* buffer, u64 bytes_to_read, u64 *actual_read_bytes) {
DWORD read;
BOOL result = ReadFile(f, buffer, (DWORD)bytes_to_read, &read, NULL);
if (actual_read_bytes) {
*actual_read_bytes = read;
}
return result;
}
bool os_write_entire_file_handle(File f, string data) {
return os_file_write_string(f, data);
}
bool os_write_entire_file(string path, string data) {
File file = os_file_open(path, O_WRITE | O_CREATE);
if (file == OS_INVALID_FILE) {
return false;
}
bool result = os_file_write_string(file, data);
os_file_close(file);
return result;
}
bool os_read_entire_file_handle(File f, string *result) {
LARGE_INTEGER file_size;
if (!GetFileSizeEx(f, &file_size)) {
return false;
}
result->data = (u8*)alloc(file_size.QuadPart);
if (!result->data) {
return false;
}
result->count = file_size.QuadPart;
return os_file_read(f, result->data, file_size.QuadPart, NULL);
}
bool os_read_entire_file(string path, string *result) {
File file = os_file_open(path, O_READ);
if (file == OS_INVALID_FILE) {
return false;
}
bool res = os_read_entire_file_handle(file, result);
os_file_close(file);
return res;
}
void fprints(File f, string fmt, ...) {
va_list args;
va_start(args, fmt);
fprint_va_list_buffered(f, fmt, args);
va_end(args);
}
void fprintf(File f, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
string s;
s.data = cast(u8*)fmt;
s.count = strlen(fmt);
fprint_va_list_buffered(f, s, args);
va_end(args);
}
///
///
// Memory
///
void* os_get_stack_base() {
NT_TIB* tib = (NT_TIB*)NtCurrentTeb();
return tib->StackBase;
}
void* os_get_stack_limit() {
NT_TIB* tib = (NT_TIB*)NtCurrentTeb();
return tib->StackLimit;
}
void os_update() {
local_persist Os_Window last_window;
@ -509,7 +701,7 @@ void os_update() {
memcpy(win32_key_states, input_frame.key_states, sizeof(input_frame.key_states));
input_frame.number_of_events = 0;
// #Simd ?
for (u64 i = 0; i < INPUT_KEY_CODE_COUNT; i++) {
win32_key_states[i] &= ~(INPUT_STATE_REPEAT);
win32_key_states[i] &= ~(INPUT_STATE_JUST_PRESSED);

View file

@ -5,18 +5,21 @@
typedef HANDLE Thread_Handle;
typedef HMODULE Dynamic_Library_Handle;
typedef HWND Window_Handle;
typedef HANDLE File;
#elif defined(__linux__)
typedef SOMETHING Mutex_Handle;
typedef SOMETHING Thread_Handle;
typedef SOMETHING Dynamic_Library_Handle;
typedef SOMETHING Window_Handle;
typedef SOMETHING File;
#error "Linux is not supported yet";
#elif defined(__APPLE__) && defined(__MACH__)
typedef SOMETHING Mutex_Handle;
typedef SOMETHING Thread_Handle;
typedef SOMETHING Dynamic_Library_Handle;
typedef SOMETHING Window_Handle;
typedef SOMETHING File;
#error "Mac is not supported yet";
#else
#error "Current OS not supported!";
@ -128,8 +131,7 @@ Thread* os_make_thread(Thread_Proc proc);
void os_start_thread(Thread* t);
void os_join_thread(Thread* t);
void os_sleep(u32 ms);
void os_yield_thread();
///
// Mutex primitive
@ -148,7 +150,7 @@ void os_spinlock_lock(Spinlock* l);
void os_spinlock_unlock(Spinlock* l);
///
// Sync utilities
// Concurrency utilities
bool os_compare_and_swap_8 (u8 *a, u8 b, u8 old);
bool os_compare_and_swap_16 (u16 *a, u16 b, u16 old);
@ -156,6 +158,9 @@ bool os_compare_and_swap_32 (u32 *a, u32 b, u32 old);
bool os_compare_and_swap_64 (u64 *a, u64 b, u64 old);
bool os_compare_and_swap_bool(bool *a, bool b, bool old);
void os_sleep(u32 ms);
void os_yield_thread();
void os_high_precision_sleep(f64 ms);
///
///
@ -182,8 +187,64 @@ void os_unload_dynamic_library(Dynamic_Library_Handle l);
// IO
///
forward_global const File OS_INVALID_FILE;
void os_write_string_to_stdout(string s);
typedef enum Os_Io_Open_Flags {
O_READ = 0,
O_CREATE = 1<<0, // Will replace existing file and start writing from 0 (if writing)
O_WRITE = 1<<1,
// To append, pass WRITE flag without CREATE flag
} Os_Io_Open_Flags;
File os_file_open(string path, Os_Io_Open_Flags flags);
void os_file_close(File f);
bool os_file_delete(string path);
bool os_file_write_string(File f, string s);
bool os_file_write_bytes(File f, void *buffer, u64 size_in_bytes);
bool os_file_read(File f, void* buffer, u64 bytes_to_read, u64 *actual_read_bytes);
bool os_write_entire_file_handle(File f, string data);
bool os_write_entire_file(string path, string data);
bool os_read_entire_file_handle(File f, string *result);
bool os_read_entire_file(string path, string *result);
void fprints(File f, string fmt, ...);
void fprintf(File f, const char* fmt, ...);
#define fprint(...) _Generic((FIRST_ARG(__VA_ARGS__)), \
string: fprints, \
default: fprintf \
)(__VA_ARGS__)
void fprint_va_list_buffered(File f, const string fmt, va_list args) {
string current = fmt;
char buffer[PRINT_BUFFER_SIZE];
while (true) {
u64 size = min(current.count, PRINT_BUFFER_SIZE-1);
if (current.count <= 0) break;
memcpy(buffer, current.data, size);
char fmt_cstring[PRINT_BUFFER_SIZE+1];
memcpy(fmt_cstring, current.data, size);
fmt_cstring[size] = 0;
string s = sprint_null_terminated_string_va_list_to_buffer(fmt_cstring, args, buffer, PRINT_BUFFER_SIZE);
os_file_write_string(f, s);
current.count -= size;
current.data += size;
}
}
///
///

View file

@ -73,6 +73,12 @@ string alloc_string(u64 count) {
void dealloc_string(string s) {
dealloc(s.data);
}
string talloc_string(u64 count) {
push_temp_allocator();
string s = alloc_string(count);
pop_allocator();
return s;
}
// context.allocator !
string string_concat(const string left, const string right) {
@ -97,7 +103,6 @@ char *temp_convert_to_null_terminated_string(const string s) {
pop_allocator();
return c;
}
bool strings_match(string a, string b) {
if (a.count != b.count) return false;

View file

@ -203,6 +203,7 @@ void print_va_list_buffered(const string fmt, va_list args) {
}
}
// context.allocator (alloc & dealloc)
void prints(const string fmt, ...) {
va_list args;

View file

@ -190,20 +190,11 @@ void test_thread_proc1(Thread* t) {
os_sleep(5);
printf("Hello from thread %llu\n", t->id);
}
Mutex_Handle test_mutex;
void test_thread_proc2(Thread* t) {
os_lock_mutex(test_mutex);
printf("Thread %llu part 1\n", t->id);
os_sleep(1);
printf("Thread %llu part 2\n", t->id);
os_sleep(1);
printf("Thread %llu part 3\n", t->id);
os_unlock_mutex(test_mutex);
}
void test_threads() {
Thread* t = os_make_thread(test_thread_proc1);
os_start_thread(t);
os_sleep(10);
os_sleep(20);
printf("This should be printed in middle of thread execution\n");
os_join_thread(t);
printf("Thread is joined\n");
@ -211,17 +202,6 @@ void test_threads() {
Mutex_Handle m = os_make_mutex();
os_lock_mutex(m);
os_unlock_mutex(m);
test_mutex = os_make_mutex();
Thread *threads[100];
for (int i = 0; i < 100; i++) {
threads[i] = os_make_thread(test_thread_proc2);
os_start_thread(threads[i]);
}
for (int i = 0; i < 100; i++) {
os_join_thread(threads[i]);
}
}
void test_allocator_threaded(Thread *t) {
@ -266,8 +246,8 @@ void test_strings() {
dealloc_string(alloc_str);
// Test string_concat
string str1 = const_string("Hello, ");
string str2 = const_string("World!");
string str1 = fixed_string("Hello, ");
string str2 = fixed_string("World!");
string concat_str = string_concat(str1, str2);
assert(concat_str.count == str1.count + str2.count, "Failed: string_concat");
assert(memcmp(concat_str.data, "Hello, World!", concat_str.count) == 0, "Failed: string_concat");
@ -284,7 +264,7 @@ void test_strings() {
// No need to dealloc, it's temporary storage
// Test sprint
string format_str = const_string("Number: %d");
string format_str = fixed_string("Number: %d");
string formatted_str = sprint(format_str, 42);
char* formatted_cstr = convert_to_null_terminated_string(formatted_str);
assert(strcmp(formatted_cstr, "Number: 42") == 0, "Failed: sprint");
@ -299,16 +279,16 @@ void test_strings() {
// Test print and printf (visual inspection)
printf("Expected output: Hello, World!\n");
print("Hello, %s!\n", const_string("World"));
print("Hello, %s!\n", fixed_string("World"));
printf("Expected output: Number: 1234\n");
print(const_string("Number: %d\n"), 1234);
print(fixed_string("Number: %d\n"), 1234);
printf("Expected output: Number: 1234\n");
print(const_string("Number: %d\n"), 1234);
print(fixed_string("Number: %d\n"), 1234);
printf("Expected output: Mixed values: 42 and 3.14\n");
print(const_string("Mixed values: %d and %.2f\n"), 42, 3.14);
print(fixed_string("Mixed values: %d and %.2f\n"), 42, 3.14);
// This should fail assert and print descriptive error
//printf("Expected output (printf): Hello, World!\n");
@ -327,7 +307,7 @@ void test_strings() {
printf("Mixed values: %d and %.2f\n", 99, 2.71);
// Test handling of empty strings
string empty_str = const_string("");
string empty_str = fixed_string("");
string concat_empty_str = string_concat(empty_str, empty_str);
assert(concat_empty_str.count == 0, "Failed: string_concat with empty strings");
dealloc_string(concat_empty_str);
@ -342,35 +322,118 @@ void test_strings() {
dealloc_string(large_concat_str);
// Test string with special characters
string special_char_str = const_string("Special chars: \n\t\r");
string special_char_str = fixed_string("Special chars: \n\t\r");
cstr = convert_to_null_terminated_string(special_char_str);
assert(strcmp(cstr, "Special chars: \n\t\r") == 0, "Failed: special character string");
dealloc(cstr);
string a = tprintf("Hello, %cs!\n", "balls");
string balls1 = string_view(a, 7, 5);
string balls2 = const_string("balls");
string balls2 = fixed_string("balls");
assert(strings_match(balls1, balls2), "String match failed");
assert(!strings_match(balls1, a), "String match failed");
}
void test_file_io() {
#if TARGET_OS == WINDOWS
// Test win32_fixed_utf8_to_null_terminated_wide
string utf8_str = fixed_string("Test");
u16 *wide_str = win32_fixed_utf8_to_null_terminated_wide(utf8_str);
assert(wide_str != NULL, "Failed: win32_fixed_utf8_to_null_terminated_wide");
assert(wide_str[4] == 0, "Failed: win32_fixed_utf8_to_null_terminated_wide");
dealloc(wide_str);
// Test temp_win32_fixed_utf8_to_null_terminated_wide
push_temp_allocator();
wide_str = temp_win32_fixed_utf8_to_null_terminated_wide(utf8_str);
assert(wide_str != NULL, "Failed: temp_win32_fixed_utf8_to_null_terminated_wide");
assert(wide_str[4] == 0, "Failed: temp_win32_fixed_utf8_to_null_terminated_wide");
pop_allocator();
#endif
File file = OS_INVALID_FILE;
os_file_close(file);
// Test os_file_open and os_file_close
file = os_file_open(fixed_string("test.txt"), O_WRITE | O_CREATE);
assert(file != OS_INVALID_FILE, "Failed: os_file_open (write/create)");
os_file_close(file);
// Test os_file_write_string and os_file_read
string hello_world_write = fixed_string("Hello, World!");
file = os_file_open(fixed_string("test.txt"), O_WRITE | O_CREATE);
assert(file != OS_INVALID_FILE, "Failed: os_file_open (write/create)");
bool write_result = os_file_write_string(file, hello_world_write);
assert(write_result, "Failed: os_file_write_string");
os_file_close(file);
file = os_file_open(fixed_string("test.txt"), O_READ);
assert(file != OS_INVALID_FILE, "Failed: os_file_open (read)");
string hello_world_read = talloc_string(hello_world_write.count);
bool read_result = os_file_read(file, hello_world_read.data, hello_world_read.count, &hello_world_read.count);
assert(read_result, "Failed: os_file_read %d", GetLastError());
assert(strings_match(hello_world_read, hello_world_write), "Failed: os_file_read write/read mismatch");
os_file_close(file);
// Test os_file_write_bytes
file = os_file_open(fixed_string("test_bytes.txt"), O_WRITE | O_CREATE);
assert(file != OS_INVALID_FILE, "Failed: os_file_open (write/create)");
int int_data = 42;
write_result = os_file_write_bytes(file, &int_data, sizeof(int));
assert(write_result, "Failed: os_file_write_bytes");
os_file_close(file);
// Test os_read_entire_file and os_write_entire_file
string write_data = fixed_string("Entire file test");
bool write_entire_result = os_write_entire_file(fixed_string("entire_test.txt"), write_data);
assert(write_entire_result, "Failed: os_write_entire_file");
string read_data;
bool read_entire_result = os_read_entire_file(fixed_string("entire_test.txt"), &read_data);
assert(read_entire_result, "Failed: os_read_entire_file");
assert(strings_match(read_data, write_data), "Failed: os_read_entire_file write/read mismatch");
assert(memcmp(read_data.data, write_data.data, write_data.count) == 0, "Failed: os_read_entire_file (content mismatch)");
dealloc(read_data.data);
// Test fprint
File balls = os_file_open(fixed_string("balls.txt"), O_WRITE | O_CREATE);
assert(balls != OS_INVALID_FILE, "Failed: Could not create balls.txt");
fprint(balls, "Hello, %cs!", "Balls");
os_file_close(balls);
string hello_balls;
read_entire_result = os_read_entire_file(fixed_string("balls.txt"), &hello_balls);
assert(read_entire_result, "Failed: could not read balls.txt");
assert(strings_match(hello_balls, fixed_string("Hello, Balls!")), "Failed: balls read/write mismatch. Expected 'Hello, Balls!', got '%s'", hello_balls);
// Clean up test files
bool delete_ok = false;
delete_ok = os_file_delete(fixed_string("test.txt"));
assert(delete_ok, "Failed: could not delete test.txt");
delete_ok = os_file_delete(fixed_string("test_bytes.txt"));
assert(delete_ok, "Failed: could not delete test_bytes.txt");
delete_ok = os_file_delete(fixed_string("entire_test.txt"));
assert(delete_ok, "Failed: could not delete entire_test.txt");
delete_ok = os_file_delete(fixed_string("balls.txt"));
assert(delete_ok, "Failed: could not delete balls.txt");
}
void oogabooga_run_tests() {
printf("Testing allocator...\n");
printf("Testing allocator... ");
test_allocator(true);
printf("OK!\n");
printf("Testing threads...\n");
printf("Testing threads... ");
test_threads();
printf("OK!\n");
printf("Testing strings...\n");
printf("Testing strings... ");
test_strings();
printf("OK!\n");
printf("Thread bombing allocator...\n");
printf("Thread bombing allocator... ");
Thread* threads[100];
for (int i = 0; i < 100; i++) {
threads[i] = os_make_thread(test_allocator_threaded);
@ -380,4 +443,8 @@ void oogabooga_run_tests() {
os_join_thread(threads[i]);
}
printf("OK!\n");
printf("Testing file IO... ");
test_file_io();
printf("OK!\n");
}