Implement hot reloading
This commit is contained in:
parent
1531817dc4
commit
2077ecde63
@ -5,6 +5,7 @@ project(epec-mcu-emulator LANGUAGES C)
|
|||||||
option(WERROR "Treat warnings as errors" OFF)
|
option(WERROR "Treat warnings as errors" OFF)
|
||||||
option(UBSAN "Enable undefined behaviour sanitizer" OFF)
|
option(UBSAN "Enable undefined behaviour sanitizer" OFF)
|
||||||
option(PERFMON "Monitor performance of game code" ON)
|
option(PERFMON "Monitor performance of game code" ON)
|
||||||
|
option(HOTRELOAD "Enable hot reloading of game code" OFF)
|
||||||
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
@ -19,6 +20,9 @@ macro(set_default_target_options target)
|
|||||||
target_compile_options(${target} PRIVATE -fsanitize=undefined)
|
target_compile_options(${target} PRIVATE -fsanitize=undefined)
|
||||||
target_link_options(${target} PRIVATE -fsanitize=undefined)
|
target_link_options(${target} PRIVATE -fsanitize=undefined)
|
||||||
endif()
|
endif()
|
||||||
|
if(${HOTRELOAD})
|
||||||
|
target_compile_definitions(engine PRIVATE HOTRELOAD)
|
||||||
|
endif()
|
||||||
endmacro()
|
endmacro()
|
||||||
|
|
||||||
add_custom_target(format
|
add_custom_target(format
|
||||||
@ -40,3 +44,9 @@ find_package(SDL2_image REQUIRED)
|
|||||||
|
|
||||||
add_subdirectory(engine)
|
add_subdirectory(engine)
|
||||||
add_subdirectory(game)
|
add_subdirectory(game)
|
||||||
|
|
||||||
|
# Really this should be in engine/CMakeLists.txt, but it has to go
|
||||||
|
# after the declaration of the game target.
|
||||||
|
if(${HOTRELOAD})
|
||||||
|
target_compile_definitions(engine PRIVATE GAMELIB=\"$<TARGET_FILE:game>\")
|
||||||
|
endif()
|
||||||
|
14
README
14
README
@ -13,4 +13,16 @@ The build is handled with CMake (version 3.10 or later):
|
|||||||
The path to a directory containing the assets must be passed on the
|
The path to a directory containing the assets must be passed on the
|
||||||
command line to run the game:
|
command line to run the game:
|
||||||
|
|
||||||
build/app/game ASSETS-DIR
|
build/game/game ASSETS-DIR
|
||||||
|
|
||||||
|
|
||||||
|
Hot Reloading
|
||||||
|
|
||||||
|
The engine supports hot reloading of the game code via the F5 key; set
|
||||||
|
the HOTRELOAD option on in CMake to enable it. You also have to run an
|
||||||
|
executable built from the engine rather than the game if using this
|
||||||
|
feature.
|
||||||
|
|
||||||
|
cmake -B build -D HOTRELOAD=ON
|
||||||
|
cmake --build build
|
||||||
|
build/engine/engine assets/
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
add_library(engine engine.c)
|
if(${HOTRELOAD})
|
||||||
|
add_executable(engine engine.c)
|
||||||
|
else()
|
||||||
|
add_library(engine SHARED engine.c)
|
||||||
|
endif()
|
||||||
set_default_target_options(engine)
|
set_default_target_options(engine)
|
||||||
target_include_directories(engine PUBLIC include)
|
target_include_directories(engine PUBLIC include)
|
||||||
if(${PERFMON})
|
if(${PERFMON})
|
||||||
target_compile_definitions(engine PRIVATE PERFMON)
|
target_compile_definitions(engine PRIVATE PERFMON)
|
||||||
endif()
|
endif()
|
||||||
|
target_link_libraries(engine PRIVATE SDL2::SDL2)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "engine_hooks.h"
|
#include "engine_hooks.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -25,8 +26,44 @@ typedef struct {
|
|||||||
double max_start, max_evt, max_update, max_render, max_total;
|
double max_start, max_evt, max_update, max_render, max_total;
|
||||||
} perf_t;
|
} perf_t;
|
||||||
|
|
||||||
|
#ifdef HOTRELOAD
|
||||||
|
static void *game_lib;
|
||||||
|
static engineconf_t game_conf;
|
||||||
|
|
||||||
|
#define X(hook) static hook##_t *hook;
|
||||||
|
HOOKS_XLIST
|
||||||
|
#undef X
|
||||||
|
|
||||||
|
static void load_game_lib(void)
|
||||||
|
{
|
||||||
|
bool firstload = game_lib == NULL;
|
||||||
|
if (!firstload)
|
||||||
|
dlclose(game_lib);
|
||||||
|
|
||||||
|
game_lib = dlopen(GAMELIB, RTLD_NOW);
|
||||||
|
assert(game_lib);
|
||||||
|
|
||||||
|
// We're copying this rather than using it as a pointer so that
|
||||||
|
// the syntax to use it with/without hot reloading is the same.
|
||||||
|
engineconf_t *conf = (engineconf_t *)dlsym(game_lib, "game_conf");
|
||||||
|
if (!firstload)
|
||||||
|
assert(conf->memsize == game_conf.memsize);
|
||||||
|
memcpy(&game_conf, conf, sizeof(engineconf_t));
|
||||||
|
|
||||||
|
#define X(hook) \
|
||||||
|
hook = (hook##_t *)dlsym(game_lib, #hook); \
|
||||||
|
assert(hook);
|
||||||
|
HOOKS_XLIST
|
||||||
|
#undef X
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
#ifdef HOTRELOAD
|
||||||
|
load_game_lib();
|
||||||
|
#endif
|
||||||
|
|
||||||
int err = SDL_Init(SDL_INIT_VIDEO);
|
int err = SDL_Init(SDL_INIT_VIDEO);
|
||||||
assert(0 == err);
|
assert(0 == err);
|
||||||
|
|
||||||
@ -56,6 +93,10 @@ int main(int argc, char *argv[])
|
|||||||
// Handle all events currently in queue
|
// Handle all events currently in queue
|
||||||
SDL_Event evt;
|
SDL_Event evt;
|
||||||
while (SDL_PollEvent(&evt)) {
|
while (SDL_PollEvent(&evt)) {
|
||||||
|
#ifdef HOTRELOAD
|
||||||
|
if (evt.type == SDL_KEYDOWN && evt.key.keysym.sym == SDLK_F5)
|
||||||
|
load_game_lib();
|
||||||
|
#endif
|
||||||
if (game_evt(gamemem, &evt) != GAMESTATUS_OK)
|
if (game_evt(gamemem, &evt) != GAMESTATUS_OK)
|
||||||
goto quit;
|
goto quit;
|
||||||
}
|
}
|
||||||
|
@ -21,14 +21,28 @@ typedef enum {
|
|||||||
GAMESTATUS_QUIT,
|
GAMESTATUS_QUIT,
|
||||||
} gamestatus_t;
|
} gamestatus_t;
|
||||||
|
|
||||||
|
typedef void
|
||||||
|
game_init_t(int argc, char *argv[], void *mem, SDL_Renderer *renderer);
|
||||||
|
typedef void game_teardown_t(void *mem);
|
||||||
|
|
||||||
|
typedef gamestatus_t game_evt_t(void *mem, const SDL_Event *evt);
|
||||||
|
typedef gamestatus_t game_update_t(void *mem, double dt);
|
||||||
|
|
||||||
|
typedef void
|
||||||
|
game_render_t(const void *mem, SDL_Renderer *renderer, long unsigned t);
|
||||||
|
|
||||||
|
#define HOOKS_XLIST \
|
||||||
|
X(game_init) \
|
||||||
|
X(game_teardown) \
|
||||||
|
X(game_evt) \
|
||||||
|
X(game_update) \
|
||||||
|
X(game_render)
|
||||||
|
|
||||||
|
#ifndef HOTRELOAD
|
||||||
extern const engineconf_t game_conf;
|
extern const engineconf_t game_conf;
|
||||||
|
#define X(hook) hook##_t hook;
|
||||||
void game_init(int argc, char *argv[], void *mem, SDL_Renderer *renderer);
|
HOOKS_XLIST
|
||||||
void game_teardown(void *mem);
|
#undef X
|
||||||
|
#endif
|
||||||
gamestatus_t game_evt(void *mem, const SDL_Event *evt);
|
|
||||||
gamestatus_t game_update(void *mem, double dt);
|
|
||||||
|
|
||||||
void game_render(const void *mem, SDL_Renderer *renderer, long unsigned t);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
add_executable(game main.c)
|
if(${HOTRELOAD})
|
||||||
|
add_library(game SHARED main.c)
|
||||||
|
get_target_property(ENGINE_INCLUDES engine INCLUDE_DIRECTORIES)
|
||||||
|
target_include_directories(game PRIVATE ${ENGINE_INCLUDES})
|
||||||
|
else()
|
||||||
|
add_executable(game main.c)
|
||||||
|
target_link_libraries(game PRIVATE engine)
|
||||||
|
endif()
|
||||||
set_default_target_options(game)
|
set_default_target_options(game)
|
||||||
target_include_directories(game PUBLIC include)
|
target_include_directories(game PUBLIC include)
|
||||||
|
|
||||||
target_link_libraries(game PRIVATE
|
target_link_libraries(game PRIVATE
|
||||||
engine
|
|
||||||
m
|
m
|
||||||
LibXml2::LibXml2
|
LibXml2::LibXml2
|
||||||
SDL2::SDL2
|
SDL2::SDL2
|
||||||
SDL2::SDL2main
|
|
||||||
SDL2_image::SDL2_image
|
SDL2_image::SDL2_image
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user