#include "twn_game_object_c.h" #include "twn_engine_context_c.h" #include "twn_util_c.h" #include #include #include #define GAME_OBJECT_NAME "libgame.so" #define MODIFIED_TICKS_MERGED 10 static void (*game_tick_callback)(void); static void (*game_end_callback)(void); static x_watcher *watcher; static void *handle = NULL; static uint64_t last_tick_modified; static bool loaded_after_modification = true; static SDL_mutex *lock; static char *game_object_path; static void load_game_object(void) { /* needs to be closed otherwise symbols aren't resolved again */ if (handle) { dlclose(handle); handle = NULL; game_tick_callback = NULL; game_end_callback = NULL; } void *new_handle = dlopen(game_object_path, RTLD_LAZY); if (!new_handle) { log_critical("Hot Reload Error: Cannot open game code shared object (%s)", dlerror()); goto ERR_OPENING_SO; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" game_tick_callback = (void (*)(void))dlsym(new_handle, "game_tick"); if (!game_tick_callback) { CRY("Hot Reload Error", "game_tick_callback() symbol wasn't found"); goto ERR_GETTING_PROC; } game_end_callback = (void (*)(void))dlsym(new_handle, "game_end"); if (!game_end_callback) { CRY("Hot Reload Error", "game_end_callback() symbol wasn't found"); goto ERR_GETTING_PROC; } #pragma GCC diagnostic pop handle = new_handle; if (ctx.game.frame_number != 0) log_info("Game object was reloaded\n"); return; ERR_GETTING_PROC: dlclose(new_handle); game_tick_callback = NULL; game_end_callback = NULL; ERR_OPENING_SO: SDL_UnlockMutex(lock); } static void watcher_callback(XWATCHER_FILE_EVENT event, const char *path, int context, void *data) { (void)context; (void)path; (void)data; switch(event) { case XWATCHER_FILE_CREATED: case XWATCHER_FILE_MODIFIED: SDL_LockMutex(lock); last_tick_modified = ctx.game.frame_number; loaded_after_modification = false; SDL_UnlockMutex(lock); break; case XWATCHER_FILE_UNSPECIFIED: case XWATCHER_FILE_REMOVED: case XWATCHER_FILE_OPENED: case XWATCHER_FILE_ATTRIBUTES_CHANGED: case XWATCHER_FILE_NONE: case XWATCHER_FILE_RENAMED: default: break; } } void game_object_load(void) { SDL_asprintf(&game_object_path, "%s%s", ctx.base_dir, GAME_OBJECT_NAME); watcher = xWatcher_create(); xWatcher_reference dir; dir.path = game_object_path; dir.callback_func = watcher_callback; xWatcher_appendFile(watcher, &dir); xWatcher_start(watcher); lock = SDL_CreateMutex(); load_game_object(); } void game_object_unload(void) { game_end_callback(); xWatcher_destroy(watcher); watcher = NULL; dlclose(handle); handle = NULL; game_tick_callback = NULL; game_end_callback = NULL; SDL_DestroyMutex(lock); } bool game_object_try_reloading(void) { bool result = false; /* only load the modified library after some time, as compilers make a lot of modifications */ SDL_LockMutex(lock); if (ctx.game.frame_number - last_tick_modified > MODIFIED_TICKS_MERGED && !loaded_after_modification) { load_game_object(); loaded_after_modification = true; result = true; } SDL_UnlockMutex(lock); return result; } void game_object_tick(void) { game_tick_callback(); } void *game_object_get_game_tick_address(void) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" return (void *)&game_tick_callback; #pragma GCC diagnostic pop }