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:
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