ohsp

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

commit e22b5a4ca66ff8f32a7553916f3448a8bc13821a
parent 82cc4f97ca6dfd33b6dc5d787ae2d069c8f321a9
Author: amin <dev@aminmesbah.com>
Date:   Mon, 13 Nov 2017 07:48:23 +0000

Merge branch 'features/live-reload'

FossilOrigin-Name: b662bb8b31d733c1c16afcf17dcbaf1369dfccd0d6368ee89cef06c0c35c4b44
Diffstat:
MMakefile | 32++++++++++++++++++++++++--------
Msrc/game.c | 20++++++++++----------
Msrc/game.h | 4++++
Msrc/platform_sdl.c | 23++++++++++++++++++++---
Msrc/platform_sdl.h | 13+++++++++++++
Asrc/util_linux.h | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 149 insertions(+), 21 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,6 +1,6 @@ CC = gcc -CFLAGS = -std=c99 -Wall -Wextra -Wfloat-equal -Wpedantic -Wshadow -Wswitch-enum -Wno-unused-parameter -LDFLAGS = $(SDL_LDFLAGS) -lm +CFLAGS = -std=c99 -Wall -Wextra -Wfloat-equal -Wshadow -Wswitch-enum -Wno-unused-parameter +LDFLAGS = $(SDL_LDFLAGS) -ldl -lm SDL_CFLAGS := $(shell sdl2-config --cflags) SDL_LDFLAGS := $(shell sdl2-config --libs) @@ -9,32 +9,48 @@ override CFLAGS += $(SDL_CFLAGS) SRC_FILES = platform_sdl.c game.c entity.c SRC = $(addprefix src/, $(SRC_FILES)) -EXE = ohsp +EXE_FILE = ohsp + +LIB_FILES = game.c entity.c +LIB = $(addprefix src/, $(LIB_FILES)) +LIB_NAME = game.so DBGDIR = debug -DBGEXE = $(DBGDIR)/$(EXE) +DBGEXE = $(DBGDIR)/$(EXE_FILE) +DBGLIB = $(DBGDIR)/$(LIB_NAME) +DBGLIBTMP = $(DBGLIB).tmp DBGCFLAGS = -g -Og -Werror RELDIR = release -RELEXE = $(RELDIR)/$(EXE) +RELEXE = $(RELDIR)/$(EXE_FILE) +RELLIB = $(RELDIR)/$(LIB_NAME) +RELLIBTMP = $(RELLIB).tmp RELCFLAGS = -O2 -Os -.PHONY: all clean compile_debug compile_release debug memcheck prep run todo +.PHONY: all clean compile_debug compile_release debug lib_debug lib_release memcheck prep run todo all: compile_debug compile_release clean: rm -f $(RELDIR)/* $(DBGDIR)/* -compile_debug: prep +compile_debug: prep lib_debug $(CC) $(CFLAGS) $(DBGCFLAGS) $(SRC) -o $(DBGEXE) $(LDFLAGS) -compile_release: prep +compile_release: prep lib_release $(CC) $(CFLAGS) $(RELCFLAGS) $(SRC) -o $(RELEXE) $(LDFLAGS) debug: compile_debug gdb $(DBGEXE) +lib_debug: + $(CC) $(CFLAGS) -fpic -shared $(DBGCFLAGS) $(LIB) -o $(DBGLIBTMP) $(LDFLAGS) + mv $(DBGLIBTMP) $(DBGLIB) + +lib_release: + $(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 @@ -62,19 +62,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/game.h b/src/game.h @@ -63,6 +63,10 @@ struct OffscreenBuffer unsigned int pitch; }; +// TODO: Consider using the #define trick from HMH +typedef void (game_update_t)(struct GameState*, struct GameControllerInput, int, int); +typedef void (game_render_t)(struct OffscreenBuffer*, float, struct GameState*); + void game_init(struct GameState *game_state, int field_width, int field_height); void game_update(struct GameState *game_state, struct GameControllerInput game_input, int field_width, int field_height); void game_render(struct OffscreenBuffer *buffer, float dt, struct GameState *game_state); diff --git a/src/platform_sdl.c b/src/platform_sdl.c @@ -1,6 +1,9 @@ #include "platform_sdl.h" -#include "game.h" +#ifdef __linux__ +#include "util_linux.h" +#elif __WIN32 +#endif #include <stdbool.h> #include <stdio.h> @@ -243,6 +246,8 @@ int main(int argc, char *argv[]) struct GameControllerInput controller_input = {0}; + struct SDLGameCode game_code = load_game_code(GAME_LIB_PATH); + while (running) { uint64_t current_ms = (SDL_GetPerformanceCounter() * SECOND) / SDL_GetPerformanceFrequency(); @@ -251,6 +256,18 @@ int main(int argc, char *argv[]) lag += elapsed_ms; //printf("%" PRIu64 ", %" PRIu64 ", %f\n", elapsed_ms, lag, MS_PER_UPDATE); + time_t last_write_time = 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) + { + unload_game_code(&game_code); + game_code = load_game_code(GAME_LIB_PATH); + if (!game_code.is_valid) + { + // TODO: fall back to backup? + } + } + SDL_Event event; while (SDL_PollEvent(&event)) { @@ -278,14 +295,14 @@ int main(int argc, char *argv[]) { while (lag >= MS_PER_UPDATE) { - game_update(&game_state, controller_input, buffer.width, buffer.height); + game_code.game_update(&game_state, controller_input, buffer.width, buffer.height); //printf("\t%" PRIu64 ", %f\n", lag, MS_PER_UPDATE); lag -= MS_PER_UPDATE; } } clear_screen(&global_back_buffer, COLOR_BACKGROUND); - game_render(&buffer, lag/SECOND, &game_state); + game_code.game_render(&buffer, lag/SECOND, &game_state); sdl_update_window(renderer, &global_back_buffer); if (elapsed_ms <= MS_PER_FRAME) { diff --git a/src/platform_sdl.h b/src/platform_sdl.h @@ -1,7 +1,11 @@ #ifndef PLATFORM_SDL_H #include "SDL.h" +#include "game.h" +#include <stdbool.h> + +#define GAME_LIB_PATH "./release/game.so" #define MAX_CONTROLLERS 4 #define DEADZONE_THRESHOLD 9000 @@ -21,5 +25,14 @@ struct SDLWindowDimension int height; }; +struct SDLGameCode +{ + bool is_valid; + void *game_code_library; + time_t last_write_time; + game_update_t *game_update; + game_render_t *game_render; +}; + #define PLATFORM_SDL_H #endif diff --git a/src/util_linux.h b/src/util_linux.h @@ -0,0 +1,78 @@ +#ifndef UTIL_LINUX_H +#define UTIL_LINUX_H + +#include <dlfcn.h> +#include <sys/stat.h> + + +time_t 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 unload_game_code(struct SDLGameCode *game_code) +{ + if (!game_code) + { + printf("Invalid pointer *game_code\n"); + return; + } + + if (game_code->game_code_library) + { + dlclose(game_code->game_code_library); + game_code->game_code_library = 0; + } + game_code->is_valid = false; + game_code->game_update = 0; + game_code->game_render = 0; +} + + +// TODO: Add backup dll in case loading fails +struct SDLGameCode load_game_code(char *source_lib_path) +{ + struct SDLGameCode game_code = {0}; + + game_code.last_write_time = file_get_modified_time(source_lib_path); + if (game_code.last_write_time) + { + game_code.game_code_library = dlopen(source_lib_path, RTLD_LAZY); + 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 dlsym on POSIX-compliant platforms. + game_code.game_update = (game_update_t *) dlsym(game_code.game_code_library, "game_update"); + game_code.game_render = (game_render_t *) dlsym(game_code.game_code_library, "game_render"); + game_code.is_valid = (game_code.game_update && game_code.game_render); + } + } + + if (!game_code.is_valid) + { + fprintf(stderr, "ERROR: Game code is not valid: %s\n", dlerror()); + } + + return game_code; +} + + +#endif