ohsp

Prototype for a game with dual thruster controls.
Log | Files | Refs | LICENSE

commit 77656fe8dca89bf4953be96e186cf1ecd03fd889
parent 02e84b7d701f83d128b5779a3a15d53a37e2e2cf
Author: Amin Mesbah <mesbahamin@gmail.com>
Date:   Sun, 12 Nov 2017 23:08:40 -0800

Load game code instantaneously on Linux

Stat is used to read the last modification time of the game.so file. It
is loaded if that time is different from the time at last load. This
means that it's loaded basically as soon as it's compiled.

In the makefile I specified that the game code lib should be compiled to
a temp, then copied to the actual .so file. This is because when I tried
compiling directly to the .so, the platform would detect the change in
the file's modified time, and would try to load it before compilation
was complete.

Diffstat:
MMakefile | 8++++++--
Msrc/game.c | 20++++++++++----------
Msrc/platform_sdl.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Msrc/platform_sdl.h | 1+
4 files changed, 67 insertions(+), 40 deletions(-)

diff --git a/Makefile b/Makefile @@ -19,11 +19,13 @@ LIB_NAME = game.so DBGDIR = debug DBGEXE = $(DBGDIR)/$(EXE_FILE) DBGLIB = $(DBGDIR)/$(LIB_NAME) +DBGLIBTMP = $(DBGLIB).tmp DBGCFLAGS = -g -Og -Werror RELDIR = release RELEXE = $(RELDIR)/$(EXE_FILE) RELLIB = $(RELDIR)/$(LIB_NAME) +RELLIBTMP = $(RELLIB).tmp RELCFLAGS = -O2 -Os .PHONY: all clean compile_debug compile_release debug lib_debug lib_release memcheck prep run todo @@ -43,10 +45,12 @@ debug: compile_debug gdb $(DBGEXE) lib_debug: - $(CC) $(CFLAGS) -fpic -shared $(DBGCFLAGS) $(LIB) -o $(DBGLIB) $(LDFLAGS) + $(CC) $(CFLAGS) -fpic -shared $(DBGCFLAGS) $(LIB) -o $(DBGLIBTMP) $(LDFLAGS) + mv $(DBGLIBTMP) $(DBGLIB) lib_release: - $(CC) $(CFLAGS) -fpic -shared $(RELCFLAGS) $(LIB) -o $(RELLIB) $(LDFLAGS) + $(CC) $(CFLAGS) -fpic -shared $(RELCFLAGS) $(LIB) -o $(RELLIBTMP) $(LDFLAGS) + mv $(RELLIBTMP) $(RELLIB) memcheck: compile_debug valgrind --track-origins=yes ./$(DBGEXE) diff --git a/src/game.c b/src/game.c @@ -45,19 +45,19 @@ void game_update(struct GameState *game_state, struct GameControllerInput game_i game_state->thrust_vector01.angle = atan2f(game_input.left_stick_y, game_input.left_stick_x); game_state->thrust_vector01.length = hypotf(game_input.left_stick_x, game_input.left_stick_y); - printf("(lx: %f, ly: %f, thrust_a: %f, thrust_l: %f)\n", - game_input.left_stick_x, - game_input.left_stick_y, - game_state->thrust_vector01.angle, - game_state->thrust_vector01.length); + //printf("(lx: %f, ly: %f, thrust_a: %f, thrust_l: %f)\n", + // game_input.left_stick_x, + // game_input.left_stick_y, + // game_state->thrust_vector01.angle, + // game_state->thrust_vector01.length); game_state->thrust_vector02.angle = atan2f(game_input.right_stick_y, game_input.right_stick_x); game_state->thrust_vector02.length = hypotf(game_input.right_stick_x, game_input.right_stick_y); - printf("(rx: %f, ry: %f, thrust_a: %f, thrust_l: %f)\n", - game_input.right_stick_x, - game_input.right_stick_y, - game_state->thrust_vector02.angle, - game_state->thrust_vector02.length); + //printf("(rx: %f, ry: %f, thrust_a: %f, thrust_l: %f)\n", + // game_input.right_stick_x, + // game_input.right_stick_y, + // game_state->thrust_vector02.angle, + // game_state->thrust_vector02.length); game_state->thrust_vector_sum = vec2d_add( game_state->thrust_vector01.angle, diff --git a/src/platform_sdl.c b/src/platform_sdl.c @@ -4,6 +4,9 @@ #include <stdio.h> #include <time.h> +// TODO: move to linux specific code +#include <sys/stat.h> + #ifndef MAP_ANONYMOUS #define MAP_ANONYMOUS MAP_ANON #endif @@ -13,6 +16,23 @@ extern bool PAUSED; static struct SDLOffscreenBuffer global_back_buffer; +time_t linux_file_get_modified_time(char *file_path) +{ + time_t mtime = 0; + struct stat file_status = {0}; + if (stat(file_path, &file_status) == 0) + { + //printf("File: %s last modified: %s\n", file_path, ctime(&file_status.st_mtime)); + mtime = file_status.st_mtime; + } + else + { + fprintf(stderr, "ERROR: Failed to stat file: %s\n", file_path); + } + return mtime; +} + + void sdl_unload_game_code(struct SDLGameCode *game_code) { if (!game_code) @@ -27,36 +47,39 @@ void sdl_unload_game_code(struct SDLGameCode *game_code) game_code->game_code_library = 0; } game_code->is_valid = false; - // TODO: see if we need to set these function pointers to stubs (hmh021) game_code->game_update = 0; game_code->game_render = 0; } -// TODO: Add temp dll in case loading fails +// TODO: Add backup dll in case loading fails struct SDLGameCode sdl_load_game_code(char *source_lib_path) { - struct SDLGameCode game_code; - game_code.is_valid = false; - game_code.game_code_library = 0; - game_code.game_update = 0; - game_code.game_render = 0; - - game_code.game_code_library = SDL_LoadObject(source_lib_path); - if (game_code.game_code_library) + struct SDLGameCode game_code = {0}; + + game_code.last_write_time = linux_file_get_modified_time(source_lib_path); + if (game_code.last_write_time) { - // NOTE: The C standard (as of C99) distinguishes function pointers - // from object pointers (`void *` is an object pointer). Technically it - // is undefined behavior to cast between these two pointer types. In - // practice, it works on most modern platforms. - game_code.game_update = (game_update_t *) SDL_LoadFunction(game_code.game_code_library, "game_update"); - game_code.game_render = (game_render_t *) SDL_LoadFunction(game_code.game_code_library, "game_render"); - game_code.is_valid = (game_code.game_update && game_code.game_render); + game_code.game_code_library = SDL_LoadObject(source_lib_path); + if (game_code.game_code_library) + { + // NOTE: The C standard (as of C99) distinguishes function pointers + // from object pointers (`void *` is an object pointer). Technically it + // is undefined behavior to cast between these two pointer types. In + // practice, it works on most modern platforms. + // + // In this case, we are protected by POSIX, which specifies that + // function and data pointers must be the same size. We will only ever + // be using SDL_LoadFunction on POSIX-compliant platforms. + game_code.game_update = (game_update_t *) SDL_LoadFunction(game_code.game_code_library, "game_update"); + game_code.game_render = (game_render_t *) SDL_LoadFunction(game_code.game_code_library, "game_render"); + game_code.is_valid = (game_code.game_update && game_code.game_render); + } } if (!game_code.is_valid) { - printf("Game code is not valid: %s\n", SDL_GetError()); + fprintf(stderr, "ERROR: Game code is not valid: %s\n", SDL_GetError()); } return game_code; @@ -296,9 +319,7 @@ int main(int argc, char *argv[]) controller_input.right_stick_x = 0; controller_input.right_stick_y = 0; - // TODO: Make this not hard-coded - struct SDLGameCode game_code = sdl_load_game_code("./release/game.so"); - int counter = 0; + struct SDLGameCode game_code = sdl_load_game_code(GAME_LIB_PATH); while (running) { @@ -308,15 +329,16 @@ int main(int argc, char *argv[]) lag += elapsed_ms; //printf("%" PRIu64 ", %" PRIu64 ", %f\n", elapsed_ms, lag, MS_PER_UPDATE); - if (counter < 500) - { - ++counter; - } - else + time_t last_write_time = linux_file_get_modified_time(GAME_LIB_PATH); + bool game_code_has_changed = last_write_time && (last_write_time != game_code.last_write_time); + if (game_code_has_changed) { sdl_unload_game_code(&game_code); - game_code = sdl_load_game_code("./release/game.so"); - counter = 0; + game_code = sdl_load_game_code(GAME_LIB_PATH); + if (!game_code.is_valid) + { + // TODO: fall back to backup? + } } SDL_Event event; diff --git a/src/platform_sdl.h b/src/platform_sdl.h @@ -5,6 +5,7 @@ #include <stdbool.h> +#define GAME_LIB_PATH "./release/game.so" #define MAX_CONTROLLERS 4 #define DEADZONE_THRESHOLD 9000