ohsp

Prototype for a game with dual thruster controls.
git clone git://git.amin.space/ohsp.git
Log | Files | Refs | LICENSE

commit 7fd94c8e3a2b002597c4ece1c3a30070c49a529c
parent 81dd4840837c968fa43530d4890c5183765692c1
Author: amin <dev@aminmesbah.com>
Date:   Mon, 13 Nov 2017 07:08:39 +0000

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.

FossilOrigin-Name: 8da5cdc5717a8af8b995525cc88beaf7939919f910d13024230e5b0f8042ab80
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