windows build compatibility, twn_game_object as abstraction for bridging game code and the engine
This commit is contained in:
parent
9892bf71dc
commit
2f36d9ea3c
104
CMakeLists.txt
104
CMakeLists.txt
@ -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})
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
137
townengine/game_object/twn_linux_game_object_c.h
Normal file
137
townengine/game_object/twn_linux_game_object_c.h
Normal 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();
|
||||||
|
}
|
21
townengine/game_object/twn_static_game_object_c.h
Normal file
21
townengine/game_object/twn_static_game_object_c.h
Normal 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();
|
||||||
|
}
|
83
townengine/game_object/twn_win32_game_object_c.h
Normal file
83
townengine/game_object/twn_win32_game_object_c.h
Normal 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();
|
||||||
|
}
|
@ -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
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
/* cmake nonsense */
|
|
||||||
struct a { int b; };
|
|
@ -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
|
||||||
|
@ -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
|
||||||
|
10
townengine/twn_engine_api.h
Normal file
10
townengine/twn_engine_api.h
Normal 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
|
12
townengine/twn_game_object.c
Normal file
12
townengine/twn_game_object.c
Normal 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
|
22
townengine/twn_game_object.h
Normal file
22
townengine/twn_game_object.h
Normal 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
|
@ -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
10
townengine/twn_loop.h
Normal 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
11
townengine/twn_main.c
Normal 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);
|
||||||
|
}
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user