app exit / shutdown callbacks

This commit is contained in:
jacob 2024-04-15 18:02:38 -05:00
parent d646d3434c
commit 511364ee05
12 changed files with 367 additions and 227 deletions

View File

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

View File

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

View File

@ -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
* ========================== */

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
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 {
tp = arena_push(&G.thread_params_arena, struct win32_thread_params);
t = arena_push_zero(&G.threads_arena, struct win32_thread);
t->gen = 1;
}
if (G.threads_last) {
G.threads_last->next = t;
}
sys_mutex_unlock(&G.thread_params_mutex);
MEMZERO_STRUCT(tp);
return tp;
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,50 +1606,64 @@ 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);
cstr_buff_from_string(BUFFER_FROM_ARRAY(t->thread_name_cstr), thread_name);
HANDLE handle = CreateThread(
t->handle = CreateThread(
NULL,
SYS_THREAD_STACK_SIZE,
win32_thread_proc,
tp,
t,
0,
NULL
);
/* Initialize struct */
if (handle) {
/* Success */
thread.handle = (u64)handle;
} else {
if (!t->handle) {
sys_panic(STR("Failed to create thread"));
}
return thread;
res = sys_thread_from_win32_thread_assume_locked(t);
}
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;
/* 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;
}

View File

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

View File

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

View File

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

View File

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