????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
This commit is contained in:
commit
cc74dc68e4
22 changed files with 2564 additions and 1963 deletions
1
build.c
1
build.c
|
@ -29,6 +29,7 @@ typedef struct Context_Extra {
|
|||
#include "oogabooga/examples/minimal_game_loop.c"
|
||||
|
||||
// #include "oogabooga/examples/text_rendering.c"
|
||||
// #include "oogabooga/examples/custom_logger.c"
|
||||
// #include "oogabooga/examples/renderer_stress_test.c"
|
||||
|
||||
// This is where you swap in your own project!
|
||||
|
|
|
@ -1,3 +1,66 @@
|
|||
## v0.00.004 - Custom logging, more concurrency & bugfixing
|
||||
|
||||
Concurrency:
|
||||
- Refactored spinlock out of OS api (deprecated old procs)
|
||||
- Concurrency utilites:
|
||||
void spinlock_init(Spinlock *l);
|
||||
void spinlock_acquire_or_wait(Spinlock* l);
|
||||
bool spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds);
|
||||
void spinlock_release(Spinlock* l);
|
||||
void mutex_init(Mutex *m);
|
||||
void mutex_destroy(Mutex *m);
|
||||
void mutex_acquire_or_wait(Mutex *m);
|
||||
void mutex_release(Mutex *m);
|
||||
void binary_semaphore_init(Binary_Semaphore *sem, bool initial_state);
|
||||
void binary_semaphore_destroy(Binary_Semaphore *sem);
|
||||
void binary_semaphore_wait(Binary_Semaphore *sem);
|
||||
void binary_semaphore_signal(Binary_Semaphore *sem);
|
||||
Macro MEMORY_BARRIER
|
||||
- Concurrency tests
|
||||
|
||||
Docs:
|
||||
- custom_logger.c example to show how one can make a custom logger and have logs displayed in-game
|
||||
|
||||
Utility:
|
||||
- draw_text_and_measure() which is just an overload of draw_text but it also does a measure and returns it
|
||||
|
||||
Misc:
|
||||
- Added u64 thread_id to global context. This is set in main() for main thread and in thread startup when you dispatch a Thread
|
||||
- Fixed a bug where plain rects would be drawn with the incorrect color
|
||||
- Fixed a bug where quads from earlier frames would be drawn
|
||||
|
||||
## v0.00.003 - Fixes
|
||||
|
||||
Random:
|
||||
- get_random_float64()
|
||||
- get_random_float32_in_range()
|
||||
- get_random_float64_in_range()
|
||||
- get_random_int_in_range()
|
||||
|
||||
Third party:
|
||||
- Added 3 minimal libraries for audio decoding
|
||||
- dr_mp3.h: MP3 decoding
|
||||
- dr_wav.h: WAV decoding
|
||||
- stb_vorbis.c: OGG decoding
|
||||
- Made a global thread_local "third_party_allocator" which is set when third party libraries are used so all memory still goes through our *program_memory
|
||||
- Stripped all third party libraries of external dependencies (C headers) & noise
|
||||
|
||||
Memory:
|
||||
- Improved assert messages to be more clear about what might be happening if they fail
|
||||
- Added more checks in debug to detect heap corruption and what not
|
||||
- Fixed a bug where the program would crash because a heap block was perfectly full
|
||||
|
||||
Misc:
|
||||
- Fixed typos in examples/text_rendering.c
|
||||
- Fixed Y placement of window when changing the window rect
|
||||
- Fixed window sizing when setting scaled_width or scaled_height
|
||||
- Updated readme
|
||||
- Portable DEPRECATED macro
|
||||
- Deprecate os_compare_and_swap and replace with more portable compare_and_swap
|
||||
- Fixed a bug where the wrong image would be drawn
|
||||
|
||||
|
||||
|
||||
## v0.00.003 - Fixes
|
||||
|
||||
Random:
|
||||
|
|
|
@ -65,7 +65,9 @@ typedef enum Log_Level {
|
|||
LOG_ERROR,
|
||||
LOG_INFO,
|
||||
LOG_WARNING,
|
||||
LOG_VERBOSE
|
||||
LOG_VERBOSE,
|
||||
|
||||
LOG_LEVEL_COUNT,
|
||||
} Log_Level;
|
||||
|
||||
|
||||
|
@ -80,6 +82,8 @@ Allocator get_heap_allocator();
|
|||
typedef struct Context {
|
||||
void *logger; // void(*Logger_Proc)(Log_Level level, string fmt, ...)
|
||||
|
||||
u64 thread_id;
|
||||
|
||||
CONTEXT_EXTRA extra;
|
||||
} Context;
|
||||
|
||||
|
|
27
oogabooga/bucket_array.c
Normal file
27
oogabooga/bucket_array.c
Normal file
|
@ -0,0 +1,27 @@
|
|||
|
||||
|
||||
|
||||
typedef struct Bucket_Array {
|
||||
|
||||
|
||||
|
||||
u64 _block_size;
|
||||
u64 _bucket_count;
|
||||
} Bucket_Array;
|
||||
|
||||
typedef struct Bucket_Array_Free_Node {
|
||||
struct Bucket_Array_Free_Node *next;
|
||||
} Bucket_Array_Free_Node;
|
||||
typedef struct Bucket_Array_Bucket {
|
||||
Bucket_Array_Free_Node *first_free;
|
||||
void *data;
|
||||
Bucket *next;
|
||||
} Bucket_Array_Bucket;
|
||||
|
||||
|
||||
Bucket_Array make_bucket_array(u64 block_size, u64 bucket_count) {
|
||||
Bucket_Array ba;
|
||||
|
||||
ba._block_size = block_size;
|
||||
ba._bucket_count = bucket_count;
|
||||
}
|
153
oogabooga/concurrency.c
Normal file
153
oogabooga/concurrency.c
Normal file
|
@ -0,0 +1,153 @@
|
|||
|
||||
typedef struct Spinlock Spinlock;
|
||||
typedef struct Mutex Mutex;
|
||||
typedef struct Binary_Semaphore Binary_Semaphore;
|
||||
|
||||
///
|
||||
// Spinlock "primitive"
|
||||
// Like a mutex but it eats up the entire core while waiting.
|
||||
// Beneficial if contention is low or sync speed is important
|
||||
void spinlock_init(Spinlock *l);
|
||||
void spinlock_acquire_or_wait(Spinlock* l);
|
||||
// This returns true if successfully acquired or false if timeout reached.
|
||||
bool spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds);
|
||||
void spinlock_release(Spinlock* l);
|
||||
|
||||
///
|
||||
// High-level mutex primitive (short spinlock then OS mutex lock)
|
||||
// Just spins for a few (configurable) microseconds with a spinlock,
|
||||
// and if acquiring fails it falls back to a OS mutex.
|
||||
void mutex_init(Mutex *m);
|
||||
void mutex_destroy(Mutex *m);
|
||||
void mutex_acquire_or_wait(Mutex *m);
|
||||
void mutex_release(Mutex *m);
|
||||
|
||||
///
|
||||
// Binary semaphore
|
||||
void binary_semaphore_init(Binary_Semaphore *sem, bool initial_state);
|
||||
void binary_semaphore_destroy(Binary_Semaphore *sem);
|
||||
void binary_semaphore_wait(Binary_Semaphore *sem);
|
||||
void binary_semaphore_signal(Binary_Semaphore *sem);
|
||||
|
||||
typedef struct Spinlock {
|
||||
bool locked;
|
||||
} Spinlock;
|
||||
|
||||
void spinlock_init(Spinlock *l) {
|
||||
memset(l, 0, sizeof(*l));
|
||||
}
|
||||
void spinlock_acquire_or_wait(Spinlock* l) {
|
||||
while (true) {
|
||||
bool expected = false;
|
||||
if (compare_and_swap_bool(&l->locked, true, expected)) {
|
||||
MEMORY_BARRIER;
|
||||
return;
|
||||
}
|
||||
while (l->locked) {
|
||||
// spinny boi
|
||||
}
|
||||
}
|
||||
}
|
||||
// Returns true on aquired, false if timeout seconds reached
|
||||
bool spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds) {
|
||||
f64 start = os_get_current_time_in_seconds();
|
||||
while (true) {
|
||||
bool expected = false;
|
||||
if (compare_and_swap_bool(&l->locked, true, expected)) {
|
||||
MEMORY_BARRIER;
|
||||
return true;
|
||||
}
|
||||
while (l->locked) {
|
||||
// spinny boi
|
||||
if ((os_get_current_time_in_seconds()-start) >= timeout_seconds) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void spinlock_release(Spinlock* l) {
|
||||
bool expected = true;
|
||||
bool success = compare_and_swap_bool(&l->locked, false, expected);
|
||||
assert(success, "This thread should have acquired the spinlock but compare_and_swap failed");
|
||||
MEMORY_BARRIER;
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
// High-level mutex primitive (short spinlock then OS mutex lock)
|
||||
#define MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS 100
|
||||
typedef struct Mutex {
|
||||
Spinlock spinlock;
|
||||
f64 spin_time_microseconds;
|
||||
Mutex_Handle os_handle;
|
||||
volatile bool spinlock_acquired;
|
||||
volatile u64 acquiring_thread;
|
||||
} Mutex;
|
||||
void mutex_init(Mutex *m) {
|
||||
spinlock_init(&m->spinlock);
|
||||
m->spin_time_microseconds = MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS;
|
||||
m->os_handle = os_make_mutex();
|
||||
m->spinlock_acquired = false;
|
||||
m->acquiring_thread = 0;
|
||||
}
|
||||
void mutex_destroy(Mutex *m) {
|
||||
os_destroy_mutex(m->os_handle);
|
||||
}
|
||||
void mutex_acquire_or_wait(Mutex *m) {
|
||||
if (spinlock_acquire_or_wait_timeout(&m->spinlock, m->spin_time_microseconds / 1000000.0)) {
|
||||
assert(!m->spinlock_acquired, "Internal sync error in Mutex");
|
||||
m->spinlock_acquired = true;
|
||||
MEMORY_BARRIER;
|
||||
}
|
||||
os_lock_mutex(m->os_handle);
|
||||
|
||||
assert(!m->acquiring_thread, "Internal sync error in Mutex: Multiple threads acquired");
|
||||
m->acquiring_thread = context.thread_id;
|
||||
MEMORY_BARRIER;
|
||||
}
|
||||
void mutex_release(Mutex *m) {
|
||||
assert(m->acquiring_thread != 0, "Tried to release a mutex which is not acquired");
|
||||
assert(m->acquiring_thread == context.thread_id, "Non-owning thread tried to release mutex");
|
||||
m->acquiring_thread = 0;
|
||||
MEMORY_BARRIER;
|
||||
bool was_spinlock_acquired = m->spinlock_acquired;
|
||||
m->spinlock_acquired = false;
|
||||
MEMORY_BARRIER;
|
||||
os_unlock_mutex(m->os_handle);
|
||||
MEMORY_BARRIER;
|
||||
if (was_spinlock_acquired) {
|
||||
spinlock_release(&m->spinlock);
|
||||
MEMORY_BARRIER;
|
||||
}
|
||||
MEMORY_BARRIER;
|
||||
}
|
||||
|
||||
typedef struct Binary_Semaphore {
|
||||
bool signaled;
|
||||
Mutex mutex;
|
||||
} Binary_Semaphore;
|
||||
|
||||
void binary_semaphore_init(Binary_Semaphore *sem, bool initial_state) {
|
||||
sem->signaled = initial_state;
|
||||
mutex_init(&sem->mutex);
|
||||
}
|
||||
|
||||
void binary_semaphore_destroy(Binary_Semaphore *sem) {
|
||||
mutex_destroy(&sem->mutex);
|
||||
}
|
||||
|
||||
void binary_semaphore_wait(Binary_Semaphore *sem) {
|
||||
mutex_acquire_or_wait(&sem->mutex);
|
||||
while (!sem->signaled) {
|
||||
mutex_release(&sem->mutex);
|
||||
os_yield_thread();
|
||||
mutex_acquire_or_wait(&sem->mutex);
|
||||
}
|
||||
sem->signaled = false;
|
||||
mutex_release(&sem->mutex);
|
||||
}
|
||||
|
||||
void binary_semaphore_signal(Binary_Semaphore *sem) {
|
||||
mutex_acquire_or_wait(&sem->mutex);
|
||||
sem->signaled = true;
|
||||
mutex_release(&sem->mutex);
|
||||
}
|
|
@ -33,6 +33,8 @@ typedef struct Cpu_Capabilities {
|
|||
__debugbreak();
|
||||
volatile int *a = 0;
|
||||
*a = 5;
|
||||
a = (int*)0xDEADBEEF;
|
||||
*a = 5;
|
||||
}
|
||||
#include <intrin.h>
|
||||
#pragma intrinsic(__rdtsc)
|
||||
|
@ -95,6 +97,8 @@ typedef struct Cpu_Capabilities {
|
|||
return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old);
|
||||
}
|
||||
|
||||
#define MEMORY_BARRIER _ReadWriteBarrier()
|
||||
|
||||
#elif COMPILER_GCC || COMPILER_CLANG
|
||||
#define inline __attribute__((always_inline)) inline
|
||||
#define alignat(x) __attribute__((aligned(x)))
|
||||
|
@ -103,6 +107,8 @@ typedef struct Cpu_Capabilities {
|
|||
__builtin_trap();
|
||||
volatile int *a = 0;
|
||||
*a = 5;
|
||||
a = (int*)0xDEADBEEF;
|
||||
*a = 5;
|
||||
}
|
||||
inline u64 rdtsc() {
|
||||
unsigned int lo, hi;
|
||||
|
@ -194,6 +200,8 @@ typedef struct Cpu_Capabilities {
|
|||
return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old);
|
||||
}
|
||||
|
||||
#define MEMORY_BARRIER __asm__ __volatile__("" ::: "memory")
|
||||
|
||||
#else
|
||||
#define inline inline
|
||||
#define COMPILER_HAS_MEMCPY_INTRINSICS 0
|
||||
|
@ -207,6 +215,8 @@ typedef struct Cpu_Capabilities {
|
|||
|
||||
#define deprecated(msg)
|
||||
|
||||
#define MEMORY_BARRIER
|
||||
|
||||
#warning "Compiler is not explicitly supported, some things will probably not work as expected"
|
||||
#endif
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -186,13 +186,13 @@ float4 sample_texture(int texture_index, int sampler_index, float2 uv) {
|
|||
float4 ps_main(PS_INPUT input) : SV_TARGET
|
||||
{
|
||||
if (input.type == QUAD_TYPE_REGULAR) {
|
||||
if (input.texture_index >= 0) {
|
||||
if (input.texture_index >= 0 && input.texture_index < 32 && input.sampler_index >= 0 && input.sampler_index <= 3) {
|
||||
return sample_texture(input.texture_index, input.sampler_index, input.uv)*input.color;
|
||||
} else {
|
||||
return input.color;
|
||||
}
|
||||
} else if (input.type == QUAD_TYPE_TEXT) {
|
||||
if (input.texture_index >= 0) {
|
||||
if (input.texture_index >= 0 && input.texture_index < 32 && input.sampler_index >= 0 && input.sampler_index <= 3) {
|
||||
float alpha = sample_texture(input.texture_index, input.sampler_index, input.uv).x;
|
||||
return float4(1.0, 1.0, 1.0, alpha)*input.color;
|
||||
} else {
|
||||
|
|
|
@ -117,13 +117,14 @@ Draw_Frame draw_frame = ZERO(Draw_Frame);
|
|||
void reset_draw_frame(Draw_Frame *frame) {
|
||||
*frame = (Draw_Frame){0};
|
||||
|
||||
frame->current = &first_block;
|
||||
frame->current->num_quads = 0;
|
||||
frame->current = 0;
|
||||
|
||||
float32 aspect = (float32)window.width/(float32)window.height;
|
||||
|
||||
frame->projection = m4_make_orthographic_projection(-aspect, aspect, -1, 1, -1, 10);
|
||||
frame->view = m4_scalar(1.0);
|
||||
|
||||
frame->num_blocks = 0;
|
||||
}
|
||||
|
||||
Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
|
||||
|
@ -139,10 +140,11 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
|
|||
draw_frame.current = &first_block;
|
||||
draw_frame.current->low_z = F32_MAX;
|
||||
draw_frame.current->high_z = F32_MIN;
|
||||
draw_frame.current->num_quads = 0;
|
||||
memset(draw_frame.current->quad_buffer, 0, sizeof(draw_frame.current->quad_buffer)); // #Temporary
|
||||
draw_frame.num_blocks = 1;
|
||||
}
|
||||
|
||||
if (draw_frame.current == &first_block) draw_frame.num_blocks = 1;
|
||||
|
||||
assert(draw_frame.current->num_quads <= QUADS_PER_BLOCK);
|
||||
|
||||
if (draw_frame.current->num_quads == QUADS_PER_BLOCK) {
|
||||
|
@ -154,11 +156,11 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
|
|||
|
||||
draw_frame.current = draw_frame.current->next;
|
||||
draw_frame.current->num_quads = 0;
|
||||
draw_frame.current->low_z = F32_MAX;
|
||||
draw_frame.current->high_z = F32_MIN;
|
||||
|
||||
draw_frame.num_blocks += 1;
|
||||
|
||||
draw_frame.current->low_z = F32_MAX;
|
||||
draw_frame.current->high_z = F32_MIN;
|
||||
}
|
||||
|
||||
draw_frame.current->quad_buffer[draw_frame.current->num_quads] = quad;
|
||||
|
@ -269,6 +271,14 @@ void draw_text(Gfx_Font *font, string text, u32 raster_height, Vector2 position,
|
|||
|
||||
draw_text_xform(font, text, raster_height, xform, scale, color);
|
||||
}
|
||||
Gfx_Text_Metrics draw_text_and_measure(Gfx_Font *font, string text, u32 raster_height, Vector2 position, Vector2 scale, Vector4 color) {
|
||||
Matrix4 xform = m4_scalar(1.0);
|
||||
xform = m4_translate(xform, v3(position.x, position.y, 0));
|
||||
|
||||
draw_text_xform(font, text, raster_height, xform, scale, color);
|
||||
|
||||
return measure_text(font, text, raster_height, scale);
|
||||
}
|
||||
|
||||
|
||||
#define COLOR_RED ((Vector4){1.0, 0.0, 0.0, 1.0})
|
||||
|
|
190
oogabooga/examples/custom_logger.c
Normal file
190
oogabooga/examples/custom_logger.c
Normal file
|
@ -0,0 +1,190 @@
|
|||
|
||||
/*
|
||||
|
||||
This is an example showcasing how we can make a custom logger to log both to stdout and
|
||||
and in-game logger.
|
||||
|
||||
We also have log levels to be able to disable/enable the respective levels.
|
||||
|
||||
*/
|
||||
|
||||
// start all log levels enabled
|
||||
bool log_level_enabled_flags[LOG_LEVEL_COUNT] = {1, 1, 1, 1};
|
||||
|
||||
// We delete old messages when this overflows
|
||||
#define MAX_LOG_MESSAGES 50
|
||||
typedef struct Log_Message {
|
||||
string message;
|
||||
Log_Level level;
|
||||
} Log_Message;
|
||||
Log_Message log_messages[MAX_LOG_MESSAGES];
|
||||
s64 num_log_messages = 0;
|
||||
|
||||
string get_log_level_name(Log_Level level) {
|
||||
switch (level) {
|
||||
case LOG_VERBOSE: return STR("Verbose");
|
||||
case LOG_INFO: return STR("Info");
|
||||
case LOG_WARNING: return STR("Warning");
|
||||
case LOG_ERROR: return STR("Error");
|
||||
default: return STR("");
|
||||
}
|
||||
}
|
||||
|
||||
void my_logger(Log_Level level, string s) {
|
||||
|
||||
|
||||
string prefix = STR("[INVALID LOG LEVEL]");
|
||||
if (level >= 0 && level < LOG_LEVEL_COUNT) {
|
||||
// if log level is disabled, we just leave
|
||||
if (!log_level_enabled_flags[level]) {
|
||||
return;
|
||||
}
|
||||
|
||||
prefix = tprint("[%s]", get_log_level_name(level));
|
||||
}
|
||||
|
||||
|
||||
// Format the final string.
|
||||
// Since we will be storing it in log_message, we need to use "sprint" rather than
|
||||
// "tprint" because it tprint uses the temp allocator which gets reset each frame.
|
||||
// In a real world scenario we would probably have a dedicated allocator for these
|
||||
// strings rather than heap allocating all of them.
|
||||
string message = sprint(get_heap_allocator(), "%s %s\n", prefix, s);
|
||||
|
||||
// Output the final string to stdout
|
||||
print(message);
|
||||
|
||||
|
||||
// Also add to in-game log messages
|
||||
Log_Message msg = (Log_Message){message, level};
|
||||
|
||||
if (num_log_messages < MAX_LOG_MESSAGES) {
|
||||
log_messages[num_log_messages] = msg;
|
||||
num_log_messages += 1;
|
||||
} else {
|
||||
// Shift memory down by one to make space for the next message and deleting the first.
|
||||
memcpy(log_messages, &log_messages[1], sizeof(log_messages)-sizeof(Log_Message));
|
||||
log_messages[num_log_messages-1] = msg;
|
||||
}
|
||||
}
|
||||
|
||||
#define FONT_HEIGHT 38
|
||||
Gfx_Font *font;
|
||||
|
||||
void draw_log(float x, float y);
|
||||
|
||||
int entry(int argc, char **argv) {
|
||||
|
||||
// Window setup
|
||||
window.title = STR("Minimal Game Example");
|
||||
window.scaled_width = 1280;
|
||||
window.scaled_height = 720;
|
||||
window.x = 200;
|
||||
window.y = 200;
|
||||
window.clear_color = hex_to_rgba(0x6495EDff);
|
||||
|
||||
// Load a font to draw the logs with
|
||||
font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator());
|
||||
assert(font, "Failed loading arial.ttf, %d", GetLastError());
|
||||
|
||||
// This is where we set the logger we want all logs to go through
|
||||
context.logger = my_logger;
|
||||
|
||||
while (!window.should_close) {
|
||||
reset_temporary_storage();
|
||||
|
||||
os_update();
|
||||
gfx_update();
|
||||
|
||||
// pixel-aligned projection
|
||||
draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10);
|
||||
|
||||
float x = -window.width/2+60;
|
||||
float y = window.height/2-FONT_HEIGHT/2-30;
|
||||
|
||||
draw_text(font, STR("Left-click to toggle, right-click to send log message"), FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK);
|
||||
draw_text(font, STR("Left-click to toggle, right-click to send log message"), FONT_HEIGHT, v2(x, y), v2(1, 1), COLOR_WHITE);
|
||||
|
||||
y -= FONT_HEIGHT*1.3;
|
||||
|
||||
// Loop through all levels to draw their state and act on input
|
||||
for (Log_Level level = 0; level < LOG_LEVEL_COUNT; level += 1) {
|
||||
bool enabled = log_level_enabled_flags[level];
|
||||
string s = tprint("%s: %s", get_log_level_name(level), enabled ? STR("on") : STR("off"));
|
||||
Gfx_Text_Metrics m = measure_text(font, s, FONT_HEIGHT, v2(1, 1));
|
||||
|
||||
Vector4 color = COLOR_WHITE;
|
||||
|
||||
Vector2 bottom_left = v2_sub(v2(x, y), m.functional_pos_min);
|
||||
|
||||
float L = bottom_left.x;
|
||||
float R = L + m.visual_size.x;
|
||||
float B = bottom_left.y;
|
||||
float T = B + m.visual_size.y;
|
||||
|
||||
float mx = input_frame.mouse_x - window.width/2;
|
||||
float my = input_frame.mouse_y - window.height/2;
|
||||
|
||||
bool hovered = mx >= L && mx < R && my >= B && my < T;
|
||||
if (hovered) color = v4(.8, .8, .8, 1.0);
|
||||
if (hovered && (is_key_down(MOUSE_BUTTON_LEFT) || is_key_down(MOUSE_BUTTON_RIGHT)))
|
||||
color = v4(.6, .6, .6, 1.0);
|
||||
|
||||
if (hovered && is_key_just_released(MOUSE_BUTTON_LEFT))
|
||||
log_level_enabled_flags[level] = !log_level_enabled_flags[level];
|
||||
if (hovered && is_key_just_released(MOUSE_BUTTON_RIGHT)) {
|
||||
if (level == LOG_VERBOSE) log_verbose("This is a log message");
|
||||
if (level == LOG_INFO) log_info("This is a log message");
|
||||
if (level == LOG_WARNING) log_warning("This is a log message");
|
||||
if (level == LOG_ERROR) log_error("This is a log message");
|
||||
}
|
||||
|
||||
draw_rect(v2_sub(bottom_left, v2(8, 8)), v2_add(m.functional_size, v2(16, 16)), v4_mul(v4(.3, .3, .3, 1), color));
|
||||
draw_text(font, s, FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK);
|
||||
draw_text(font, s, FONT_HEIGHT, v2(x, y), v2(1, 1), color);
|
||||
|
||||
y -= FONT_HEIGHT*1.3;
|
||||
}
|
||||
|
||||
y -= FONT_HEIGHT*1.3;
|
||||
|
||||
draw_log(x, y);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void draw_log(float x, float y) {
|
||||
|
||||
|
||||
draw_text(font, STR("In-game log:"), FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK);
|
||||
Gfx_Text_Metrics m = draw_text_and_measure(font, STR("In-game log:"), FONT_HEIGHT, v2(x, y), v2(1, 1), COLOR_WHITE);
|
||||
|
||||
y -= m.functional_size.y+20;
|
||||
|
||||
// Here we draw each entry in the log that we can fit on screen, starting from the top
|
||||
// so we see the last log message first.
|
||||
for (s64 i = num_log_messages-1; i >= 0; i--) {
|
||||
|
||||
Log_Level level = log_messages[i].level;
|
||||
|
||||
if (level >= 0 && level < LOG_LEVEL_COUNT && !log_level_enabled_flags[level]) {
|
||||
// If it's disabled, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set color reflecting log level
|
||||
Vector4 color = COLOR_WHITE;
|
||||
if (level == LOG_VERBOSE) color = v4(.6, .6, 1, 1);
|
||||
else if (level == LOG_INFO) color = v4(.3, 1, .4, 1);
|
||||
else if (level == LOG_WARNING) color = v4(.8, .8, 1, 1);
|
||||
else if (level == LOG_ERROR) color = v4(1, .2, .2, 1);
|
||||
|
||||
draw_text(font, log_messages[i].message, FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK);
|
||||
draw_text(font, log_messages[i].message, FONT_HEIGHT, v2(x, y), v2(1, 1), color);
|
||||
|
||||
y -= FONT_HEIGHT * 1.3;
|
||||
|
||||
if (y+FONT_HEIGHT < -window.height/2) break; // Occlude text outside of view
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ int entry(int argc, char **argv) {
|
|||
Matrix4 rect_xform = m4_scalar(1.0);
|
||||
rect_xform = m4_rotate_z(rect_xform, (f32)now);
|
||||
rect_xform = m4_translate(rect_xform, v3(-.25f, -.25f, 0));
|
||||
draw_rect_xform(rect_xform, v2(.5f, .5f), COLOR_RED);
|
||||
draw_rect_xform(rect_xform, v2(.5f, .5f), COLOR_GREEN);
|
||||
|
||||
gfx_update();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ int entry(int argc, char **argv) {
|
|||
// as it was rasterized at with no down- or up-scaling.
|
||||
// It's fairly common in video games to render the UI with a separate projection for this
|
||||
// very reason.
|
||||
draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.height * -0.5, window.height * 0.5, -1, 10);
|
||||
draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10);
|
||||
|
||||
// Easy drop shadow: Just draw the same text underneath with a slight offset
|
||||
draw_text(font, STR("I am text"), font_height, v2(-2, 2), v2(1, 1), COLOR_BLACK);
|
||||
|
|
|
@ -534,8 +534,6 @@ void d3d11_draw_call(int number_of_rendered_quads, ID3D11ShaderResourceView **te
|
|||
VTABLE(PSSetSamplers, d3d11_context, 3, 1, &d3d11_image_sampler_nl_fp);
|
||||
VTABLE(PSSetShaderResources, d3d11_context, 0, num_textures, textures);
|
||||
|
||||
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
|
||||
|
||||
VTABLE(Draw, d3d11_context, number_of_rendered_quads * 6, 0);
|
||||
}
|
||||
|
||||
|
@ -543,6 +541,8 @@ void d3d11_process_draw_frame() {
|
|||
|
||||
HRESULT hr;
|
||||
|
||||
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
|
||||
|
||||
///
|
||||
// Maybe grow quad vbo
|
||||
u32 required_size = sizeof(D3D11_Vertex) * draw_frame.num_blocks*QUADS_PER_BLOCK*6;
|
||||
|
@ -566,6 +566,7 @@ void d3d11_process_draw_frame() {
|
|||
|
||||
log_verbose("Grew quad vbo to %d bytes.", d3d11_quad_vbo_size);
|
||||
}
|
||||
memset(d3d11_staging_quad_buffer, 0, d3d11_quad_vbo_size);// #Temporary
|
||||
|
||||
if (draw_frame.num_blocks > 0) {
|
||||
///
|
||||
|
@ -583,7 +584,8 @@ void d3d11_process_draw_frame() {
|
|||
Draw_Quad_Block *block = &first_block;
|
||||
|
||||
tm_scope_cycles("Quad processing") {
|
||||
while (block != 0 && block->num_quads > 0) tm_scope_cycles("Quad block") {
|
||||
u64 block_index = 0;
|
||||
while (block != 0 && block->num_quads > 0 && block_index < draw_frame.num_blocks) tm_scope_cycles("Quad block") {
|
||||
for (u64 i = 0; i < block->num_quads; i++) {
|
||||
|
||||
Draw_Quad *q = &block->quad_buffer[i];
|
||||
|
@ -667,7 +669,7 @@ void d3d11_process_draw_frame() {
|
|||
BL->texture_index=TL->texture_index=TR->texture_index=BR->texture_index = texture_index;
|
||||
BL->type=TL->type=TR->type=BR->type = (u8)q->type;
|
||||
|
||||
u8 sampler = 0;
|
||||
u8 sampler = -1;
|
||||
if (q->image_min_filter == GFX_FILTER_MODE_NEAREST
|
||||
&& q->image_mag_filter == GFX_FILTER_MODE_NEAREST)
|
||||
sampler = 0;
|
||||
|
@ -691,7 +693,7 @@ void d3d11_process_draw_frame() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
block_index += 1;
|
||||
block = block->next;
|
||||
}
|
||||
}
|
||||
|
@ -721,7 +723,7 @@ void d3d11_process_draw_frame() {
|
|||
void gfx_update() {
|
||||
if (window.should_close) return;
|
||||
|
||||
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
|
||||
|
||||
|
||||
HRESULT hr;
|
||||
///
|
||||
|
@ -739,6 +741,7 @@ void gfx_update() {
|
|||
|
||||
VTABLE(Present, d3d11_swap_chain, window.enable_vsync, window.enable_vsync ? 0 : DXGI_PRESENT_ALLOW_TEARING);
|
||||
|
||||
|
||||
#if CONFIGURATION == DEBUG
|
||||
///
|
||||
// Check debug messages, output to stdout
|
||||
|
|
|
@ -85,7 +85,7 @@ typedef struct {
|
|||
|
||||
Heap_Block *heap_head;
|
||||
bool heap_initted = false;
|
||||
Spinlock *heap_lock; // This is terrible but I don't care for now
|
||||
Spinlock heap_lock; // This is terrible but I don't care for now
|
||||
|
||||
|
||||
u64 get_heap_block_size_excluding_metadata(Heap_Block *block) {
|
||||
|
@ -234,7 +234,7 @@ void heap_init() {
|
|||
assert(sizeof(Heap_Allocation_Metadata) % HEAP_ALIGNMENT == 0);
|
||||
heap_initted = true;
|
||||
heap_head = make_heap_block(0, DEFAULT_HEAP_BLOCK_SIZE);
|
||||
heap_lock = os_make_spinlock(get_initialization_allocator());
|
||||
spinlock_init(&heap_lock);
|
||||
}
|
||||
|
||||
|
||||
|
@ -244,7 +244,9 @@ void *heap_alloc(u64 size) {
|
|||
if (!heap_initted) heap_init();
|
||||
|
||||
// #Sync #Speed oof
|
||||
os_spinlock_lock(heap_lock);
|
||||
spinlock_acquire_or_wait(&heap_lock);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -344,7 +346,7 @@ void *heap_alloc(u64 size) {
|
|||
#endif
|
||||
|
||||
// #Sync #Speed oof
|
||||
os_spinlock_unlock(heap_lock);
|
||||
spinlock_release(&heap_lock);
|
||||
|
||||
|
||||
void *p = ((u8*)meta)+sizeof(Heap_Allocation_Metadata);
|
||||
|
@ -356,7 +358,7 @@ void heap_dealloc(void *p) {
|
|||
|
||||
if (!heap_initted) heap_init();
|
||||
|
||||
os_spinlock_lock(heap_lock);
|
||||
spinlock_acquire_or_wait(&heap_lock);
|
||||
|
||||
assert(is_pointer_in_program_memory(p), "A bad pointer was passed tp heap_dealloc: it is out of program memory bounds!");
|
||||
p = (u8*)p-sizeof(Heap_Allocation_Metadata);
|
||||
|
@ -428,7 +430,7 @@ void heap_dealloc(void *p) {
|
|||
|
||||
|
||||
// #Sync #Speed oof
|
||||
os_spinlock_unlock(heap_lock);
|
||||
spinlock_release(&heap_lock);
|
||||
}
|
||||
|
||||
void* heap_allocator_proc(u64 size, void *p, Allocator_Message message, void* data) {
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
|
||||
#define OGB_VERSION_MAJOR 0
|
||||
#define OGB_VERSION_MINOR 0
|
||||
#define OGB_VERSION_PATCH 3
|
||||
#define OGB_VERSION_PATCH 4
|
||||
|
||||
#define OGB_VERSION (OGB_VERSION_MAJOR*1000000+OGB_VERSION_MINOR*1000+OGB_VERSION_PATCH)
|
||||
|
||||
|
@ -265,11 +265,14 @@ typedef u8 bool;
|
|||
#include "hash.c"
|
||||
#include "path_utils.c"
|
||||
#include "linmath.c"
|
||||
#include "range.c"
|
||||
|
||||
#include "hash_table.c"
|
||||
|
||||
#include "os_interface.c"
|
||||
#include "concurrency.c"
|
||||
#include "gfx_interface.c"
|
||||
|
||||
#include "font.c"
|
||||
|
||||
#include "profiling.c"
|
||||
|
@ -317,6 +320,7 @@ void oogabooga_init(u64 program_memory_size) {
|
|||
os_init(program_memory_size);
|
||||
heap_init();
|
||||
temporary_storage_init();
|
||||
log_info("Ooga booga version is %d.%02d.%03d", OGB_VERSION_MAJOR, OGB_VERSION_MINOR, OGB_VERSION_PATCH);
|
||||
gfx_init();
|
||||
log_verbose("CPU has sse1: %cs", features.sse1 ? "true" : "false");
|
||||
log_verbose("CPU has sse2: %cs", features.sse2 ? "true" : "false");
|
||||
|
@ -335,7 +339,7 @@ int main(int argc, char **argv) {
|
|||
|
||||
print("Ooga booga program started\n");
|
||||
oogabooga_init(INITIAL_PROGRAM_MEMORY_SIZE);
|
||||
log_info("Ooga booga version is %d.%02d.%03d", OGB_VERSION_MAJOR, OGB_VERSION_MINOR, OGB_VERSION_PATCH);
|
||||
|
||||
|
||||
assert(main != ENTRY_PROC, "You've ooga'd your last booga");
|
||||
|
||||
|
|
|
@ -135,6 +135,8 @@ LRESULT CALLBACK win32_window_proc(HWND passed_window, UINT message, WPARAM wpar
|
|||
|
||||
void os_init(u64 program_memory_size) {
|
||||
|
||||
context.thread_id = GetCurrentThreadId();
|
||||
|
||||
memset(&window, 0, sizeof(window));
|
||||
|
||||
timeBeginPeriod(1);
|
||||
|
@ -370,6 +372,7 @@ DWORD WINAPI win32_thread_invoker(LPVOID param) {
|
|||
Thread *t = (Thread*)param;
|
||||
temporary_storage_init();
|
||||
context = t->initial_context;
|
||||
context.thread_id = GetCurrentThreadId();
|
||||
t->proc(t);
|
||||
return 0;
|
||||
}
|
||||
|
@ -379,9 +382,15 @@ Thread* os_make_thread(Thread_Proc proc, Allocator allocator) {
|
|||
t->id = 0; // This is set when we start it
|
||||
t->proc = proc;
|
||||
t->initial_context = context;
|
||||
t->allocator = allocator;
|
||||
|
||||
return t;
|
||||
}
|
||||
void os_destroy_thread(Thread *t) {
|
||||
os_join_thread(t);
|
||||
CloseHandle(t->os_handle);
|
||||
dealloc(t->allocator, t);
|
||||
}
|
||||
void os_start_thread(Thread *t) {
|
||||
t->os_handle = CreateThread(
|
||||
0,
|
||||
|
@ -396,14 +405,24 @@ void os_start_thread(Thread *t) {
|
|||
}
|
||||
void os_join_thread(Thread *t) {
|
||||
WaitForSingleObject(t->os_handle, INFINITE);
|
||||
CloseHandle(t->os_handle);
|
||||
}
|
||||
|
||||
///
|
||||
// Mutex primitive
|
||||
|
||||
Mutex_Handle os_make_mutex() {
|
||||
return CreateMutex(0, FALSE, 0);
|
||||
local_persist const int MAX_ATTEMPTS = 100;
|
||||
|
||||
HANDLE m = CreateMutexW(0, FALSE, 0);
|
||||
|
||||
int attempts = 1;
|
||||
while (m == 0) {
|
||||
assert(attempts <= MAX_ATTEMPTS, "Failed creating win32 mutex. error %d", GetLastError());
|
||||
m = CreateMutex(0, FALSE, 0);
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
void os_destroy_mutex(Mutex_Handle m) {
|
||||
CloseHandle(m);
|
||||
|
@ -415,8 +434,8 @@ void os_lock_mutex(Mutex_Handle m) {
|
|||
case WAIT_OBJECT_0:
|
||||
break;
|
||||
|
||||
case WAIT_ABANDONED:
|
||||
break;
|
||||
//case WAIT_ABANDONED:
|
||||
// break;
|
||||
|
||||
default:
|
||||
assert(false, "Unexpected mutex lock result");
|
||||
|
@ -425,7 +444,7 @@ void os_lock_mutex(Mutex_Handle m) {
|
|||
}
|
||||
void os_unlock_mutex(Mutex_Handle m) {
|
||||
BOOL result = ReleaseMutex(m);
|
||||
assert(result, "Unlock mutex failed");
|
||||
assert(result, "Unlock mutex 0x%x failed with error %d", m, GetLastError());
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
@ -94,32 +94,30 @@ typedef struct Thread {
|
|||
void* data;
|
||||
Thread_Proc proc;
|
||||
Thread_Handle os_handle;
|
||||
|
||||
Allocator allocator;
|
||||
} Thread;
|
||||
|
||||
///
|
||||
// Thread primitive
|
||||
Thread* os_make_thread(Thread_Proc proc, Allocator allocator);
|
||||
void os_destroy_thread(Thread *t);
|
||||
void os_start_thread(Thread* t);
|
||||
void os_join_thread(Thread* t);
|
||||
|
||||
|
||||
|
||||
///
|
||||
// Mutex primitive
|
||||
// Low-level Mutex primitive. Mutex in concurrency.c is probably a better alternative.
|
||||
Mutex_Handle os_make_mutex();
|
||||
void os_destroy_mutex(Mutex_Handle m);
|
||||
void os_lock_mutex(Mutex_Handle m);
|
||||
void os_unlock_mutex(Mutex_Handle m);
|
||||
|
||||
///
|
||||
// Spinlock "primitive"
|
||||
typedef struct Spinlock {
|
||||
bool locked;
|
||||
} Spinlock;
|
||||
Spinlock *os_make_spinlock(Allocator allocator);
|
||||
void os_spinlock_lock(Spinlock* l);
|
||||
void os_spinlock_unlock(Spinlock* l);
|
||||
typedef struct Spinlock Spinlock;
|
||||
// #Cleanup Moved to threading.c
|
||||
DEPRECATED(Spinlock *os_make_spinlock(Allocator allocator), "use spinlock_init instead");
|
||||
DEPRECATED(void os_spinlock_lock(Spinlock* l), "use spinlock_acquire_or_wait instead");
|
||||
DEPRECATED(void os_spinlock_unlock(Spinlock* l), "use spinlock_release instead");
|
||||
|
||||
///
|
||||
// Concurrency utilities
|
||||
|
@ -130,6 +128,7 @@ void os_spinlock_unlock(Spinlock* l);
|
|||
// instruction anyways, so may as well just inline asm it (or Win32
|
||||
// if we're compiling with msvc) (LDREX/STREX on ARM)
|
||||
// - CharlieM July 8th 2024
|
||||
// compare_and_swap in cpu.c
|
||||
DEPRECATED(bool os_compare_and_swap_8 (u8 *a, u8 b, u8 old), "use compare_and_swap instead");
|
||||
DEPRECATED(bool os_compare_and_swap_16 (u16 *a, u16 b, u16 old), "use compare_and_swap instead");
|
||||
DEPRECATED(bool os_compare_and_swap_32 (u32 *a, u32 b, u32 old), "use compare_and_swap instead");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
String_Builder _profile_output = {0};
|
||||
bool profiler_initted = false;
|
||||
Spinlock *_profiler_lock = 0;
|
||||
Spinlock _profiler_lock;
|
||||
void dump_profile_result() {
|
||||
File file = os_file_open("google_trace.json", O_CREATE | O_WRITE);
|
||||
|
||||
|
@ -14,19 +14,19 @@ void dump_profile_result() {
|
|||
}
|
||||
void _profiler_report_time_cycles(string name, u64 count, u64 start) {
|
||||
if (!profiler_initted) {
|
||||
_profiler_lock = os_make_spinlock(get_heap_allocator());
|
||||
spinlock_init(&_profiler_lock);
|
||||
profiler_initted = true;
|
||||
|
||||
string_builder_init_reserve(&_profile_output, 1024*1000, get_heap_allocator());
|
||||
|
||||
}
|
||||
|
||||
os_spinlock_lock(_profiler_lock);
|
||||
spinlock_acquire_or_wait(&_profiler_lock);
|
||||
|
||||
string fmt = STR("{\"cat\":\"function\",\"dur\":%.3f,\"name\":\"%s\",\"ph\":\"X\",\"pid\":0,\"tid\":%zu,\"ts\":%lld},");
|
||||
string_builder_print(&_profile_output, fmt, (float64)count*1000, name, GetCurrentThreadId(), start*1000);
|
||||
|
||||
os_spinlock_unlock(_profiler_lock);
|
||||
spinlock_release(&_profiler_lock);
|
||||
}
|
||||
#if ENABLE_PROFILING
|
||||
#define tm_scope_cycles(name) \
|
||||
|
|
42
oogabooga/range.c
Normal file
42
oogabooga/range.c
Normal file
|
@ -0,0 +1,42 @@
|
|||
|
||||
// randy: we might wanna remove the f by default, similar to the Vector2 ?
|
||||
// I know that we'll have a Range2i at some point, so maybe it's better to be explicit for less confusion?
|
||||
// I'll leave this decision up to u charlie just delete this whenever u see it
|
||||
|
||||
typedef struct Range1f {
|
||||
float min;
|
||||
float max;
|
||||
} Range1f;
|
||||
// ...
|
||||
|
||||
typedef struct Range2f {
|
||||
Vector2 min;
|
||||
Vector2 max;
|
||||
} Range2f;
|
||||
|
||||
inline Range2f range2f_make(Vector2 min, Vector2 max) { return (Range2f) { min, max }; }
|
||||
|
||||
Range2f range2f_shift(Range2f r, Vector2 shift) {
|
||||
r.min = v2_add(r.min, shift);
|
||||
r.max = v2_add(r.max, shift);
|
||||
return r;
|
||||
}
|
||||
|
||||
Range2f range2f_make_bottom_center(Vector2 size) {
|
||||
Range2f range = {0};
|
||||
range.max = size;
|
||||
range = range2f_shift(range, v2(size.x * -0.5, 0.0));
|
||||
return range;
|
||||
}
|
||||
|
||||
Vector2 range2f_size(Range2f range) {
|
||||
Vector2 size = {0};
|
||||
size = v2_sub(range.min, range.max);
|
||||
size.x = fabsf(size.x);
|
||||
size.y = fabsf(size.y);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool range2f_contains(Range2f range, Vector2 v) {
|
||||
return v.x >= range.min.x && v.x <= range.max.x && v.y >= range.min.y && v.y <= range.max.y;
|
||||
}
|
|
@ -229,6 +229,7 @@ void default_logger(Log_Level level, string s) {
|
|||
case LOG_INFO: print("[INFO]: %s\n", s); break;
|
||||
case LOG_WARNING: print("[WARNING]: %s\n", s); break;
|
||||
case LOG_ERROR: print("[ERROR]: %s\n", s); break;
|
||||
case LOG_LEVEL_COUNT: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
///
|
||||
|
||||
void log_heap() {
|
||||
os_spinlock_lock(heap_lock);
|
||||
spinlock_acquire_or_wait(&heap_lock);
|
||||
print("\nHEAP:\n");
|
||||
|
||||
Heap_Block *block = heap_head;
|
||||
|
@ -31,7 +31,7 @@ void log_heap() {
|
|||
|
||||
block = block->next;
|
||||
}
|
||||
os_spinlock_unlock(heap_lock);
|
||||
spinlock_release(&heap_lock);
|
||||
}
|
||||
|
||||
void test_allocator(bool do_log_heap) {
|
||||
|
@ -1080,6 +1080,67 @@ void test_random_distribution() {
|
|||
|
||||
print("Min: %d, max: %d\n", min_bin, max_bin);
|
||||
}
|
||||
|
||||
#define MUTEX_TEST_TASK_COUNT 1000
|
||||
typedef struct Mutex_Test_Shared_Data {
|
||||
int counter;
|
||||
bool any_active_thread;
|
||||
Mutex mutex;
|
||||
} Mutex_Test_Shared_Data;
|
||||
void mutex_test_increment_counter(Thread* t) {
|
||||
Mutex_Test_Shared_Data* data = (Mutex_Test_Shared_Data*)t->data;
|
||||
for (int i = 0; i < MUTEX_TEST_TASK_COUNT; i++) {
|
||||
mutex_acquire_or_wait(&data->mutex);
|
||||
assert(!data->any_active_thread, "Failed: More than one thread is in critical section!");
|
||||
data->any_active_thread = true;
|
||||
data->counter++;
|
||||
data->any_active_thread = false;
|
||||
mutex_release(&data->mutex);
|
||||
}
|
||||
}
|
||||
void test_mutex() {
|
||||
Mutex m;
|
||||
|
||||
// Test initialization
|
||||
mutex_init(&m);
|
||||
assert(m.spin_time_microseconds == MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS, "Failed: Default spin time incorrect");
|
||||
assert(!m.spinlock_acquired, "Failed: Spinlock should not be acquired after initialization");
|
||||
|
||||
// Test acquire and release without contention
|
||||
mutex_acquire_or_wait(&m);
|
||||
assert(m.spinlock_acquired, "Failed: Mutex should be acquired after mutex_acquire_or_wait");
|
||||
|
||||
mutex_release(&m);
|
||||
assert(!m.spinlock_acquired, "Failed: Spinlock should not be acquired after mutex_release");
|
||||
|
||||
// Clean up
|
||||
mutex_destroy(&m);
|
||||
|
||||
Mutex_Test_Shared_Data data;
|
||||
data.counter = 0;
|
||||
data.any_active_thread = false;
|
||||
mutex_init(&data.mutex);
|
||||
|
||||
Allocator allocator = get_heap_allocator();
|
||||
|
||||
const int num_threads = 100;
|
||||
|
||||
Thread **threads = alloc(allocator, sizeof(Thread*)*num_threads);
|
||||
for (u64 i = 0; i < num_threads; i++) {
|
||||
threads[i] = os_make_thread(mutex_test_increment_counter, allocator);
|
||||
threads[i]->data = &data;
|
||||
}
|
||||
for (u64 i = 0; i < num_threads; i++) {
|
||||
os_start_thread(threads[i]);
|
||||
}
|
||||
for (u64 i = 0; i < num_threads; i++) {
|
||||
os_join_thread(threads[i]);
|
||||
}
|
||||
|
||||
assert(data.counter == num_threads * MUTEX_TEST_TASK_COUNT, "Failed: Counter does not match expected value after threading tasks");
|
||||
|
||||
mutex_destroy(&data.mutex);
|
||||
}
|
||||
void oogabooga_run_tests() {
|
||||
|
||||
|
||||
|
@ -1095,20 +1156,6 @@ void oogabooga_run_tests() {
|
|||
test_strings();
|
||||
print("OK!\n");
|
||||
|
||||
|
||||
#if CONFIGURATION != RELEASE
|
||||
print("Thread bombing allocator... ");
|
||||
Thread* threads[300];
|
||||
for (int i = 0; i < 300; i++) {
|
||||
threads[i] = os_make_thread(test_allocator_threaded, get_heap_allocator());
|
||||
os_start_thread(threads[i]);
|
||||
}
|
||||
for (int i = 0; i < 300; i++) {
|
||||
os_join_thread(threads[i]);
|
||||
}
|
||||
print("OK!\n");
|
||||
#endif
|
||||
|
||||
print("Testing file IO... ");
|
||||
test_file_io();
|
||||
print("OK!\n");
|
||||
|
@ -1129,4 +1176,9 @@ void oogabooga_run_tests() {
|
|||
test_random_distribution();
|
||||
print("OK!\n");
|
||||
|
||||
print("Testing mutex... ");
|
||||
test_mutex();
|
||||
print("OK!\n");
|
||||
|
||||
|
||||
}
|
Reference in a new issue