windows build compatibility, twn_game_object as abstraction for bridging game code and the engine

This commit is contained in:
veclavtalica 2024-08-27 00:33:37 +03:00
parent 9892bf71dc
commit 2f36d9ea3c
19 changed files with 454 additions and 261 deletions

View File

@ -2,10 +2,6 @@ cmake_minimum_required(VERSION 3.21)
project(townengine LANGUAGES C) project(townengine LANGUAGES C)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# dependencies # dependencies
find_package(SDL2 REQUIRED GLOBAL) find_package(SDL2 REQUIRED GLOBAL)
find_package(SDL2_image REQUIRED GLOBAL) find_package(SDL2_image REQUIRED GLOBAL)
@ -15,14 +11,14 @@ if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif () endif ()
set(TOWNENGINE_TARGET townengine CACHE INTERNAL "") set(TWN_TARGET townengine CACHE INTERNAL "")
set(TOWNENGINE_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "") set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
option(TOWNENGINE_HOT_RELOAD "Enable hot reloading support" ON) option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
option(TOWNENGINE_ARCHIVE_DATA "Enable archival of assets" OFF) option(TWN_ARCHIVE_DATA "Enable archival of assets" OFF)
# add -fPIC globally so that it's linked well # add -fPIC globally so that it's linked well
add_compile_options($<$<BOOL:${TOWNENGINE_HOT_RELOAD}>:-fPIC>) add_compile_options($<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:-fPIC>)
set(PHYSFS_BUILD_SHARED FALSE) set(PHYSFS_BUILD_SHARED FALSE)
set(PHYSFS_DISABLE_INSTALL TRUE) set(PHYSFS_DISABLE_INSTALL TRUE)
@ -45,12 +41,13 @@ else()
set(SYSTEM_SOURCE_FILES) set(SYSTEM_SOURCE_FILES)
endif() endif()
set(TOWNENGINE_SOURCE_FILES set(TWN_SOURCE_FILES
third-party/physfs/extras/physfsrwops.c third-party/physfs/extras/physfsrwops.c
third-party/stb/stb_vorbis.c third-party/stb/stb_vorbis.c
third-party/glad/src/glad.c third-party/glad/src/glad.c
townengine/main.c townengine/twn_loop.c
townengine/twn_main.c
townengine/config.h townengine/config.h
townengine/context/context.c townengine/context.h townengine/context/context.c townengine/context.h
townengine/audio/audio.c townengine/audio.h townengine/audio/audio.c townengine/audio.h
@ -59,25 +56,29 @@ set(TOWNENGINE_SOURCE_FILES
townengine/input/input.c townengine/input.h townengine/input/input.c townengine/input.h
townengine/camera.c townengine/camera.h townengine/camera.c townengine/camera.h
townengine/textures/textures.c townengine/textures/textures.c
townengine/twn_game_object.c
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:townengine/twn_main.c>
${SYSTEM_SOURCE_FILES}) ${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 # base engine code, reused for games and tools
if (TOWNENGINE_HOT_RELOAD) if (TWN_FEATURE_DYNLIB_GAME)
add_library(${TOWNENGINE_TARGET} SHARED ${TOWNENGINE_SOURCE_FILES}) add_library(${TWN_TARGET} SHARED ${TWN_SOURCE_FILES})
else () else ()
add_library(${TOWNENGINE_TARGET} STATIC ${TOWNENGINE_SOURCE_FILES}) add_library(${TWN_TARGET} STATIC ${TWN_SOURCE_FILES})
endif () 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 11
C_STANDARD_REQUIRED ON C_STANDARD_REQUIRED ON
C_EXTENSIONS ON) # extensions are required by stb_ds.h 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/glad/include/glad/glad.h
third-party/stb/stb_ds.h) third-party/stb/stb_ds.h)
@ -163,7 +164,7 @@ function(give_options target)
ORGANIZATION_NAME="${ORGANIZATION_NAME}" ORGANIZATION_NAME="${ORGANIZATION_NAME}"
APP_NAME="${APP_NAME}" APP_NAME="${APP_NAME}"
PACKAGE_EXTENSION="${PACKAGE_EXTENSION}" PACKAGE_EXTENSION="${PACKAGE_EXTENSION}"
$<$<BOOL:${TOWNENGINE_HOT_RELOAD}>:HOT_RELOAD_SUPPORT>) $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>)
endfunction() endfunction()
@ -178,18 +179,17 @@ function(include_deps target)
third-party/stb third-party/stb
third-party/x-watcher) 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}) target_include_directories(${target} SYSTEM PRIVATE ${THIRD_PARTY_INCLUDES})
# allow access to headers from any point in source tree # 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() endfunction()
function(link_deps target) function(link_deps target)
target_link_libraries(${target} PUBLIC target_link_libraries(${target} PUBLIC
SDL2::SDL2 SDL2::SDL2
SDL2::SDL2main
SDL2_image::SDL2_image SDL2_image::SDL2_image
SDL2_ttf::SDL2_ttf SDL2_ttf::SDL2_ttf
physfs-static physfs-static
@ -198,21 +198,23 @@ endfunction()
function(use_townengine target sources output_directory data_dir) function(use_townengine target sources output_directory data_dir)
if (TOWNENGINE_HOT_RELOAD) if (TWN_FEATURE_DYNLIB_GAME)
# game shared library, for reloading # game shared library, for reloading
add_library(${target}_game SHARED ${sources}) add_library(${target}_game SHARED ${sources})
set_target_properties(${target}_game PROPERTIES
OUTPUT_NAME game
LIBRARY_OUTPUT_DIRECTORY ${output_directory})
give_options(${target}_game) give_options(${target}_game)
include_deps(${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 # 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 # put libtownengine.so alongside the binary
set_target_properties(${TOWNENGINE_TARGET} PROPERTIES set_target_properties(${TWN_TARGET} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${output_directory}) LIBRARY_OUTPUT_DIRECTORY ${output_directory})
else () else ()
add_executable(${target}_app ${sources}) add_executable(${target}_app ${sources})
@ -231,42 +233,51 @@ function(use_townengine target sources output_directory data_dir)
give_options(${target}_app) give_options(${target}_app)
include_deps(${target}_app) include_deps(${target}_app)
link_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 set_target_properties(${target}_app PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${output_directory}) RUNTIME_OUTPUT_DIRECTORY ${output_directory})
# copy dlls for baby windows # copy dlls for baby windows
add_custom_command(TARGET ${target}_app POST_BUILD add_custom_command(TARGET ${target}_app POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy -t $<TARGET_FILE_DIR:${target}_app> COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:${target}_app>
$<TARGET_RUNTIME_DLLS:${target}_app> $<TARGET_FILE_DIR:${target}_app>
COMMAND_EXPAND_LISTS) COMMAND_EXPAND_LISTS)
if (UNIX) set(TWN_BOOTSTRAP_EXEC_ARGS
# create a bootstrapping script "$<IF:$<BOOL:${TWN_ARCHIVE_DATA}>,--data-dir ./data.${PACKAGE_EXTENSION},--data-dir ${data_dir}>")
set(TOWNENGINE_BOOTSTRAP_EXEC_ARGS
"$<IF:$<BOOL:${TOWNENGINE_ARCHIVE_DATA}>,--data-dir ./data.${PACKAGE_EXTENSION},--data-dir ${data_dir}>")
string(JOIN "\n" TOWNENGINE_BOOTSTRAP string(JOIN "\n" TWN_BOOTSTRAP_SH
"#!/bin/env sh" "#!/bin/env sh"
"cd \"$(dirname \"$0\")\"" "cd \"$(dirname \"$0\")\""
"LD_LIBRARY_PATH=./ ./launcher ${TOWNENGINE_BOOTSTRAP_EXEC_ARGS}" "LD_LIBRARY_PATH=./ ./launcher ${TWN_BOOTSTRAP_EXEC_ARGS}"
"") "")
FILE(GENERATE OUTPUT ${output_directory}/${target} FILE(GENERATE OUTPUT ${output_directory}/${target}
CONTENT "${TOWNENGINE_BOOTSTRAP}" 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}.bat
CONTENT "${TWN_BOOTSTRAP_BAT}"
FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ) FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)
endif () endif ()
if (TOWNENGINE_ARCHIVE_DATA) if (TWN_ARCHIVE_DATA)
# zip up assets # zip up assets
add_custom_command(TARGET ${target}_app POST_BUILD 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}) WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endif () endif ()
endfunction() endfunction()
give_options(${TOWNENGINE_TARGET}) give_options(${TWN_TARGET})
include_deps(${TOWNENGINE_TARGET}) include_deps(${TWN_TARGET})
link_deps(${TOWNENGINE_TARGET}) link_deps(${TWN_TARGET})
# build the testgame if this cmake list is built directly # build the testgame if this cmake list is built directly
if (${CMAKE_PROJECT_NAME} MATCHES townengine) if (${CMAKE_PROJECT_NAME} MATCHES townengine)
@ -274,6 +285,7 @@ if (${CMAKE_PROJECT_NAME} MATCHES townengine)
endif () endif ()
# move compie_commands.json into root directory so that it plays nicer with editors # 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 COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json
${TOWNENGINE_ROOT_DIR}) ${TWN_ROOT_DIR})

View File

@ -5,6 +5,8 @@
#include "townengine/game_api.h" #include "townengine/game_api.h"
#include <stdio.h>
static void title_tick(struct state *state) { static void title_tick(struct state *state) {
struct scene_title *scn = (struct scene_title *)state->scene; 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 */ /* 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); 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"; const char *font = "fonts/kenney-pixel.ttf";
int text_h = 32; int text_h = 32;

View File

@ -1,6 +1,8 @@
#ifndef AUDIO_H #ifndef AUDIO_H
#define AUDIO_H #define AUDIO_H
#include "twn_engine_api.h"
#include <stdbool.h> #include <stdbool.h>
@ -23,14 +25,14 @@ typedef struct play_audio_args {
/* path must contain valid file extension to infer which file format it is */ /* path must contain valid file extension to infer which file format it is */
/* supported formats: .ogg, .xm */ /* supported formats: .ogg, .xm */
/* preserves args that are already specified on the channel */ /* 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 */ /* could be used for modifying args */
/* warn: is only valid if no other calls to audio are made */ /* 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 #endif

View File

@ -5,6 +5,7 @@
#include "rendering/internal_api.h" #include "rendering/internal_api.h"
#include "textures/internal_api.h" #include "textures/internal_api.h"
#include "input.h" #include "input.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@ -65,6 +66,6 @@ typedef struct context {
bool initialization_needed; bool initialization_needed;
} t_ctx; } t_ctx;
extern t_ctx ctx; TWN_API extern t_ctx ctx;
#endif #endif

View File

@ -8,15 +8,15 @@
#include "townengine/audio.h" #include "townengine/audio.h"
#include "townengine/util.h" #include "townengine/util.h"
#include "townengine/input.h" #include "townengine/input.h"
#include "twn_engine_api.h"
#ifndef HOT_RELOAD_SUPPORT
/* sole game logic and display function. /* sole game logic and display function.
all state must be used from and saved to supplied state pointer. */ 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. */ /* called when application is closing. */
extern void game_end(void); TWN_API extern void game_end(void);
#endif
#endif #endif

View File

@ -0,0 +1,137 @@
#include "townengine/twn_game_object.h"
#include "townengine/context.h"
#include <x-watcher.h>
#include <SDL2/SDL.h>
#include <dlfcn.h>
#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();
}

View File

@ -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();
}

View File

@ -0,0 +1,83 @@
#include "townengine/twn_game_object.h"
#include "townengine/context.h"
#include <errhandlingapi.h>
#include <libloaderapi.h>
#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();
}

View File

@ -4,6 +4,7 @@
#include "config.h" #include "config.h"
#include "vec.h" #include "vec.h"
#include "util.h" #include "util.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@ -68,33 +69,33 @@ struct input_state {
}; };
void input_state_init(struct input_state *input); TWN_API void input_state_init(struct input_state *input);
void input_state_deinit(struct input_state *input); TWN_API void input_state_deinit(struct input_state *input);
void input_state_update(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, char *action_name,
SDL_Scancode scancode); 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, char *action_name,
SDL_Scancode scancode); 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, char *action_name,
uint8_t mouse_button); 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, char *action_name,
uint8_t mouse_button); uint8_t mouse_button);
void input_add_action(struct input_state *input, char *action_name); TWN_API 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_delete_action(struct input_state *input, char *action_name);
bool input_is_action_pressed(struct input_state *input, char *action_name); TWN_API 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); TWN_API 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_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); TWN_API void input_set_mouse_captured(struct input_state *input, bool value);
bool input_is_mouse_captured(struct input_state *input); TWN_API bool input_is_mouse_captured(struct input_state *input);
#endif #endif

View File

@ -1,2 +0,0 @@
/* cmake nonsense */
struct a { int b; };

View File

@ -4,6 +4,7 @@
#include "util.h" #include "util.h"
#include "macros/option.h" #include "macros/option.h"
#include "camera.h" #include "camera.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@ -25,24 +26,24 @@ typedef struct push_sprite_args {
/* pushes a sprite onto the sprite render queue */ /* pushes a sprite onto the sprite render queue */
/* this is a simplified version of push_sprite_ex for the most common case. */ /* 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 */ /* 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__})) #define m_sprite(...) (push_sprite((t_push_sprite_args){__VA_ARGS__}))
/* pushes a filled rectangle onto the rectangle render queue */ /* 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 */ /* 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); TWN_API void text_cache_init(struct text_cache *cache);
void text_cache_deinit(struct text_cache *cache); TWN_API 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); TWN_API 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 int get_text_width(char *string, int height_px, const char *font_path);
/* pushes a textured 3d triangle onto the render queue */ /* pushes a textured 3d triangle onto the render queue */
/* vertices are in absolute coordinates, relative to world origin */ /* vertices are in absolute coordinates, relative to world origin */
/* texture coordinates are in pixels */ /* texture coordinates are in pixels */
void unfurl_triangle(const char *path, TWN_API void unfurl_triangle(const char *path,
t_fvec3 v0, t_fvec3 v0,
t_fvec3 v1, t_fvec3 v1,
t_fvec3 v2, t_fvec3 v2,
@ -70,6 +71,6 @@ void unfurl_triangle(const char *path,
// t_frect uvs); // t_frect uvs);
/* pushes a camera state to be used for all future unfurl_* commands */ /* 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 #endif

View File

@ -3,6 +3,7 @@
#include "../util.h" #include "../util.h"
#include "../textures/modes.h" #include "../textures/modes.h"
#include "../twn_engine_api.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <stb_rect_pack.h> #include <stb_rect_pack.h>
@ -46,11 +47,11 @@ typedef struct { uint16_t id; } t_texture_key;
/* tests whether given key structure corresponds to any texture */ /* tests whether given key structure corresponds to any texture */
#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1) #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); TWN_API void textures_cache_init(struct texture_cache *cache, SDL_Window *window);
void textures_cache_deinit(struct texture_cache *cache); TWN_API void textures_cache_deinit(struct texture_cache *cache);
/* for debugging */ /* 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. */ /* 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 */ /* 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 */ /* repacks the current texture atlas based on the texture cache if needed */
/* any previously returned srcrect results are invalidated after that */ /* any previously returned srcrect results are invalidated after that */
/* call it every time before rendering */ /* 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 */ /* returns a persistent handle to some texture in cache, loading it if needed */
/* check the result with m_texture_key_is_valid() */ /* 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 */ /* 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) */ /* 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 */ /* 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 */ /* 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 */ /* 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 */ /* 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 */ /* 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 #endif

View File

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

View File

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

View File

@ -0,0 +1,22 @@
#ifndef GAME_OBJECT_H
#define GAME_OBJECT_H
#include <stdbool.h>
/*
* 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

View File

@ -1,8 +1,9 @@
#include "twn_loop.h"
#include "townengine/context.h" #include "townengine/context.h"
#include "townengine/rendering.h" #include "townengine/rendering.h"
#include "townengine/input/internal_api.h" #include "townengine/input/internal_api.h"
#include "townengine/util.h" #include "townengine/util.h"
#include "townengine/game_api.h" #include "townengine/twn_game_object.h"
#include "townengine/audio/internal_api.h" #include "townengine/audio/internal_api.h"
#include "townengine/textures/internal_api.h" #include "townengine/textures/internal_api.h"
#include "townengine/rendering/internal_api.h" #include "townengine/rendering/internal_api.h"
@ -20,30 +21,6 @@
#include <tgmath.h> #include <tgmath.h>
#include <limits.h> #include <limits.h>
#ifdef HOT_RELOAD_SUPPORT
#include <x-watcher.h>
#include <dlfcn.h>
#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) { static void poll_events(void) {
SDL_Event e; SDL_Event e;
@ -192,7 +169,7 @@ static void main_loop(void) {
input_state_update(&ctx.input); input_state_update(&ctx.input);
game_tick(); game_object_tick();
ctx.frame_accumulator -= ctx.desired_frametime; ctx.frame_accumulator -= ctx.desired_frametime;
ctx.tick_count = (ctx.tick_count % ULLONG_MAX) + 1; ctx.tick_count = (ctx.tick_count % ULLONG_MAX) + 1;
@ -375,8 +352,6 @@ static bool initialize(void) {
} }
*/ */
game_object_mutex = SDL_CreateMutex();
return true; return true;
fail: fail:
@ -404,8 +379,6 @@ static void clean_up(void) {
text_cache_deinit(&ctx.text_cache); text_cache_deinit(&ctx.text_cache);
textures_cache_deinit(&ctx.texture_cache); textures_cache_deinit(&ctx.texture_cache);
SDL_DestroyMutex(game_object_mutex);
PHYSFS_deinit(); PHYSFS_deinit();
TTF_Quit(); TTF_Quit();
IMG_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) { static void reset_state(void) {
input_reset_state(&ctx.input); input_reset_state(&ctx.input);
textures_reset_state(); textures_reset_state();
} }
int main(int argc, char **argv) { int enter_loop(int argc, char **argv) {
ctx.argc = argc; ctx.argc = argc;
ctx.argv = argv; ctx.argv = argv;
@ -537,26 +409,21 @@ int main(int argc, char **argv) {
} }
} }
load_game(); game_object_load();
ctx.was_successful = true; ctx.was_successful = true;
ctx.initialization_needed = true; ctx.initialization_needed = true;
while (ctx.is_running) { while (ctx.is_running) {
/* only load the modified library after some time, as compilers make a lot of modifications */ if (game_object_try_reloading()) {
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;
ctx.initialization_needed = true; ctx.initialization_needed = true;
reset_state(); reset_state();
} }
SDL_UnlockMutex(game_object_mutex);
main_loop(); main_loop();
} }
game_end(); game_object_unload();
clean_up(); clean_up();

10
townengine/twn_loop.h Normal file
View File

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

11
townengine/twn_main.c Normal file
View File

@ -0,0 +1,11 @@
#include "twn_loop.h"
#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>
int main(int argc, char **argv) {
SDL_SetMainReady();
enter_loop(argc, argv);
}

View File

@ -2,6 +2,7 @@
#define UTIL_H #define UTIL_H
#include "townengine/vec.h" #include "townengine/vec.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <physfs.h> #include <physfs.h>
@ -17,28 +18,28 @@
/* GENERAL UTILITIES */ /* 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(title, text) cry_impl(__FILE__, __LINE__, title, text)
#define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError()) #define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError())
#define CRY_PHYSFS(title) \ #define CRY_PHYSFS(title) \
cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())) cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))
void log_info(const char *restrict format, ...); TWN_API void log_info(const char *restrict format, ...);
void log_critical(const char *restrict format, ...); TWN_API void log_critical(const char *restrict format, ...);
void log_warn(const char *restrict format, ...); TWN_API void log_warn(const char *restrict format, ...);
/* for when there's absolutely no way to continue */ /* 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. */ /* "critical" allocation functions which will log and abort() on failure. */
/* if it is reasonable to handle this gracefully, use the standard versions. */ /* if it is reasonable to handle this gracefully, use the standard versions. */
/* the stb implementations will be configured to use these */ /* the stb implementations will be configured to use these */
void *cmalloc(size_t size); TWN_API void *cmalloc(size_t size);
void *crealloc(void *ptr, size_t size); TWN_API void *crealloc(void *ptr, size_t size);
void *ccalloc(size_t num, size_t size); TWN_API void *ccalloc(size_t num, size_t size);
/* DON'T FORGET ABOUT DOUBLE EVALUATION */ /* DON'T FORGET ABOUT DOUBLE EVALUATION */
@ -58,20 +59,20 @@ void *ccalloc(size_t num, size_t size);
#define RAD2DEG (180 / M_PI) #define RAD2DEG (180 / M_PI)
/* TODO: this is why generics were invented. sorry, i'm tired today */ /* TODO: this is why generics were invented. sorry, i'm tired today */
double clamp(double d, double min, double max); TWN_API double clamp(double d, double min, double max);
float clampf(float f, float min, float max); TWN_API float clampf(float f, float min, float max);
int clampi(int i, int min, int 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. */ /* sets buf_out to a pointer to a byte buffer which must be freed. */
/* returns the size of this buffer. */ /* 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 */ /* 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 */ /* 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; } 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)*/ /* 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 { typedef struct matrix4 {
@ -129,16 +130,16 @@ typedef struct matrix4 {
* example: * example:
* tick_timer(&player->jump_air_timer); * 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 */ /* decrements a floating point second-based timer, stopping at 0.0 */
/* meant for poll based real time logic in game logic */ /* meant for poll based real time logic in game logic */
/* note that it should be decremented only on the next tick after its creation */ /* 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 */ /* same as `tick_ftimer` but instead of clamping it repeats */
/* returns true if value was cycled */ /* 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 */ /* http://www.azillionmonkeys.com/qed/sqroot.html */
static inline float fast_sqrt(float x) static inline float fast_sqrt(float x)