app exit / shutdown callbacks
This commit is contained in:
parent
d646d3434c
commit
511364ee05
65
src/app.c
65
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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
81
src/game.c
81
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
|
||||
* ========================== */
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
21
src/sys.h
21
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
|
||||
);
|
||||
|
||||
|
||||
187
src/sys_win32.c
187
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;
|
||||
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,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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
121
src/user.c
121
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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
25
src/work.c
25
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);
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user