From 2f36d9ea3c9ecdd4f24d9b996006127716e8cd42 Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Tue, 27 Aug 2024 00:33:37 +0300 Subject: [PATCH] windows build compatibility, twn_game_object as abstraction for bridging game code and the engine --- CMakeLists.txt | 112 +++++++------ apps/testgame/scenes/title.c | 6 +- townengine/audio.h | 10 +- townengine/context.h | 3 +- townengine/game_api.h | 8 +- .../game_object/twn_linux_game_object_c.h | 137 ++++++++++++++++ .../game_object/twn_static_game_object_c.h | 21 +++ .../game_object/twn_win32_game_object_c.h | 83 ++++++++++ townengine/input.h | 31 ++-- townengine/null.c | 2 - townengine/rendering.h | 19 +-- townengine/textures/internal_api.h | 28 ++-- townengine/twn_engine_api.h | 10 ++ townengine/twn_game_object.c | 12 ++ townengine/twn_game_object.h | 22 +++ townengine/{main.c => twn_loop.c} | 147 +----------------- townengine/twn_loop.h | 10 ++ townengine/twn_main.c | 11 ++ townengine/util.h | 43 ++--- 19 files changed, 454 insertions(+), 261 deletions(-) create mode 100644 townengine/game_object/twn_linux_game_object_c.h create mode 100644 townengine/game_object/twn_static_game_object_c.h create mode 100644 townengine/game_object/twn_win32_game_object_c.h delete mode 100644 townengine/null.c create mode 100644 townengine/twn_engine_api.h create mode 100644 townengine/twn_game_object.c create mode 100644 townengine/twn_game_object.h rename townengine/{main.c => twn_loop.c} (78%) create mode 100644 townengine/twn_loop.h create mode 100644 townengine/twn_main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e140346..9d83e9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,10 +2,6 @@ cmake_minimum_required(VERSION 3.21) project(townengine LANGUAGES C) -list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - # dependencies find_package(SDL2 REQUIRED GLOBAL) find_package(SDL2_image REQUIRED GLOBAL) @@ -15,14 +11,14 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif () -set(TOWNENGINE_TARGET townengine CACHE INTERNAL "") -set(TOWNENGINE_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "") +set(TWN_TARGET townengine CACHE INTERNAL "") +set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "") -option(TOWNENGINE_HOT_RELOAD "Enable hot reloading support" ON) -option(TOWNENGINE_ARCHIVE_DATA "Enable archival of assets" OFF) +option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON) +option(TWN_ARCHIVE_DATA "Enable archival of assets" OFF) # add -fPIC globally so that it's linked well -add_compile_options($<$:-fPIC>) +add_compile_options($<$:-fPIC>) set(PHYSFS_BUILD_SHARED FALSE) set(PHYSFS_DISABLE_INSTALL TRUE) @@ -45,12 +41,13 @@ else() set(SYSTEM_SOURCE_FILES) endif() -set(TOWNENGINE_SOURCE_FILES +set(TWN_SOURCE_FILES third-party/physfs/extras/physfsrwops.c third-party/stb/stb_vorbis.c third-party/glad/src/glad.c - townengine/main.c + townengine/twn_loop.c + townengine/twn_main.c townengine/config.h townengine/context/context.c townengine/context.h townengine/audio/audio.c townengine/audio.h @@ -59,25 +56,29 @@ set(TOWNENGINE_SOURCE_FILES townengine/input/input.c townengine/input.h townengine/camera.c townengine/camera.h townengine/textures/textures.c + townengine/twn_game_object.c + + $<$>:townengine/twn_main.c> ${SYSTEM_SOURCE_FILES}) -list(TRANSFORM TOWNENGINE_SOURCE_FILES PREPEND ${TOWNENGINE_ROOT_DIR}/) +list(TRANSFORM TWN_SOURCE_FILES PREPEND ${TWN_ROOT_DIR}/) # base engine code, reused for games and tools -if (TOWNENGINE_HOT_RELOAD) - add_library(${TOWNENGINE_TARGET} SHARED ${TOWNENGINE_SOURCE_FILES}) +if (TWN_FEATURE_DYNLIB_GAME) + add_library(${TWN_TARGET} SHARED ${TWN_SOURCE_FILES}) else () - add_library(${TOWNENGINE_TARGET} STATIC ${TOWNENGINE_SOURCE_FILES}) + add_library(${TWN_TARGET} STATIC ${TWN_SOURCE_FILES}) endif () -source_group(TREE ${TOWNENGINE_ROOT_DIR} FILES ${TOWNENGINE_SOURCE_FILES}) -set_target_properties(${TOWNENGINE_TARGET} PROPERTIES +source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_SOURCE_FILES}) + +set_target_properties(${TWN_TARGET} PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON C_EXTENSIONS ON) # extensions are required by stb_ds.h -target_precompile_headers(${TOWNENGINE_TARGET} PRIVATE +target_precompile_headers(${TWN_TARGET} PRIVATE third-party/glad/include/glad/glad.h third-party/stb/stb_ds.h) @@ -163,7 +164,7 @@ function(give_options target) ORGANIZATION_NAME="${ORGANIZATION_NAME}" APP_NAME="${APP_NAME}" PACKAGE_EXTENSION="${PACKAGE_EXTENSION}" - $<$:HOT_RELOAD_SUPPORT>) + $<$:TWN_FEATURE_DYNLIB_GAME>) endfunction() @@ -178,18 +179,17 @@ function(include_deps target) third-party/stb third-party/x-watcher) - list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TOWNENGINE_ROOT_DIR}/) + list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TWN_ROOT_DIR}/) target_include_directories(${target} SYSTEM PRIVATE ${THIRD_PARTY_INCLUDES}) # allow access to headers from any point in source tree - target_include_directories(${target} PRIVATE ${TOWNENGINE_ROOT_DIR}) + target_include_directories(${target} PRIVATE ${TWN_ROOT_DIR}) endfunction() function(link_deps target) target_link_libraries(${target} PUBLIC SDL2::SDL2 - SDL2::SDL2main SDL2_image::SDL2_image SDL2_ttf::SDL2_ttf physfs-static @@ -198,22 +198,24 @@ endfunction() function(use_townengine target sources output_directory data_dir) - if (TOWNENGINE_HOT_RELOAD) + if (TWN_FEATURE_DYNLIB_GAME) # game shared library, for reloading add_library(${target}_game SHARED ${sources}) - set_target_properties(${target}_game PROPERTIES - OUTPUT_NAME game - LIBRARY_OUTPUT_DIRECTORY ${output_directory}) give_options(${target}_game) include_deps(${target}_game) - target_link_libraries(${target}_game PUBLIC SDL2::SDL2) + target_link_libraries(${target}_game PUBLIC SDL2::SDL2 ${TWN_TARGET}) + set_target_properties(${target}_game PROPERTIES + OUTPUT_NAME game + LIBRARY_OUTPUT_DIRECTORY ${output_directory} + RUNTIME_OUTPUT_DIRECTORY ${output_directory}) # launcher binary, loads game and engine shared library - add_executable(${target}_app ${TOWNENGINE_ROOT_DIR}/townengine/null.c) + add_executable(${target}_app ${TWN_ROOT_DIR}/townengine/twn_main.c) + # todo: copy instead? # put libtownengine.so alongside the binary - set_target_properties(${TOWNENGINE_TARGET} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${output_directory}) + set_target_properties(${TWN_TARGET} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${output_directory}) else () add_executable(${target}_app ${sources}) endif () @@ -231,42 +233,51 @@ function(use_townengine target sources output_directory data_dir) give_options(${target}_app) include_deps(${target}_app) link_deps(${target}_app) - target_link_libraries(${target}_app PUBLIC ${TOWNENGINE_TARGET}) + target_link_libraries(${target}_app PUBLIC ${TWN_TARGET}) set_target_properties(${target}_app PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${output_directory}) # copy dlls for baby windows add_custom_command(TARGET ${target}_app POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy -t $ - $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ + $ COMMAND_EXPAND_LISTS) - if (UNIX) - # create a bootstrapping script - set(TOWNENGINE_BOOTSTRAP_EXEC_ARGS - "$,--data-dir ./data.${PACKAGE_EXTENSION},--data-dir ${data_dir}>") + set(TWN_BOOTSTRAP_EXEC_ARGS + "$,--data-dir ./data.${PACKAGE_EXTENSION},--data-dir ${data_dir}>") - string(JOIN "\n" TOWNENGINE_BOOTSTRAP - "#!/bin/env sh" - "cd \"$(dirname \"$0\")\"" - "LD_LIBRARY_PATH=./ ./launcher ${TOWNENGINE_BOOTSTRAP_EXEC_ARGS}" + string(JOIN "\n" TWN_BOOTSTRAP_SH + "#!/bin/env sh" + "cd \"$(dirname \"$0\")\"" + "LD_LIBRARY_PATH=./ ./launcher ${TWN_BOOTSTRAP_EXEC_ARGS}" + "") + + FILE(GENERATE OUTPUT ${output_directory}/${target} + CONTENT "${TWN_BOOTSTRAP_SH}" + FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ) + + if (WIN32) + string(JOIN "\n" TWN_BOOTSTRAP_BAT + "pushd \"%~dp0\"" + "launcher ${TWN_BOOTSTRAP_EXEC_ARGS}" "") - FILE(GENERATE OUTPUT ${output_directory}/${target} - CONTENT "${TOWNENGINE_BOOTSTRAP}" + + FILE(GENERATE OUTPUT ${output_directory}/${target}.bat + CONTENT "${TWN_BOOTSTRAP_BAT}" FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ) endif () - if (TOWNENGINE_ARCHIVE_DATA) + if (TWN_ARCHIVE_DATA) # zip up assets add_custom_command(TARGET ${target}_app POST_BUILD - COMMAND cd ${data_dir} && zip -r ${output_directory}/data.${PACKAGE_EXTENSION} ./* + COMMAND cd ${data_dir} && ${CMAKE_COMMAND} -E tar "cf" ${output_directory}/data.${PACKAGE_EXTENSION} --format=zip ./ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif () endfunction() -give_options(${TOWNENGINE_TARGET}) -include_deps(${TOWNENGINE_TARGET}) -link_deps(${TOWNENGINE_TARGET}) +give_options(${TWN_TARGET}) +include_deps(${TWN_TARGET}) +link_deps(${TWN_TARGET}) # build the testgame if this cmake list is built directly if (${CMAKE_PROJECT_NAME} MATCHES townengine) @@ -274,6 +285,7 @@ if (${CMAKE_PROJECT_NAME} MATCHES townengine) endif () # move compie_commands.json into root directory so that it plays nicer with editors -add_custom_command(TARGET ${TOWNENGINE_TARGET} POST_BUILD +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +add_custom_command(TARGET ${TWN_TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json - ${TOWNENGINE_ROOT_DIR}) + ${TWN_ROOT_DIR}) diff --git a/apps/testgame/scenes/title.c b/apps/testgame/scenes/title.c index 02b8fc6..5807e13 100644 --- a/apps/testgame/scenes/title.c +++ b/apps/testgame/scenes/title.c @@ -5,6 +5,8 @@ #include "townengine/game_api.h" +#include + static void title_tick(struct state *state) { struct scene_title *scn = (struct scene_title *)state->scene; @@ -20,9 +22,9 @@ static void title_tick(struct state *state) { /* draw the tick count as an example of dynamic text */ - size_t text_str_len = snprintf(NULL, 0, "%ld", state->ctx->tick_count) + 1; + size_t text_str_len = snprintf(NULL, 0, "%lld", state->ctx->tick_count) + 1; char *text_str = cmalloc(text_str_len); - snprintf(text_str, text_str_len, "%ld", state->ctx->tick_count); + snprintf(text_str, text_str_len, "%lld", state->ctx->tick_count); const char *font = "fonts/kenney-pixel.ttf"; int text_h = 32; diff --git a/townengine/audio.h b/townengine/audio.h index 86b6fa1..2e25c89 100644 --- a/townengine/audio.h +++ b/townengine/audio.h @@ -1,6 +1,8 @@ #ifndef AUDIO_H #define AUDIO_H +#include "twn_engine_api.h" + #include @@ -23,14 +25,14 @@ typedef struct play_audio_args { /* path must contain valid file extension to infer which file format it is */ /* supported formats: .ogg, .xm */ /* preserves args that are already specified on the channel */ -void play_audio(const char *path, const char *channel); +TWN_API void play_audio(const char *path, const char *channel); -void play_audio_ex(const char *path, const char *channel, t_play_audio_args args); +TWN_API void play_audio_ex(const char *path, const char *channel, t_play_audio_args args); /* could be used for modifying args */ /* warn: is only valid if no other calls to audio are made */ -t_play_audio_args *get_audio_args(const char *channel); +TWN_API t_play_audio_args *get_audio_args(const char *channel); -t_play_audio_args get_default_audio_args(void); +TWN_API t_play_audio_args get_default_audio_args(void); #endif diff --git a/townengine/context.h b/townengine/context.h index f393c0f..5a9b524 100644 --- a/townengine/context.h +++ b/townengine/context.h @@ -5,6 +5,7 @@ #include "rendering/internal_api.h" #include "textures/internal_api.h" #include "input.h" +#include "twn_engine_api.h" #include @@ -65,6 +66,6 @@ typedef struct context { bool initialization_needed; } t_ctx; -extern t_ctx ctx; +TWN_API extern t_ctx ctx; #endif diff --git a/townengine/game_api.h b/townengine/game_api.h index 7ffeb9b..9ba0650 100644 --- a/townengine/game_api.h +++ b/townengine/game_api.h @@ -8,15 +8,15 @@ #include "townengine/audio.h" #include "townengine/util.h" #include "townengine/input.h" +#include "twn_engine_api.h" -#ifndef HOT_RELOAD_SUPPORT /* sole game logic and display function. all state must be used from and saved to supplied state pointer. */ -extern void game_tick(void); +TWN_API extern void game_tick(void); /* called when application is closing. */ -extern void game_end(void); -#endif +TWN_API extern void game_end(void); + #endif diff --git a/townengine/game_object/twn_linux_game_object_c.h b/townengine/game_object/twn_linux_game_object_c.h new file mode 100644 index 0000000..dab0712 --- /dev/null +++ b/townengine/game_object/twn_linux_game_object_c.h @@ -0,0 +1,137 @@ +#include "townengine/twn_game_object.h" +#include "townengine/context.h" + +#include +#include + +#include + + +#define GAME_OBJECT_PATH "./libgame.so" +#define MODIFIED_TICKS_MERGED 10 + + +static void (*game_tick_callback)(void); +static void (*game_end_callback)(void); + +static x_watcher *watcher; +static void *handle = NULL; + +static uint64_t last_tick_modified; +static bool loaded_after_modification = true; +static SDL_mutex *lock; + + +static void load_game_object(void) { + /* needs to be closed otherwise symbols aren't resolved again */ + if (handle) { + dlclose(handle); + handle = NULL; + game_tick_callback = NULL; + game_end_callback = NULL; + } + + void *new_handle = dlopen(GAME_OBJECT_PATH, RTLD_LAZY); + if (!new_handle) { + log_critical("Hot Reload Error: Cannot open game code shared object (%s)", dlerror()); + goto ERR_OPENING_SO; + } + + game_tick_callback = (void (*)(void))dlsym(new_handle, "game_tick"); + if (!game_tick_callback) { + CRY("Hot Reload Error", "game_tick_callback() symbol wasn't found"); + goto ERR_GETTING_PROC; + } + + game_end_callback = (void (*)(void))dlsym(new_handle, "game_end"); + if (!game_end_callback) { + CRY("Hot Reload Error", "game_end_callback() symbol wasn't found"); + goto ERR_GETTING_PROC; + } + + handle = new_handle; + + if (ctx.tick_count != 0) + log_info("Game object was reloaded\n"); + + return; + +ERR_GETTING_PROC: + dlclose(new_handle); + game_tick_callback = NULL; + game_end_callback = NULL; + +ERR_OPENING_SO: + SDL_UnlockMutex(lock); + +} + +static void watcher_callback(XWATCHER_FILE_EVENT event, + const char *path, + int context, + void *data) +{ + (void)context; + (void)path; + (void)data; + + switch(event) { + case XWATCHER_FILE_MODIFIED: + SDL_LockMutex(lock); + last_tick_modified = ctx.tick_count; + loaded_after_modification = false; + SDL_UnlockMutex(lock); + break; + + default: + break; + } +} + + +void game_object_load(void) { + watcher = xWatcher_create(); + + xWatcher_reference dir; + dir.path = GAME_OBJECT_PATH; + dir.callback_func = watcher_callback; + + xWatcher_appendFile(watcher, &dir); + xWatcher_start(watcher); + + lock = SDL_CreateMutex(); + load_game_object(); +} + + +void game_object_unload(void) { + game_end_callback(); + xWatcher_destroy(watcher); + watcher = NULL; + dlclose(handle); + handle = NULL; + game_tick_callback = NULL; + game_end_callback = NULL; + SDL_DestroyMutex(lock); +} + + +bool game_object_try_reloading(void) { + bool result = false; + + /* only load the modified library after some time, as compilers make a lot of modifications */ + SDL_LockMutex(lock); + if (ctx.tick_count - last_tick_modified > MODIFIED_TICKS_MERGED && + !loaded_after_modification) { + load_game_object(); + loaded_after_modification = true; + result = true; + } SDL_UnlockMutex(lock); + + return result; +} + + +void game_object_tick(void) { + game_tick_callback(); +} diff --git a/townengine/game_object/twn_static_game_object_c.h b/townengine/game_object/twn_static_game_object_c.h new file mode 100644 index 0000000..adeb7f4 --- /dev/null +++ b/townengine/game_object/twn_static_game_object_c.h @@ -0,0 +1,21 @@ +#include "townengine/twn_game_object.h" +#include "townengine/game_api.h" + + +void game_object_load(void) { +} + + +void game_object_unload(void) { + game_end(); +} + + +bool game_object_try_reloading(void) { + return false; +} + + +void game_object_tick(void) { + game_tick(); +} diff --git a/townengine/game_object/twn_win32_game_object_c.h b/townengine/game_object/twn_win32_game_object_c.h new file mode 100644 index 0000000..32006e0 --- /dev/null +++ b/townengine/game_object/twn_win32_game_object_c.h @@ -0,0 +1,83 @@ +#include "townengine/twn_game_object.h" +#include "townengine/context.h" + +#include +#include + + +#define GAME_OBJECT_PATH "libgame.dll" + + +static void (*game_tick_callback)(void); +static void (*game_end_callback)(void); + +static void *handle = NULL; + + +static void load_game_object(void) { + /* needs to be closed otherwise symbols aren't resolved again */ + if (handle) { + FreeLibrary(handle); + handle = NULL; + game_tick_callback = NULL; + game_end_callback = NULL; + } + + void *new_handle = LoadLibraryA(GAME_OBJECT_PATH); + if (!new_handle) { + log_critical("Hot Reload Error: Cannot open game code shared object (%s)", GetLastError()); + goto ERR_OPENING_SO; + } + + game_tick_callback = (void (*)(void))GetProcAddress(new_handle, "game_tick"); + if (!game_tick_callback) { + CRY("Hot Reload Error", "game_tick_callback() symbol wasn't found"); + goto ERR_GETTING_PROC; + } + + game_end_callback = (void (*)(void))GetProcAddress(new_handle, "game_end"); + if (!game_end_callback) { + CRY("Hot Reload Error", "game_end_callback() symbol wasn't found"); + goto ERR_GETTING_PROC; + } + + handle = new_handle; + + if (ctx.tick_count != 0) + log_info("Game object was reloaded\n"); + + return; + +ERR_GETTING_PROC: + FreeLibrary(new_handle); + game_tick_callback = NULL; + game_end_callback = NULL; + +ERR_OPENING_SO: + die_abruptly(); +} + + +void game_object_load(void) { + load_game_object(); +} + + +void game_object_unload(void) { + game_end_callback(); + FreeLibrary(handle); + handle = NULL; + game_tick_callback = NULL; + game_end_callback = NULL; +} + + +/* doesn't support reloading because of problems with file watching */ +bool game_object_try_reloading(void) { + return false; +} + + +void game_object_tick(void) { + game_tick_callback(); +} diff --git a/townengine/input.h b/townengine/input.h index 2f1c489..226c91a 100644 --- a/townengine/input.h +++ b/townengine/input.h @@ -4,6 +4,7 @@ #include "config.h" #include "vec.h" #include "util.h" +#include "twn_engine_api.h" #include @@ -68,33 +69,33 @@ struct input_state { }; -void input_state_init(struct input_state *input); -void input_state_deinit(struct input_state *input); -void input_state_update(struct input_state *input); +TWN_API void input_state_init(struct input_state *input); +TWN_API void input_state_deinit(struct input_state *input); +TWN_API void input_state_update(struct input_state *input); -void input_bind_action_scancode(struct input_state *input, +TWN_API void input_bind_action_scancode(struct input_state *input, char *action_name, SDL_Scancode scancode); -void input_unbind_action_scancode(struct input_state *input, +TWN_API void input_unbind_action_scancode(struct input_state *input, char *action_name, SDL_Scancode scancode); -void input_bind_action_mouse(struct input_state *input, +TWN_API void input_bind_action_mouse(struct input_state *input, char *action_name, uint8_t mouse_button); -void input_unbind_action_mouse(struct input_state *input, +TWN_API void input_unbind_action_mouse(struct input_state *input, char *action_name, uint8_t mouse_button); -void input_add_action(struct input_state *input, char *action_name); -void input_delete_action(struct input_state *input, char *action_name); +TWN_API void input_add_action(struct input_state *input, char *action_name); +TWN_API void input_delete_action(struct input_state *input, char *action_name); -bool input_is_action_pressed(struct input_state *input, char *action_name); -bool input_is_action_just_pressed(struct input_state *input, char *action_name); -bool input_is_action_just_released(struct input_state *input, char *action_name); +TWN_API bool input_is_action_pressed(struct input_state *input, char *action_name); +TWN_API bool input_is_action_just_pressed(struct input_state *input, char *action_name); +TWN_API bool input_is_action_just_released(struct input_state *input, char *action_name); -t_fvec2 input_get_action_position(struct input_state *input, char *action_name); +TWN_API t_fvec2 input_get_action_position(struct input_state *input, char *action_name); -void input_set_mouse_captured(struct input_state *input, bool value); -bool input_is_mouse_captured(struct input_state *input); +TWN_API void input_set_mouse_captured(struct input_state *input, bool value); +TWN_API bool input_is_mouse_captured(struct input_state *input); #endif diff --git a/townengine/null.c b/townengine/null.c deleted file mode 100644 index 465596d..0000000 --- a/townengine/null.c +++ /dev/null @@ -1,2 +0,0 @@ -/* cmake nonsense */ -struct a { int b; }; diff --git a/townengine/rendering.h b/townengine/rendering.h index 1efeb18..c602ffd 100644 --- a/townengine/rendering.h +++ b/townengine/rendering.h @@ -4,6 +4,7 @@ #include "util.h" #include "macros/option.h" #include "camera.h" +#include "twn_engine_api.h" #include @@ -25,24 +26,24 @@ typedef struct push_sprite_args { /* pushes a sprite onto the sprite render queue */ /* this is a simplified version of push_sprite_ex for the most common case. */ /* it assumes you want no color modulation, no rotation, no flip */ -void push_sprite(t_push_sprite_args args); +TWN_API void push_sprite(t_push_sprite_args args); #define m_sprite(...) (push_sprite((t_push_sprite_args){__VA_ARGS__})) /* pushes a filled rectangle onto the rectangle render queue */ -void push_rectangle(t_frect rect, t_color color); +TWN_API void push_rectangle(t_frect rect, t_color color); /* pushes a filled circle onto the circle render queue */ -void push_circle(t_fvec2 position, float radius, t_color color); +TWN_API void push_circle(t_fvec2 position, float radius, t_color color); -void text_cache_init(struct text_cache *cache); -void text_cache_deinit(struct text_cache *cache); -void push_text(char *string, t_fvec2 position, int height_px, t_color color, const char *font_path); -int get_text_width(char *string, int height_px, const char *font_path); +TWN_API void text_cache_init(struct text_cache *cache); +TWN_API void text_cache_deinit(struct text_cache *cache); +TWN_API void push_text(char *string, t_fvec2 position, int height_px, t_color color, const char *font_path); +TWN_API int get_text_width(char *string, int height_px, const char *font_path); /* pushes a textured 3d triangle onto the render queue */ /* vertices are in absolute coordinates, relative to world origin */ /* texture coordinates are in pixels */ -void unfurl_triangle(const char *path, +TWN_API void unfurl_triangle(const char *path, t_fvec3 v0, t_fvec3 v1, t_fvec3 v2, @@ -70,6 +71,6 @@ void unfurl_triangle(const char *path, // t_frect uvs); /* pushes a camera state to be used for all future unfurl_* commands */ -void set_camera(const t_camera *camera); +TWN_API void set_camera(const t_camera *camera); #endif diff --git a/townengine/textures/internal_api.h b/townengine/textures/internal_api.h index b2ad139..a0af666 100644 --- a/townengine/textures/internal_api.h +++ b/townengine/textures/internal_api.h @@ -3,6 +3,7 @@ #include "../util.h" #include "../textures/modes.h" +#include "../twn_engine_api.h" #include #include @@ -46,11 +47,11 @@ typedef struct { uint16_t id; } t_texture_key; /* tests whether given key structure corresponds to any texture */ #define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1) -void textures_cache_init(struct texture_cache *cache, SDL_Window *window); -void textures_cache_deinit(struct texture_cache *cache); +TWN_API void textures_cache_init(struct texture_cache *cache, SDL_Window *window); +TWN_API void textures_cache_deinit(struct texture_cache *cache); /* for debugging */ -void textures_dump_atlases(struct texture_cache *cache); +TWN_API void textures_dump_atlases(struct texture_cache *cache); /* loads an image if it isn't in the cache, otherwise a no-op. */ /* can be called from anywhere at any time after init, useful if you want to */ @@ -60,33 +61,34 @@ void textures_dump_atlases(struct texture_cache *cache); /* repacks the current texture atlas based on the texture cache if needed */ /* any previously returned srcrect results are invalidated after that */ /* call it every time before rendering */ -void textures_update_atlas(struct texture_cache *cache); +TWN_API void textures_update_atlas(struct texture_cache *cache); /* returns a persistent handle to some texture in cache, loading it if needed */ /* check the result with m_texture_key_is_valid() */ -t_texture_key textures_get_key(struct texture_cache *cache, const char *path); +TWN_API t_texture_key textures_get_key(struct texture_cache *cache, const char *path); /* returns a rect in a texture cache of the given key */ -t_frect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key); +TWN_API t_frect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key); /* returns a rect of dimensions of the whole texture (whole atlas) */ -t_frect textures_get_dims(const struct texture_cache *cache, t_texture_key key); +TWN_API t_frect textures_get_dims(const struct texture_cache *cache, t_texture_key key); /* returns an identifier that is equal for all textures placed in the same atlas */ -int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key key); +TWN_API int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key key); /* binds atlas texture in opengl state */ -void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target); +TWN_API void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target); /* binds texture in opengl state, ensuring that it's usable with texture repeat */ -void textures_bind_repeating(const struct texture_cache *cache, t_texture_key key, GLenum target); +TWN_API void textures_bind_repeating(const struct texture_cache *cache, t_texture_key key, GLenum target); /* returns helpful information about contents of alpha channel in given texture */ -enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key); +TWN_API enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key); /* returns the number of atlases in the cache */ -size_t textures_get_num_atlases(const struct texture_cache *cache); +TWN_API size_t textures_get_num_atlases(const struct texture_cache *cache); -void textures_reset_state(void); +/* TODO: should recieve texture_cache, get_key optimization cache should be cleared some other way */ +TWN_API void textures_reset_state(void); #endif diff --git a/townengine/twn_engine_api.h b/townengine/twn_engine_api.h new file mode 100644 index 0000000..adbeffd --- /dev/null +++ b/townengine/twn_engine_api.h @@ -0,0 +1,10 @@ +#ifndef TWN_ENGINE_API_H +#define TWN_ENGINE_API_H + +#if defined(__WIN32) + #define TWN_API __declspec(dllexport) +#else + #define TWN_API +#endif + +#endif diff --git a/townengine/twn_game_object.c b/townengine/twn_game_object.c new file mode 100644 index 0000000..1431033 --- /dev/null +++ b/townengine/twn_game_object.c @@ -0,0 +1,12 @@ +#if defined(TWN_FEATURE_DYNLIB_GAME) + #if defined(__linux__) + #include "game_object/twn_linux_game_object_c.h" + #elif defined(_WIN32) + #include "game_object/twn_win32_game_object_c.h" + #else + #warning "TWN_FEATURE_DYNLIB_GAME is set, but not supported" + #include "game_object/twn_static_game_object_c.h" + #endif +#else + #include "game_object/twn_static_game_object_c.h" +#endif diff --git a/townengine/twn_game_object.h b/townengine/twn_game_object.h new file mode 100644 index 0000000..47519aa --- /dev/null +++ b/townengine/twn_game_object.h @@ -0,0 +1,22 @@ +#ifndef GAME_OBJECT_H +#define GAME_OBJECT_H + +#include + +/* + * game object provides an interface for bridging between game code and engine. + * for example, it might implement dynamic load libraries with hot reloading. + */ + + +void game_object_load(void); + +void game_object_unload(void); + +/* returns true if reload happened, otherwise false */ +bool game_object_try_reloading(void); + +void game_object_tick(void); + + +#endif diff --git a/townengine/main.c b/townengine/twn_loop.c similarity index 78% rename from townengine/main.c rename to townengine/twn_loop.c index 48e6fcb..cfa2cdc 100644 --- a/townengine/main.c +++ b/townengine/twn_loop.c @@ -1,8 +1,9 @@ +#include "twn_loop.h" #include "townengine/context.h" #include "townengine/rendering.h" #include "townengine/input/internal_api.h" #include "townengine/util.h" -#include "townengine/game_api.h" +#include "townengine/twn_game_object.h" #include "townengine/audio/internal_api.h" #include "townengine/textures/internal_api.h" #include "townengine/rendering/internal_api.h" @@ -20,30 +21,6 @@ #include #include -#ifdef HOT_RELOAD_SUPPORT -#include -#include - -#ifdef __linux__ - #define GAME_OBJECT_PATH "./libgame.so" -#else - #define GAME_OBJECT_PATH "game.dll" -#endif -#endif - - -#ifdef HOT_RELOAD_SUPPORT - -static void (*game_tick)(void); -static void (*game_end)(void); - -#endif - -/* is used to lock x-watcher driven hot reloading */ -static uint64_t game_object_last_time_modified; -static bool game_object_loaded_after_modification = true; -static SDL_mutex *game_object_mutex; - static void poll_events(void) { SDL_Event e; @@ -192,7 +169,7 @@ static void main_loop(void) { input_state_update(&ctx.input); - game_tick(); + game_object_tick(); ctx.frame_accumulator -= ctx.desired_frametime; ctx.tick_count = (ctx.tick_count % ULLONG_MAX) + 1; @@ -375,8 +352,6 @@ static bool initialize(void) { } */ - game_object_mutex = SDL_CreateMutex(); - return true; fail: @@ -404,8 +379,6 @@ static void clean_up(void) { text_cache_deinit(&ctx.text_cache); textures_cache_deinit(&ctx.texture_cache); - SDL_DestroyMutex(game_object_mutex); - PHYSFS_deinit(); TTF_Quit(); IMG_Quit(); @@ -413,114 +386,13 @@ static void clean_up(void) { } -#ifdef HOT_RELOAD_SUPPORT - -static void load_game_shared_object(void) { -#ifdef __linux__ - static void *handle = NULL; - - SDL_LockMutex(game_object_mutex); - - /* needs to be closed otherwise symbols aren't resolved again */ - if (handle) { - dlclose(handle); - handle = NULL; - game_tick = NULL; - game_end = NULL; - } - - void *new_handle = dlopen(GAME_OBJECT_PATH, RTLD_LAZY); - if (!new_handle) { - log_critical("Hot Reload Error: Cannot open game code shared object: %s", dlerror()); - goto ERR_OPENING_SO; - } - - game_tick = (void (*)(void))dlsym(new_handle, "game_tick"); - if (!game_tick) { - CRY("Hot Reload Error", "game_tick() symbol wasn't found"); - goto ERR_GETTING_PROC; - } - - game_end = (void (*)(void))dlsym(new_handle, "game_end"); - if (!game_end) { - CRY("Hot Reload Error", "game_end() symbol wasn't found"); - goto ERR_GETTING_PROC; - } - - handle = new_handle; - - SDL_UnlockMutex(game_object_mutex); - - log_info("Game code hot loaded\n"); - - return; - -ERR_GETTING_PROC: - dlclose(new_handle); - game_tick = NULL; - game_end = NULL; - -ERR_OPENING_SO: - SDL_UnlockMutex(game_object_mutex); - -#endif -} - -static void hot_reload_callback(XWATCHER_FILE_EVENT event, - const char *path, - int context, - void *data) -{ - (void)context; - (void)path; - (void)data; - - switch(event) { - case XWATCHER_FILE_MODIFIED: - SDL_LockMutex(game_object_mutex); - game_object_last_time_modified = ctx.tick_count; - game_object_loaded_after_modification = false; - SDL_UnlockMutex(game_object_mutex); - break; - - default: - break; - } -} - -#else - -static void load_game_shared_object(void) {} - -#endif - - -static void load_game(void) { -#ifdef HOT_RELOAD_SUPPORT - x_watcher *watcher = xWatcher_create(); - - xWatcher_reference dir; - dir.path = GAME_OBJECT_PATH; - dir.callback_func = hot_reload_callback; - - xWatcher_appendFile(watcher, &dir); - xWatcher_start(watcher); - - // xWatcher_destroy(watcher); - - load_game_shared_object(); - -#endif -} - - static void reset_state(void) { input_reset_state(&ctx.input); textures_reset_state(); } -int main(int argc, char **argv) { +int enter_loop(int argc, char **argv) { ctx.argc = argc; ctx.argv = argv; @@ -537,26 +409,21 @@ int main(int argc, char **argv) { } } - load_game(); + game_object_load(); ctx.was_successful = true; ctx.initialization_needed = true; while (ctx.is_running) { - /* only load the modified library after some time, as compilers make a lot of modifications */ - SDL_LockMutex(game_object_mutex); - if (ctx.tick_count - game_object_last_time_modified > 10 && !game_object_loaded_after_modification) { - load_game_shared_object(); - game_object_loaded_after_modification = true; + if (game_object_try_reloading()) { ctx.initialization_needed = true; reset_state(); } - SDL_UnlockMutex(game_object_mutex); main_loop(); } - game_end(); + game_object_unload(); clean_up(); diff --git a/townengine/twn_loop.h b/townengine/twn_loop.h new file mode 100644 index 0000000..4eb5969 --- /dev/null +++ b/townengine/twn_loop.h @@ -0,0 +1,10 @@ +#ifndef TWN_LOOP_H +#define TWN_LOOP_H + +#include "twn_engine_api.h" + + +TWN_API int enter_loop(int argc, char **argv); + + +#endif diff --git a/townengine/twn_main.c b/townengine/twn_main.c new file mode 100644 index 0000000..36bca00 --- /dev/null +++ b/townengine/twn_main.c @@ -0,0 +1,11 @@ +#include "twn_loop.h" + +#define SDL_MAIN_HANDLED +#include + + +int main(int argc, char **argv) { + SDL_SetMainReady(); + + enter_loop(argc, argv); +} diff --git a/townengine/util.h b/townengine/util.h index 9cb0a87..bbc9a83 100644 --- a/townengine/util.h +++ b/townengine/util.h @@ -2,6 +2,7 @@ #define UTIL_H #include "townengine/vec.h" +#include "twn_engine_api.h" #include #include @@ -17,28 +18,28 @@ /* GENERAL UTILITIES */ /* */ -void cry_impl(const char *file, const int line, const char *title, const char *text); +TWN_API void cry_impl(const char *file, const int line, const char *title, const char *text); #define CRY(title, text) cry_impl(__FILE__, __LINE__, title, text) #define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError()) #define CRY_PHYSFS(title) \ cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())) -void log_info(const char *restrict format, ...); -void log_critical(const char *restrict format, ...); -void log_warn(const char *restrict format, ...); +TWN_API void log_info(const char *restrict format, ...); +TWN_API void log_critical(const char *restrict format, ...); +TWN_API void log_warn(const char *restrict format, ...); /* for when there's absolutely no way to continue */ -noreturn void die_abruptly(void); +TWN_API noreturn void die_abruptly(void); /* "critical" allocation functions which will log and abort() on failure. */ /* if it is reasonable to handle this gracefully, use the standard versions. */ /* the stb implementations will be configured to use these */ -void *cmalloc(size_t size); -void *crealloc(void *ptr, size_t size); -void *ccalloc(size_t num, size_t size); +TWN_API void *cmalloc(size_t size); +TWN_API void *crealloc(void *ptr, size_t size); +TWN_API void *ccalloc(size_t num, size_t size); /* DON'T FORGET ABOUT DOUBLE EVALUATION */ @@ -58,20 +59,20 @@ void *ccalloc(size_t num, size_t size); #define RAD2DEG (180 / M_PI) /* TODO: this is why generics were invented. sorry, i'm tired today */ -double clamp(double d, double min, double max); -float clampf(float f, float min, float max); -int clampi(int i, int min, int max); +TWN_API double clamp(double d, double min, double max); +TWN_API float clampf(float f, float min, float max); +TWN_API int clampi(int i, int min, int max); /* sets buf_out to a pointer to a byte buffer which must be freed. */ /* returns the size of this buffer. */ -int64_t file_to_bytes(const char *path, unsigned char **buf_out); +TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out); /* returns a pointer to a string which must be freed */ -char *file_to_str(const char *path); +TWN_API char *file_to_str(const char *path); /* returns true if str ends with suffix */ -bool strends(const char *str, const char *suffix); +TWN_API TWN_API bool strends(const char *str, const char *suffix); /* */ @@ -108,14 +109,14 @@ _Alignas(16) } t_frect; -bool intersect_rect(const t_rect *a, const t_rect *b, t_rect *result); +TWN_API bool intersect_rect(const t_rect *a, const t_rect *b, t_rect *result); -bool intersect_frect(const t_frect *a, const t_frect *b, t_frect *result); +TWN_API bool intersect_frect(const t_frect *a, const t_frect *b, t_frect *result); /* TODO: generics and specials (see m_to_fvec2() for an example)*/ -t_frect to_frect(t_rect rect); +TWN_API t_frect to_frect(t_rect rect); -t_fvec2 frect_center(t_frect rect); +TWN_API t_fvec2 frect_center(t_frect rect); typedef struct matrix4 { @@ -129,16 +130,16 @@ typedef struct matrix4 { * example: * tick_timer(&player->jump_air_timer); */ -void tick_timer(int *value); +TWN_API void tick_timer(int *value); /* decrements a floating point second-based timer, stopping at 0.0 */ /* meant for poll based real time logic in game logic */ /* note that it should be decremented only on the next tick after its creation */ -void tick_ftimer(float *value); +TWN_API void tick_ftimer(float *value); /* same as `tick_ftimer` but instead of clamping it repeats */ /* returns true if value was cycled */ -bool repeat_ftimer(float *value, float at); +TWN_API bool repeat_ftimer(float *value, float at); /* http://www.azillionmonkeys.com/qed/sqroot.html */ static inline float fast_sqrt(float x)