From 511364ee051d59abdfa2ff58d3d7eb34090ce546 Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 15 Apr 2024 18:02:38 -0500 Subject: [PATCH] app exit / shutdown callbacks --- src/app.c | 65 ++++++++++--- src/app.h | 8 +- src/game.c | 81 +++++++++------- src/game.h | 1 - src/playback.h | 1 - src/playback_wasapi.c | 56 ++++++----- src/sys.h | 21 +++-- src/sys_win32.c | 213 ++++++++++++++++++++++++++++-------------- src/user.c | 121 +++++++++++++----------- src/user.h | 1 - src/work.c | 25 +++-- src/work.h | 1 - 12 files changed, 367 insertions(+), 227 deletions(-) diff --git a/src/app.c b/src/app.c index 24c13a82..77fa1612 100644 --- a/src/app.c +++ b/src/app.c @@ -21,10 +21,21 @@ #include "draw.h" #include "math.h" +struct exit_callback { + app_exit_callback_func *func; + struct exit_callback *next; + struct sys_thread thread; +}; + GLOBAL struct { struct arena arena; struct string write_path; - struct sync_flag quit_sf; + struct sync_flag exit_sf; + + /* Exit callbacks */ + struct sys_mutex exit_callbacks_mutex; + struct arena exit_callbacks_arena; + struct exit_callback *exit_callbacks_head; } G = { 0 }, DEBUG_ALIAS(G, G_app); /* ========================== * @@ -85,13 +96,37 @@ INTERNAL struct sys_window_settings default_window_settings(struct sys_window *w }; } +/* ========================== * + * Exit callbacks + * ========================== */ + +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(exit_callback_thread_entry_point, vcallback) +{ + struct exit_callback *callback = (struct exit_callback *)vcallback; + callback->func(); +} + +void app_register_exit_callback(app_exit_callback_func *func) +{ + sys_mutex_lock(&G.exit_callbacks_mutex); + { + struct exit_callback *callback = arena_push_zero(&G.exit_callbacks_arena, struct exit_callback); + callback->func = func; + callback->next = G.exit_callbacks_head; + G.exit_callbacks_head = callback; + } + sys_mutex_unlock(&G.exit_callbacks_mutex); +} + /* ========================== * * Entry point * ========================== */ void app_entry_point(void) { - G.quit_sf = sync_flag_alloc(); + G.exit_sf = sync_flag_alloc(); + G.exit_callbacks_mutex = sys_mutex_alloc(); + G.exit_callbacks_arena = arena_alloc(GIGABYTE(64)); u32 worker_count = 4; { @@ -189,17 +224,25 @@ void app_entry_point(void) /* Show window */ sys_window_show(&window); - /* Wait for app_quit() */ - sync_flag_wait(&G.quit_sf); + /* Wait for app_exit() */ + sync_flag_wait(&G.exit_sf); - /* Shutdown threaded systems */ + /* Call exit callbacks */ /* FIXME: Only wait on threads for a certain period of time before * forcing process exit (to prevent process hanging in the background * when a thread gets stuck) */ - playback_shutdown(); - user_shutdown(); - game_shutdown(); - work_shutdown(); + sys_mutex_lock(&G.exit_callbacks_mutex); + { + /* Start callback threads */ + for (struct exit_callback *callback = G.exit_callbacks_head; callback; callback = callback->next) { + callback->thread = sys_thread_init(&exit_callback_thread_entry_point, callback, STR("[P4] Exit callback thread")); + } + /* Wait on callback threads */ + for (struct exit_callback *callback = G.exit_callbacks_head; callback; callback = callback->next) { + sys_thread_join(&callback->thread); + } + } + sys_mutex_unlock(&G.exit_callbacks_mutex); /* Write window settings to file */ { @@ -224,7 +267,7 @@ void app_entry_point(void) logf_info("Program exited normally"); } -void app_quit(void) +void app_exit(void) { - sync_flag_set(&G.quit_sf); + sync_flag_set(&G.exit_sf); } diff --git a/src/app.h b/src/app.h index 77a0e50a..64dec18c 100644 --- a/src/app.h +++ b/src/app.h @@ -1,9 +1,13 @@ #ifndef APP_H #define APP_H -struct string app_write_path_cat(struct arena *arena, struct string filename); +#define APP_EXIT_CALLBACK_FUNC_DEF(name) void name(void) +typedef APP_EXIT_CALLBACK_FUNC_DEF(app_exit_callback_func); +struct string app_write_path_cat(struct arena *arena, struct string filename); +/* Register a function that will be called when the application exits */ +void app_register_exit_callback(app_exit_callback_func *func); void app_entry_point(void); -void app_quit(void); +void app_exit(void); #endif diff --git a/src/game.c b/src/game.c index 110db521..d9aa828f 100644 --- a/src/game.c +++ b/src/game.c @@ -8,9 +8,10 @@ #include "math.h" #include "scratch.h" #include "atomic.h" +#include "app.h" GLOBAL struct { - b32 shutdown; + struct atomic_i32 game_thread_shutdown; struct sys_thread game_thread; struct world world; @@ -25,6 +26,46 @@ GLOBAL struct { struct atomic_u64 published_tick_id; } G = { 0 }, DEBUG_ALIAS(G, G_game); +/* ========================== * + * Startup + * ========================== */ + +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(game_shutdown); +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg); + +struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr, + struct sheet_startup_receipt *sheet_sr, + struct sound_startup_receipt *sound_sr) +{ + (UNUSED)mixer_sr; + (UNUSED)sheet_sr; + (UNUSED)sound_sr; + + /* Initialize game cmd storage */ + G.game_cmds_mutex = sys_mutex_alloc(); + G.game_cmds_arena = arena_alloc(GIGABYTE(64)); + + /* Initialize world */ + world_alloc(&G.world); + + /* Initialize tick transmission */ + world_alloc(&G.published_tick); + G.published_tick_mutex = sys_mutex_alloc(); + + G.world.timescale = GAME_TIMESCALE; + G.game_thread = sys_thread_init(&game_thread_entry_point, NULL, STR("[P2] Game thread")); + app_register_exit_callback(&game_shutdown); + + return (struct game_startup_receipt) { 0 }; +} + +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(game_shutdown) +{ + __prof; + atomic_i32_eval_exchange(&G.game_thread_shutdown, true); + sys_thread_join(&G.game_thread); +} + /* ========================== * * Game cmd * ========================== */ @@ -542,15 +583,15 @@ INTERNAL void game_update(void) } /* ========================== * - * Startup + * Game thread * ========================== */ -INTERNAL SYS_THREAD_FUNC_DEF(game_thread_entry_point, arg) +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg) { (UNUSED)arg; sys_timestamp_t last_frame_ts = 0; f64 target_dt = GAME_FPS > (0) ? (1.0 / GAME_FPS) : 0; - while (!G.shutdown) { + while (!atomic_i32_eval(&G.game_thread_shutdown)) { __profscope(game_update_w_sleep); sleep_frame(last_frame_ts, target_dt); last_frame_ts = sys_timestamp(); @@ -558,38 +599,6 @@ INTERNAL SYS_THREAD_FUNC_DEF(game_thread_entry_point, arg) } } -struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr, - struct sheet_startup_receipt *sheet_sr, - struct sound_startup_receipt *sound_sr) -{ - (UNUSED)mixer_sr; - (UNUSED)sheet_sr; - (UNUSED)sound_sr; - - /* Initialize game cmd storage */ - G.game_cmds_mutex = sys_mutex_alloc(); - G.game_cmds_arena = arena_alloc(GIGABYTE(64)); - - /* Initialize world */ - world_alloc(&G.world); - - /* Initialize tick transmission */ - world_alloc(&G.published_tick); - G.published_tick_mutex = sys_mutex_alloc(); - - - G.world.timescale = GAME_TIMESCALE; - G.game_thread = sys_thread_init(&game_thread_entry_point, NULL, STR("[P2] Game thread")); - - return (struct game_startup_receipt) { 0 }; -} - -void game_shutdown(void) -{ - G.shutdown = true; - sys_thread_join(&G.game_thread); -} - /* ========================== * * Interface * ========================== */ diff --git a/src/game.h b/src/game.h index 3a9cbace..bb642afc 100644 --- a/src/game.h +++ b/src/game.h @@ -34,7 +34,6 @@ struct game_startup_receipt { i32 _; }; struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr, struct sheet_startup_receipt *sheet_sr, struct sound_startup_receipt *sound_sr); -void game_shutdown(void); void game_get_latest_tick(struct world *dest); u64 game_get_latest_tick_id(void); diff --git a/src/playback.h b/src/playback.h index 7961a31f..53e19ef9 100644 --- a/src/playback.h +++ b/src/playback.h @@ -7,6 +7,5 @@ struct mixer_startup_receipt; struct playback_startup_receipt { i32 _; }; struct playback_startup_receipt playback_startup(struct mixer_startup_receipt *mixer_sr); -void playback_shutdown(void); #endif diff --git a/src/playback_wasapi.c b/src/playback_wasapi.c index 1a510783..7f70e8a2 100644 --- a/src/playback_wasapi.c +++ b/src/playback_wasapi.c @@ -9,6 +9,8 @@ #include "scratch.h" #include "sys.h" #include "mixer.h" +#include "atomic.h" +#include "app.h" #define COBJMACROS #define WIN32_LEAN_AND_MEAN @@ -34,7 +36,7 @@ struct wasapi_buffer { }; GLOBAL struct { - b32 shutdown; + struct atomic_i32 playback_thread_shutdown; struct sys_thread playback_thread; IAudioClient *client; HANDLE event; @@ -45,7 +47,33 @@ GLOBAL struct { } G = { 0 }, DEBUG_ALIAS(G, G_playback_wasapi); /* ========================== * - * Initialize + * Startup + * ========================== */ + +INTERNAL void wasapi_initialize(void); +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(playback_shutdown); +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(playback_thread_entry_point, arg); + +struct playback_startup_receipt playback_startup(struct mixer_startup_receipt *mixer_sr) +{ + (UNUSED)mixer_sr; + + wasapi_initialize(); + G.playback_thread = sys_thread_init(&playback_thread_entry_point, NULL, STR("[P3] Audio thread")); + app_register_exit_callback(&playback_shutdown); + + return (struct playback_startup_receipt) { 0 }; +} + +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(playback_shutdown) +{ + __prof; + atomic_i32_eval_exchange(&G.playback_thread_shutdown, true); + sys_thread_join(&G.playback_thread); +} + +/* ========================== * + * Wasapi initialization * ========================== */ INTERNAL void wasapi_initialize(void) @@ -143,7 +171,7 @@ INTERNAL void wasapi_initialize(void) } /* ========================== * - * Update + * Playback thread update * ========================== */ INTERNAL struct wasapi_buffer wasapi_update_begin(void) @@ -202,16 +230,16 @@ INTERNAL void wasapi_update_end(struct wasapi_buffer *wspbuf, struct mixed_pcm_f } /* ========================== * - * Startup + * Playback thread entry * ========================== */ -INTERNAL SYS_THREAD_FUNC_DEF(playback_thread_entry_point, arg) +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(playback_thread_entry_point, arg) { (UNUSED)arg; /* FIXME: If playback fails at any point and mixer stops advancing, we * need to halt mixer to prevent memory leak when sounds are played. */ - while (!G.shutdown) { + while (!atomic_i32_eval(&G.playback_thread_shutdown)) { struct temp_arena scratch = scratch_begin_no_conflict(); struct wasapi_buffer wspbuf = wasapi_update_begin(); @@ -221,19 +249,3 @@ INTERNAL SYS_THREAD_FUNC_DEF(playback_thread_entry_point, arg) scratch_end(scratch); } } - -struct playback_startup_receipt playback_startup(struct mixer_startup_receipt *mixer_sr) -{ - (UNUSED)mixer_sr; - - wasapi_initialize(); - G.playback_thread = sys_thread_init(&playback_thread_entry_point, NULL, STR("[P3] Audio thread")); - - return (struct playback_startup_receipt) { 0 }; -} - -void playback_shutdown(void) -{ - G.shutdown = true; - sys_thread_join(&G.playback_thread); -} diff --git a/src/sys.h b/src/sys.h index 7e33b200..3ca0d354 100644 --- a/src/sys.h +++ b/src/sys.h @@ -248,8 +248,8 @@ enum sys_window_flags { }; -#define SYS_WINDOW_EVENT_CALLBACK_DEF(name, arg_name) void name(struct sys_event arg_name) -typedef SYS_WINDOW_EVENT_CALLBACK_DEF(sys_window_event_callback, event); +#define SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(name, arg_name) void name(struct sys_event arg_name) +typedef SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(sys_window_event_callback_func, event); /* sys_window_update_settings should be used when altering settings values */ struct sys_window_settings { @@ -273,8 +273,8 @@ struct sys_window { struct sys_window sys_window_alloc(void); void sys_window_release(struct sys_window *sys_window); -void sys_window_register_event_callback(struct sys_window *sys_window, sys_window_event_callback *func); -void sys_window_unregister_event_callback(struct sys_window *sys_window, sys_window_event_callback *func); +void sys_window_register_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func); +void sys_window_unregister_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func); void sys_window_update_settings(struct sys_window *sys_window, struct sys_window_settings *settings); struct sys_window_settings sys_window_get_settings(struct sys_window *sys_window); @@ -378,6 +378,7 @@ struct sys_semaphore { struct sys_semaphore sys_semaphore_alloc(u32 max_count); void sys_semaphore_release(struct sys_semaphore *semaphore); void sys_semaphore_wait(struct sys_semaphore *semaphore); +void sys_semaphore_wait_timed(struct sys_semaphore *semaphore, f64 seconds); void sys_semaphore_signal(struct sys_semaphore *semaphore, u32 count); /* ========================== * @@ -392,17 +393,17 @@ struct thread_local_store *sys_thread_get_thread_local_store(void); #define SYS_THREAD_STACK_SIZE MEGABYTE(4) -#define SYS_THREAD_FUNC_DEF(name, arg_name) void name(void *arg_name) -typedef SYS_THREAD_FUNC_DEF(sys_thread_func, data); +#define SYS_THREAD_ENTRY_POINT_FUNC_DEF(name, arg_name) void name(void *arg_name) +typedef SYS_THREAD_ENTRY_POINT_FUNC_DEF(sys_thread_entry_point_func, data); struct sys_thread { - u64 handle; + u64 handle[2]; }; -/* Creates and starts running the thread in the supplied `thread_func` */ +/* Creates a new thread running in the supplied `entry_point` */ struct sys_thread sys_thread_init( - sys_thread_func *thread_func, - void *thread_data, /* Passed to thread_func */ + sys_thread_entry_point_func *entry_point, + void *thread_data, /* Passed as arg to `entry_point` */ struct string thread_name ); diff --git a/src/sys_win32.c b/src/sys_win32.c index b9a60d99..6eee83f1 100644 --- a/src/sys_win32.c +++ b/src/sys_win32.c @@ -31,12 +31,16 @@ #define SYS_WINDOW_EVENT_LISTENERS_MAX 512 #define WINDOW_CLASS_NAME L"power_play_window_class" -struct win32_thread_params { - sys_thread_func *thread_func; +struct win32_thread { + u64 gen; + sys_thread_entry_point_func *entry_point; void *thread_data; char thread_name_cstr[256]; - struct win32_thread_params *next_free; + struct win32_thread *next; + struct win32_thread *prev; + + HANDLE handle; }; struct win32_condition_variable { @@ -81,7 +85,7 @@ struct win32_window { struct sys_thread event_thread; struct sys_mutex event_callbacks_mutex; - sys_window_event_callback *event_callbacks[SYS_WINDOW_EVENT_LISTENERS_MAX]; + sys_window_event_callback_func *event_callbacks[SYS_WINDOW_EVENT_LISTENERS_MAX]; u64 event_callbacks_count; struct win32_window *next_free; @@ -107,9 +111,11 @@ GLOBAL struct { struct win32_condition_variable *first_free_condition_variable; /* Thread params */ - struct sys_mutex thread_params_mutex; - struct arena thread_params_arena; - struct win32_thread_params *first_free_thread_params; + struct sys_rw_mutex threads_rw_mutex; + struct arena threads_arena; + struct win32_thread *threads_first; + struct win32_thread *threads_last; + struct win32_thread *threads_first_free; /* Windows */ WNDCLASSEXW window_class; @@ -1080,7 +1086,7 @@ void sys_window_release(struct sys_window *sys_window) win32_window_release(window); } -void sys_window_register_event_callback(struct sys_window *sys_window, sys_window_event_callback *func) +void sys_window_register_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func) { struct win32_window *window = (struct win32_window *)sys_window->handle; @@ -1095,14 +1101,14 @@ void sys_window_register_event_callback(struct sys_window *sys_window, sys_windo sys_mutex_unlock(&window->event_callbacks_mutex); } -void sys_window_unregister_event_callback(struct sys_window *sys_window, sys_window_event_callback *func) +void sys_window_unregister_event_callback(struct sys_window *sys_window, sys_window_event_callback_func *func) { struct win32_window *window = (struct win32_window *)sys_window->handle; sys_mutex_lock(&window->event_callbacks_mutex); { u64 count = window->event_callbacks_count; - sys_window_event_callback *last = count > 0 ? window->event_callbacks[count - 1] : NULL; + sys_window_event_callback_func *last = count > 0 ? window->event_callbacks[count - 1] : NULL; for (u64 i = 0; i < window->event_callbacks_count; ++i) { if (window->event_callbacks[i] == func) { @@ -1442,6 +1448,13 @@ void sys_semaphore_wait(struct sys_semaphore *semaphore) WaitForSingleObjectEx((HANDLE)semaphore->handle, INFINITE, FALSE); } +void sys_semaphore_wait_timed(struct sys_semaphore *semaphore, f64 seconds) +{ + __prof; + u32 ms = max_u32(1, math_round((f32)(seconds * 1000.0))); + WaitForSingleObjectEx((HANDLE)semaphore->handle, ms, FALSE); +} + void sys_semaphore_signal(struct sys_semaphore *semaphore, u32 count) { __prof; @@ -1494,37 +1507,67 @@ struct thread_local_store *sys_thread_get_thread_local_store(void) * Threads * ========================== */ -INTERNAL struct win32_thread_params *thread_params_alloc(void) +INTERNAL struct win32_thread *win32_thread_alloc_assume_locked(void) { - struct win32_thread_params *tp = NULL; - sys_mutex_lock(&G.thread_params_mutex); - { - if (G.first_free_thread_params) { - tp = G.first_free_thread_params; - G.first_free_thread_params = tp->next_free; - } else { - tp = arena_push(&G.thread_params_arena, struct win32_thread_params); - } + struct win32_thread *t = NULL; + if (G.threads_first_free) { + t = G.threads_first_free; + G.threads_first_free = t->next; + MEMZERO_STRUCT(t); + } else { + t = arena_push_zero(&G.threads_arena, struct win32_thread); + t->gen = 1; } - sys_mutex_unlock(&G.thread_params_mutex); - MEMZERO_STRUCT(tp); - return tp; + if (G.threads_last) { + G.threads_last->next = t; + } + t->prev = G.threads_last; + return t; } -INTERNAL void thread_params_release(struct win32_thread_params *tp) +INTERNAL void win32_thread_release_assume_locked(struct win32_thread *t) { - sys_mutex_lock(&G.thread_params_mutex); - { - tp->next_free = G.first_free_thread_params; - G.first_free_thread_params = tp; + if (t->prev) { + t->prev->next = t->next; } - sys_mutex_unlock(&G.thread_params_mutex); + if (t->next) { + t->next->prev = t->prev; + } + if (G.threads_first == t) { + G.threads_first = t->next; + } + if (G.threads_last == t) { + G.threads_last = t->prev; + } + u64 new_gen = t->gen + 1; + *t = (struct win32_thread) { + .next = G.threads_first_free, + .gen = new_gen + }; } -INTERNAL DWORD WINAPI win32_thread_proc(LPVOID params) +INTERNAL struct win32_thread *win32_thread_from_sys_thread_assume_locked(struct sys_thread st) { - struct win32_thread_params thread_params = *(struct win32_thread_params *)params; - thread_params_release((struct win32_thread_params *)params); + u64 gen = st.handle[0]; + struct win32_thread *t = (struct win32_thread *)st.handle[1]; + if (t->gen == gen) { + return t; + } else { + return NULL; + } +} + +INTERNAL struct sys_thread sys_thread_from_win32_thread_assume_locked(struct win32_thread *t) +{ + return (struct sys_thread) { + .handle[0] = t->gen, + .handle[1] = (u64)t + }; +} + +INTERNAL DWORD WINAPI win32_thread_proc(LPVOID vt) +{ + struct win32_thread *t = (struct win32_thread *)vt; /* Initialize COM */ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); @@ -1534,7 +1577,7 @@ INTERNAL DWORD WINAPI win32_thread_proc(LPVOID params) win32_thread_set_tls(&tls); /* Set thread name */ - struct string thread_name = string_from_cstr(thread_params.thread_name_cstr); + struct string thread_name = string_from_cstr(t->thread_name_cstr); if (thread_name.len > 0) { struct temp_arena scratch = scratch_begin_no_conflict(); wchar_t *wc_thread_name = wstr_from_string(scratch.arena, thread_name); @@ -1545,7 +1588,14 @@ INTERNAL DWORD WINAPI win32_thread_proc(LPVOID params) logf_info("New thread \"%F\" created with ID %F", FMT_STR(thread_name), FMT_UINT(sys_thread_id())); /* Start thread */ - thread_params.thread_func(thread_params.thread_data); + t->entry_point(t->thread_data); + + /* Release thread object */ + sys_rw_mutex_lock_exclusive(&G.threads_rw_mutex); + { + win32_thread_release_assume_locked(t); + } + sys_rw_mutex_unlock_exclusive(&G.threads_rw_mutex); /* Release TLS */ win32_tls_release(&tls); @@ -1556,49 +1606,63 @@ INTERNAL DWORD WINAPI win32_thread_proc(LPVOID params) return 0; } -struct sys_thread sys_thread_init(sys_thread_func *thread_func, void *thread_data, struct string thread_name) +struct sys_thread sys_thread_init(sys_thread_entry_point_func *entry_point, void *thread_data, struct string thread_name) { - struct sys_thread thread = { 0 }; - - ASSERT(thread_func != NULL); - + ASSERT(entry_point != NULL); logf_info("Creating thread \"%F\"", FMT_STR(thread_name)); - /* Create thread params */ - struct win32_thread_params *tp = thread_params_alloc(); - tp->thread_func = thread_func; - tp->thread_data = thread_data; + struct sys_thread res = { 0 }; + sys_rw_mutex_lock_exclusive(&G.threads_rw_mutex); + { + /* Allocate thread object */ + struct win32_thread *t = win32_thread_alloc_assume_locked(); + t->entry_point = entry_point; + t->thread_data = thread_data; - /* Copy thread name to params */ - cstr_buff_from_string(BUFFER_FROM_ARRAY(tp->thread_name_cstr), thread_name); + /* Copy thread name to params */ + cstr_buff_from_string(BUFFER_FROM_ARRAY(t->thread_name_cstr), thread_name); - HANDLE handle = CreateThread( - NULL, - SYS_THREAD_STACK_SIZE, - win32_thread_proc, - tp, - 0, - NULL - ); + t->handle = CreateThread( + NULL, + SYS_THREAD_STACK_SIZE, + win32_thread_proc, + t, + 0, + NULL + ); - /* Initialize struct */ - if (handle) { - /* Success */ - thread.handle = (u64)handle; - } else { - sys_panic(STR("Failed to create thread")); + if (!t->handle) { + sys_panic(STR("Failed to create thread")); + } + + res = sys_thread_from_win32_thread_assume_locked(t); } - - return thread; + sys_rw_mutex_unlock_exclusive(&G.threads_rw_mutex); + return res; } void sys_thread_join(struct sys_thread *thread) { - DWORD res = WaitForSingleObject((HANDLE)thread->handle, INFINITE); - CloseHandle((HANDLE)thread->handle); + HANDLE handle = 0; - (UNUSED)res; - ASSERT(res != WAIT_FAILED); + /* Lookup */ + sys_rw_mutex_lock_shared(&G.threads_rw_mutex); + { + struct win32_thread *t = win32_thread_from_sys_thread_assume_locked(*thread); + if (t) { + handle = t->handle; + } + } + sys_rw_mutex_unlock_shared(&G.threads_rw_mutex); + + /* Wait */ + if (handle) { + DWORD res = WaitForSingleObject(handle, INFINITE); + CloseHandle(handle); + + (UNUSED)res; + ASSERT(res != WAIT_FAILED); + } } u32 sys_thread_id(void) @@ -1893,7 +1957,7 @@ void sys_sleep(f64 seconds) * Entry point * ========================== */ -INTERNAL SYS_THREAD_FUNC_DEF(app_thread_entry_point, arg) +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(app_thread_entry_point, arg) { (UNUSED)arg; app_entry_point(); @@ -1932,8 +1996,8 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, G.condition_variables_arena = arena_alloc(GIGABYTE(64)); /* Set up threads */ - G.thread_params_mutex = sys_mutex_alloc(); - G.thread_params_arena = arena_alloc(GIGABYTE(64)); + G.threads_rw_mutex = sys_rw_mutex_alloc(); + G.threads_arena = arena_alloc(GIGABYTE(64)); /* Set up windows */ G.windows_mutex = sys_mutex_alloc(); @@ -1990,17 +2054,24 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, } /* ========================== * - * Main thread setup + * App thread setup * ========================== */ /* Initialize main thread context. */ struct win32_tls main_thread_tls = win32_tls_alloc(); win32_thread_set_tls(&main_thread_tls); - /* Create app thread & wait for return */ + /* Call app thread and wait for return */ struct sys_thread app_thread = sys_thread_init(&app_thread_entry_point, NULL, STR("[P9] App thread")); sys_thread_join(&app_thread); + /* Release main thread context */ + win32_tls_release(&main_thread_tls); + + /* ========================== * + * Abort + * ========================== */ + abort: if (error_msg) { @@ -2009,8 +2080,6 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance, return 1; } - win32_tls_release(&main_thread_tls); - return 0; } diff --git a/src/user.c b/src/user.c index a13e0e1e..7699ff1a 100644 --- a/src/user.c +++ b/src/user.c @@ -16,6 +16,8 @@ #include "world.h" #include "entity.h" #include "mixer.h" +#include "atomic.h" +#include "app.h" struct bind_state { b32 is_held; /* Is this bind held down this frame */ @@ -30,7 +32,7 @@ struct blend_tick { }; GLOBAL struct { - b32 shutdown; + struct atomic_i32 user_thread_shutdown; struct sys_thread user_thread; struct arena arena; @@ -93,6 +95,57 @@ GLOBAL READONLY enum user_bind_kind g_binds[SYS_BTN_COUNT] = { [SYS_BTN_CTRL] = USER_BIND_KIND_CTRL_TEST }; +/* ========================== * + * Startup + * ========================== */ + +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(user_shutdown); +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg); +INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event); + +struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr, + struct renderer_startup_receipt *renderer_sr, + struct font_startup_receipt *font_sr, + struct texture_startup_receipt *texture_sr, + struct draw_startup_receipt *draw_sr, + struct game_startup_receipt *game_sr, + struct asset_cache_startup_receipt *asset_cache_sr, + struct mixer_startup_receipt *mixer_sr, + struct sys_window *window) +{ + (UNUSED)work_sr; + (UNUSED)renderer_sr; + (UNUSED)font_sr; + (UNUSED)texture_sr; + (UNUSED)draw_sr; + (UNUSED)game_sr; + (UNUSED)asset_cache_sr; + (UNUSED)mixer_sr; + + G.arena = arena_alloc(GIGABYTE(64)); + G.sys_events_mutex = sys_mutex_alloc(); + G.sys_events_arena = arena_alloc(GIGABYTE(64)); + world_alloc(&G.world); + G.world_canvas = renderer_canvas_alloc(); + G.world_view = XFORM_TRS(.t = V2(0, 0), .r = 0, .s = V2(PIXELS_PER_UNIT, PIXELS_PER_UNIT)); + G.viewport_bg_canvas = renderer_canvas_alloc(); + G.viewport_canvas = renderer_canvas_alloc(); + G.window = window; + sys_window_register_event_callback(G.window, &window_event_callback); + + G.user_thread = sys_thread_init(&user_thread_entry_point, NULL, STR("[P1] User thread")); + app_register_exit_callback(&user_shutdown); + + return (struct user_startup_receipt) { 0 }; +} + +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(user_shutdown) +{ + __prof; + atomic_i32_eval_exchange(&G.user_thread_shutdown, true); + sys_thread_join(&G.user_thread); +} + /* ========================== * * Window -> user communication * ========================== */ @@ -113,7 +166,7 @@ INTERNAL struct sys_event_array pop_sys_events(struct arena *arena) return array; } -INTERNAL SYS_WINDOW_EVENT_CALLBACK_DEF(window_event_callback, event) +INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event) { sys_mutex_lock(&G.sys_events_mutex); { @@ -452,14 +505,14 @@ INTERNAL void user_update(void) struct sys_event *event = &events.events[entity_index]; if (event->kind == SYS_EVENT_KIND_QUIT) { - app_quit(); + app_exit(); } if (event->kind == SYS_EVENT_KIND_BUTTON_UP) { #if DEVELOPER /* Escape quit */ if (event->button == SYS_BTN_ESC) { - app_quit(); + app_exit(); } #endif } @@ -1020,17 +1073,17 @@ INTERNAL void user_update(void) } /* ========================== * - * Startup + * User thread entry point * ========================== */ -INTERNAL SYS_THREAD_FUNC_DEF(user_thread_entry_point, arg) +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg) { (UNUSED)arg; sys_timestamp_t last_frame_ts = 0; f64 target_dt = USER_FRAME_LIMIT > (0) ? (1.0 / USER_FRAME_LIMIT) : 0; - while (!G.shutdown) { + while (!atomic_i32_eval(&G.user_thread_shutdown)) { __profscope(user_update_w_sleep); sleep_frame(last_frame_ts, target_dt); last_frame_ts = sys_timestamp(); @@ -1038,52 +1091,6 @@ INTERNAL SYS_THREAD_FUNC_DEF(user_thread_entry_point, arg) } } -struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr, - struct renderer_startup_receipt *renderer_sr, - struct font_startup_receipt *font_sr, - struct texture_startup_receipt *texture_sr, - struct draw_startup_receipt *draw_sr, - struct game_startup_receipt *game_sr, - struct asset_cache_startup_receipt *asset_cache_sr, - struct mixer_startup_receipt *mixer_sr, - struct sys_window *window) -{ - (UNUSED)work_sr; - (UNUSED)renderer_sr; - (UNUSED)font_sr; - (UNUSED)texture_sr; - (UNUSED)draw_sr; - (UNUSED)game_sr; - (UNUSED)asset_cache_sr; - (UNUSED)mixer_sr; - - G.arena = arena_alloc(GIGABYTE(64)); - - G.sys_events_mutex = sys_mutex_alloc(); - G.sys_events_arena = arena_alloc(GIGABYTE(64)); - - world_alloc(&G.world); - - G.world_canvas = renderer_canvas_alloc(); - G.world_view = XFORM_TRS(.t = V2(0, 0), .r = 0, .s = V2(PIXELS_PER_UNIT, PIXELS_PER_UNIT)); - - G.viewport_bg_canvas = renderer_canvas_alloc(); - G.viewport_canvas = renderer_canvas_alloc(); - - G.window = window; - sys_window_register_event_callback(G.window, &window_event_callback); - - G.user_thread = sys_thread_init(&user_thread_entry_point, NULL, STR("[P1] User thread")); - - return (struct user_startup_receipt) { 0 }; -} - -void user_shutdown(void) -{ - G.shutdown = true; - sys_thread_join(&G.user_thread); -} - #else #include "user.h" #include "renderer.h" @@ -1198,7 +1205,7 @@ INTERNAL struct sys_event_array pop_sys_events(struct arena *arena) return array; } -INTERNAL SYS_WINDOW_EVENT_CALLBACK_DEF(window_event_callback, event) +INTERNAL SYS_WINDOW_EVENT_CALLBACK_FUNC_DEF(window_event_callback, event) { sys_mutex_lock(&G.sys_events_mutex); { @@ -1543,14 +1550,14 @@ INTERNAL void user_update(void) struct sys_event *event = &events.events[entity_index]; if (event->kind == SYS_EVENT_KIND_QUIT) { - app_quit(); + app_exit(); } if (event->kind == SYS_EVENT_KIND_BUTTON_UP) { #if DEVELOPER /* Escape quit */ if (event->button == SYS_BTN_ESC) { - app_quit(); + app_exit(); } #endif } @@ -2118,7 +2125,7 @@ INTERNAL void user_update(void) * Startup * ========================== */ -INTERNAL SYS_THREAD_FUNC_DEF(user_thread_entry_point, arg) +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(user_thread_entry_point, arg) { (UNUSED)arg; diff --git a/src/user.h b/src/user.h index ca596b92..fd080958 100644 --- a/src/user.h +++ b/src/user.h @@ -43,6 +43,5 @@ struct user_startup_receipt user_startup(struct work_startup_receipt *work_sr, struct asset_cache_startup_receipt *asset_cache_sr, struct mixer_startup_receipt *mixer_sr, struct sys_window *window); -void user_shutdown(void); #endif diff --git a/src/work.c b/src/work.c index 822799b2..321da11a 100644 --- a/src/work.c +++ b/src/work.c @@ -7,6 +7,8 @@ #include "string.h" #include "log.h" #include "thread_local.h" +#include "atomic.h" +#include "app.h" /* Terminology: * @@ -65,13 +67,12 @@ struct work_task { * ========================== */ GLOBAL struct { - b32 shutdown; - struct arena arena; struct sys_mutex mutex; struct sys_semaphore semaphore; + struct atomic_i32 workers_shutdown; u32 worker_count; u32 idle_worker_count; struct worker *worker_head; @@ -102,7 +103,8 @@ GLOBAL THREAD_LOCAL_VAR_DEF(tl_worker_ctx, struct worker_ctx, NULL, NULL); * Startup * ========================== */ -INTERNAL void worker_thread_entry_point(void *thread_data); +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(work_shutdown); +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(worker_thread_entry_point, thread_data); struct work_startup_receipt work_startup(u32 num_worker_threads) { @@ -117,6 +119,7 @@ struct work_startup_receipt work_startup(u32 num_worker_threads) G.semaphore = sys_semaphore_alloc(num_worker_threads); G.worker_count = num_worker_threads; G.idle_worker_count = num_worker_threads; + app_register_exit_callback(&work_shutdown); /* Initialize threads */ { @@ -146,10 +149,10 @@ struct work_startup_receipt work_startup(u32 num_worker_threads) return (struct work_startup_receipt) { 0 }; } -void work_shutdown(void) +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(work_shutdown) { - G.shutdown = true; - WRITE_BARRIER(); + __prof; + atomic_i32_eval_exchange(&G.workers_shutdown, true); sys_semaphore_signal(&G.semaphore, G.worker_count); for (struct worker *worker = G.worker_head; (worker = worker->next);) { sys_thread_join(&worker->thread); @@ -375,7 +378,7 @@ INTERNAL void work_exec_remaining_tasks_maybe_release_assume_locked(struct work * Work thread proc * ========================== */ -INTERNAL void worker_thread_entry_point(void *thread_data) +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(worker_thread_entry_point, thread_data) { (UNUSED)thread_data; @@ -384,12 +387,8 @@ INTERNAL void worker_thread_entry_point(void *thread_data) .is_worker = true }; - while (true) { - sys_semaphore_wait(&G.semaphore); - if (G.shutdown) { - /* Exit thread */ - break; - } + while (!atomic_i32_eval(&G.workers_shutdown)) { + sys_semaphore_wait_timed(&G.semaphore, 0.500); /* Timed to poll G.shutdown */ while (G.scheduled_work_head) { /* Do work from top */ sys_mutex_lock(&G.mutex); diff --git a/src/work.h b/src/work.h index a6772b17..196ae0a4 100644 --- a/src/work.h +++ b/src/work.h @@ -33,7 +33,6 @@ struct work_slate { struct work_startup_receipt { i32 _; }; struct work_startup_receipt work_startup(u32 num_worker_threads); -void work_shutdown(void); struct work_slate work_slate_begin(void); struct work_handle work_slate_end(struct work_slate *ws, enum work_priority priority);