diff --git a/.gitignore b/.gitignore index 86fa60d..670f4e7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ !Makefile **/*.exe +**/*.so +**/*.dll .vs/ .vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3eb5749..4b1ee8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,8 @@ endif () set(TOWNENGINE_TARGET townengine CACHE INTERNAL "") set(TOWNENGINE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "") +add_compile_options($<$:-fPIC>) + set(PHYSFS_BUILD_SHARED FALSE) set(PHYSFS_DISABLE_INSTALL TRUE) set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall") @@ -33,10 +35,11 @@ add_subdirectory(third-party/physfs) add_subdirectory(third-party/libxm) +option(TOWNENGINE_HOT_RELOAD "Enable hot reloading support" TRUE) + + if(UNIX) - set(SYSTEM_SOURCE_FILES - townengine/system/linux/elf.c - ) + set(SYSTEM_SOURCE_FILES townengine/system/linux/elf.c) else() set(SYSTEM_SOURCE_FILES) endif() @@ -48,21 +51,25 @@ set(TOWNENGINE_SOURCE_FILES townengine/main.c townengine/config.h - townengine/context.c townengine/context.h - townengine/audio.c townengine/audio.h + townengine/context/context.c townengine/context.h + townengine/audio/audio.c townengine/audio.h townengine/util.c townengine/util.h townengine/rendering.c townengine/rendering.h - townengine/input.c townengine/input.h + townengine/input/input.c townengine/input.h townengine/text.c townengine/text.h townengine/camera.c townengine/camera.h townengine/textures/textures.c - ${SYSTEM_SOURCE_FILES} -) + ${SYSTEM_SOURCE_FILES}) + list(TRANSFORM TOWNENGINE_SOURCE_FILES PREPEND ${TOWNENGINE_DIR}/) # base engine code, reused for games and tools -add_library(${TOWNENGINE_TARGET} ${TOWNENGINE_SOURCE_FILES}) +if (TOWNENGINE_HOT_RELOAD) + add_library(${TOWNENGINE_TARGET} SHARED ${TOWNENGINE_SOURCE_FILES}) +else () + add_library(${TOWNENGINE_TARGET} STATIC ${TOWNENGINE_SOURCE_FILES}) +endif () source_group(TREE ${TOWNENGINE_DIR} FILES ${TOWNENGINE_SOURCE_FILES}) set_target_properties(${TOWNENGINE_TARGET} PROPERTIES @@ -72,8 +79,7 @@ set_target_properties(${TOWNENGINE_TARGET} PROPERTIES target_precompile_headers(${TOWNENGINE_TARGET} PRIVATE third-party/glad/include/glad/glad.h - third-party/stb/stb_ds.h -) + third-party/stb/stb_ds.h) # distribution definitions set(ORGANIZATION_NAME "wanp" CACHE STRING @@ -156,11 +162,12 @@ function(give_options target) target_compile_definitions(${target} PRIVATE ORGANIZATION_NAME="${ORGANIZATION_NAME}" APP_NAME="${APP_NAME}" - PACKAGE_EXTENSION="${PACKAGE_EXTENSION}") + PACKAGE_EXTENSION="${PACKAGE_EXTENSION}" + $<$:HOT_RELOAD_SUPPORT>) endfunction() -function(link_and_include_deps target) +function(include_deps target) # header-only libraries should be marked as "system includes" # to suppress compiler warnings in their code (it's not my problem after all) set(THIRD_PARTY_INCLUDES @@ -169,57 +176,65 @@ function(link_and_include_deps target) third-party/libxm/include third-party/glad/include third-party/stb - ) + third-party/x-watcher) + list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TOWNENGINE_DIR}/) target_include_directories(${target} SYSTEM PRIVATE ${THIRD_PARTY_INCLUDES}) # allow access to headers from any point in source tree target_include_directories(${target} PRIVATE ${TOWNENGINE_DIR}) - - target_link_libraries(${target} PUBLIC - SDL2::SDL2 - SDL2::SDL2main - SDL2_image::SDL2_image - SDL2_ttf::SDL2_ttf - physfs-static - xms - ) endfunction() -function(use_townengine target sources) - add_executable(${target} ${sources}) - - # system libraries - find_library(MATH_LIBRARY m) - if(MATH_LIBRARY) - target_link_libraries(${target} PUBLIC ${MATH_LIBRARY}) - endif() - - give_options(${target}) - link_and_include_deps(${target}) - - # third-party libraries +function(link_deps target) target_link_libraries(${target} PUBLIC - ${TOWNENGINE_TARGET} SDL2::SDL2 SDL2::SDL2main SDL2_image::SDL2_image SDL2_ttf::SDL2_ttf physfs-static - xms - ) + xms) +endfunction() + + +function(use_townengine target sources output_directory) + if (TOWNENGINE_HOT_RELOAD) + add_library(${target}_shared SHARED ${sources}) + set_target_properties(${target}_shared PROPERTIES + OUTPUT_NAME game + LIBRARY_OUTPUT_DIRECTORY ${output_directory}) + give_options(${target}_shared) + include_deps(${target}_shared) + target_link_libraries(${target}_shared PUBLIC SDL2::SDL2) + add_executable(${target} ${CMAKE_SOURCE_DIR}/townengine/null.c) + else () + add_executable(${target} ${sources}) + endif () + + # system libraries + find_library(MATH_LIBRARY m) + if (MATH_LIBRARY) + target_link_libraries(${target} PUBLIC ${MATH_LIBRARY}) + endif () + + give_options(${target}) + include_deps(${target}) + link_deps(${target}) + target_link_libraries(${target} PUBLIC ${TOWNENGINE_TARGET}) + + set_target_properties(${target} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${output_directory}) # copy dlls for baby windows add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy -t $ $ - COMMAND_EXPAND_LISTS - ) + COMMAND_EXPAND_LISTS) endfunction() give_options(${TOWNENGINE_TARGET}) -link_and_include_deps(${TOWNENGINE_TARGET}) +include_deps(${TOWNENGINE_TARGET}) +link_deps(${TOWNENGINE_TARGET}) if (${CMAKE_PROJECT_NAME} MATCHES townengine) add_subdirectory(apps/testgame) diff --git a/apps/template/game.c b/apps/template/game.c index ab41ac1..6477369 100644 --- a/apps/template/game.c +++ b/apps/template/game.c @@ -4,11 +4,13 @@ #include -void game_tick(void) { - /* do your initialization on first tick */ - if (ctx.tick_count == 0) { +void game_tick(t_ctx *ctx) { + /* do state initialization when engine asks for it */ + /* it could happen multiple times per application run, as game code is reloadable */ + if (ctx.initialization_needed) { /* application data could be stored in ctx.udata and retrieved anywhere */ - ctx.udata = ccalloc(1, sizeof (struct state)); + if (!ctx.udata) + ctx.udata = ccalloc(1, sizeof (struct state)); } /* a lot of data is accessible from `ctx`, look into `townengine/context.h` for more */ diff --git a/apps/testgame/CMakeLists.txt b/apps/testgame/CMakeLists.txt index 4b310c2..59e2170 100644 --- a/apps/testgame/CMakeLists.txt +++ b/apps/testgame/CMakeLists.txt @@ -23,8 +23,4 @@ set(SOURCE_FILES scenes/ingame.c scenes/ingame.h ) -use_townengine(${PROJECT_NAME} "${SOURCE_FILES}") - -set_target_properties(${PROJECT_NAME} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) +use_townengine(${PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/apps/testgame/game.c b/apps/testgame/game.c index 8c6c392..59f2ef5 100644 --- a/apps/testgame/game.c +++ b/apps/testgame/game.c @@ -1,8 +1,9 @@ -#include "townengine/game_api.h" #include "state.h" #include "scenes/scene.h" #include "scenes/title.h" +#include "townengine/game_api.h" + #include #include #include @@ -10,11 +11,16 @@ void game_tick(void) { - if (ctx.tick_count == 0) { - ctx.udata = ccalloc(1, sizeof (struct state)); + if (ctx.initialization_needed) { + if (!ctx.udata) { + ctx.udata = ccalloc(1, sizeof (struct state)); + + struct state *state = ctx.udata; + state->ctx = &ctx; + state->scene = title_scene(state); + } + struct state *state = ctx.udata; - state->ctx = &ctx; - state->scene = title_scene(state); input_add_action(&ctx.input, "debug_dump_atlases"); input_bind_action_scancode(&ctx.input, "debug_dump_atlases", SDL_SCANCODE_HOME); diff --git a/apps/testgame/scenes/ingame.c b/apps/testgame/scenes/ingame.c index f03b615..a4686be 100644 --- a/apps/testgame/scenes/ingame.c +++ b/apps/testgame/scenes/ingame.c @@ -3,7 +3,6 @@ #include "scene.h" #include "townengine/game_api.h" -#include "townengine/tabela.h" #define STB_PERLIN_IMPLEMENTATION #include @@ -15,8 +14,6 @@ static void ingame_tick(struct state *state) { world_drawdef(scn->world); player_calc(scn->player); - static t_camera cam = { .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 }; - if (input_is_mouse_captured(&ctx.input)) { const float sensitivity = 0.6f; /* TODO: put this in a better place */ scn->yaw += (float)ctx.input.mouse_relative_position.x * sensitivity; @@ -26,32 +23,32 @@ static void ingame_tick(struct state *state) { const float yaw_rad = scn->yaw * (float)DEG2RAD; const float pitch_rad = scn->pitch * (float)DEG2RAD; - cam.target = m_vec_norm(((t_fvec3){ + scn->cam.target = m_vec_norm(((t_fvec3){ cosf(yaw_rad) * cosf(pitch_rad), sinf(pitch_rad), sinf(yaw_rad) * cosf(pitch_rad) })); } - const t_fvec3 right = m_vec_norm(m_vec_cross(cam.target, cam.up)); + const t_fvec3 right = m_vec_norm(m_vec_cross(scn->cam.target, scn->cam.up)); const float speed = 0.04f; /* TODO: put this in a better place */ if (input_is_action_pressed(&ctx.input, "player_left")) - cam.pos = fvec3_sub(cam.pos, m_vec_scale(right, speed)); + scn->cam.pos = fvec3_sub(scn->cam.pos, m_vec_scale(right, speed)); if (input_is_action_pressed(&ctx.input, "player_right")) - cam.pos = fvec3_add(cam.pos, m_vec_scale(right, speed)); + scn->cam.pos = fvec3_add(scn->cam.pos, m_vec_scale(right, speed)); if (input_is_action_pressed(&ctx.input, "player_forward")) - cam.pos = fvec3_add(cam.pos, m_vec_scale(cam.target, speed)); + scn->cam.pos = fvec3_add(scn->cam.pos, m_vec_scale(scn->cam.target, speed)); if (input_is_action_pressed(&ctx.input, "player_backward")) - cam.pos = fvec3_sub(cam.pos, m_vec_scale(cam.target, speed)); + scn->cam.pos = fvec3_sub(scn->cam.pos, m_vec_scale(scn->cam.target, speed)); if (input_is_action_pressed(&ctx.input, "player_jump")) - cam.pos.y += speed; + scn->cam.pos.y += speed; if (input_is_action_pressed(&ctx.input, "player_run")) - cam.pos.y -= speed; + scn->cam.pos.y -= speed; /* toggle mouse capture with end key */ if (input_is_action_just_pressed(&ctx.input, "mouse_capture_toggle")) { @@ -84,16 +81,16 @@ static void ingame_tick(struct state *state) { m_set(rect, ((t_frect){ 128, 32, 128, 64 })), m_opt(rotation, (float)M_PI * 2 * (float)(ctx.tick_count % 64) / 64 )); - set_camera(&cam); + set_camera(&scn->cam); #define TERRAIN_FREQUENCY 0.1f for (int y = 64; y--;) { for (int x = 64; x--;) { - float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 10 - 6; - float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 10 - 6; - float d2 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 10 - 6; - float d3 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 10 - 6; + float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 20 - 6; + float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 20 - 6; + float d2 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 20 - 6; + float d3 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)(y - 1) * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 20 - 6; unfurl_triangle("/assets/grass.gif", (t_fvec3){ (float)x, d0, (float)y }, @@ -133,6 +130,8 @@ struct scene *ingame_scene(struct state *state) { new_scene->world = world_create(); new_scene->player = player_create(new_scene->world); + new_scene->cam = (t_camera){ .pos = { 32, 0, 1 }, .up = { 0, 1, 0 }, .fov = (float)M_PI_2 }; + play_audio_ex("music/mod65.xm", "soundtrack", (t_play_audio_args){ .repeat = true, .volume = 1.0f diff --git a/apps/testgame/scenes/ingame.h b/apps/testgame/scenes/ingame.h index 3901cd1..6b19d90 100644 --- a/apps/testgame/scenes/ingame.h +++ b/apps/testgame/scenes/ingame.h @@ -3,6 +3,7 @@ #include "townengine/game_api.h" + #include "../state.h" #include "scene.h" #include "../player.h" @@ -15,6 +16,8 @@ struct scene_ingame { struct world *world; struct player *player; + t_camera cam; + /* TODO: put this in a better place */ float yaw; float pitch; diff --git a/apps/testgame/scenes/title.c b/apps/testgame/scenes/title.c index 4571579..e532dc4 100644 --- a/apps/testgame/scenes/title.c +++ b/apps/testgame/scenes/title.c @@ -2,6 +2,7 @@ #include "ingame.h" #include "../world.h" #include "../player.h" + #include "townengine/game_api.h" diff --git a/apps/testgame/world.c b/apps/testgame/world.c index 3b2ea8c..d053971 100644 --- a/apps/testgame/world.c +++ b/apps/testgame/world.c @@ -1,6 +1,7 @@ -#include "world.h" #include "townengine/game_api.h" +#include "world.h" + #include #include #include diff --git a/third-party/x-watcher/array.h b/third-party/x-watcher/array.h new file mode 100644 index 0000000..50849f1 --- /dev/null +++ b/third-party/x-watcher/array.h @@ -0,0 +1,77 @@ +#ifndef ARRAY_H +#define ARRAY_H + +#include +#include + +struct _ArrayHeader { + size_t count, capacity; +}; + +#define ARRAY_INITIAL_SIZE 8 + +#define _arr_header(a) ((struct _ArrayHeader*)(a) - 1) + +#define arr_init(a) arr_init_n((a), ARRAY_INITIAL_SIZE) + +#define arr_init_n(a, n) do { \ + struct _ArrayHeader *header; \ + header = malloc(sizeof(*header) + (sizeof(*(a)) * (n))); \ + header->count = 0; \ + header->capacity = (n); \ + (a) = (void*)(header + 1); \ +} while(0) + +#define arr_count(a) (_arr_header(a)->count) +#define arr_capacity(a) (_arr_header(a)->capacity) + +#define arr_back(a) ((a)[arr_count(a) - 1]) +#define arr_pop(a) ((a)[_arr_header(a)->count--]) + +#define arr_reserve(a, n) do { \ + if(n <= arr_capacity(a)) break; \ + struct _ArrayHeader *header = _arr_header(a); \ + header->capacity = n; \ + (a) = (void*)((struct _ArrayHeader*)realloc( \ + header, sizeof(*header) + (sizeof(*(a)) * (n))) + 1); \ +} while(0) + +#define arr_resize(a, n) do { \ + arr_reserve((a), (n)); \ + _arr_header(a)->count = n; \ +} while(0) + +#define arr_resize_zero(a, n) do { \ + size_t initial_count = arr_count(a); \ + arr_resize((a), (n)); \ + if(arr_count(a) > initial_count) \ + memset( \ + &(a)[initial_count], 0, \ + (arr_count(a) - initial_count) * sizeof(*a)); \ +} while(0) + +// Take a vararg list to support compound literals +#define arr_add(a, ...) do { \ + struct _ArrayHeader *header = _arr_header(a); \ + if(header->count == header->capacity) \ + arr_reserve((a), header->capacity << 1); \ + (a)[_arr_header(a)->count++] = (__VA_ARGS__); \ +} while(0) + +#define arr_free(a) do { \ + free(_arr_header(a)); \ +} while(0) + +#define arr_find(a, val, idx) do { \ + *idx = -1; \ + for(size_t i = 0; i < arr_count((a)); i++) { \ + if((a)[i] == val) { \ + *idx = i; \ + break; \ + } \ + } \ +} while(0) + +#endif + + diff --git a/third-party/x-watcher/x-watcher.h b/third-party/x-watcher/x-watcher.h new file mode 100644 index 0000000..0eebd9b --- /dev/null +++ b/third-party/x-watcher/x-watcher.h @@ -0,0 +1,838 @@ +#ifndef __X_WATCHER_H +#define __X_WATCHER_H + +// necessary includes for the public stuff +#include +#include +#if defined(__linux__) + // noop +#elif defined(__WIN32__) + #include + #include +#else + #error "Unsupported" +#endif + +// PUBLIC STUFF +typedef enum event { + XWATCHER_FILE_UNSPECIFIED, + XWATCHER_FILE_REMOVED, + XWATCHER_FILE_CREATED, + XWATCHER_FILE_MODIFIED, + XWATCHER_FILE_OPENED, + XWATCHER_FILE_ATTRIBUTES_CHANGED, + XWATCHER_FILE_NONE, + XWATCHER_FILE_RENAMED, + // probs more but i couldn't care much +} XWATCHER_FILE_EVENT; + +typedef struct xWatcher_reference { + char *path; + void (*callback_func)( + XWATCHER_FILE_EVENT event, + const char *path, + int context, + void *additional_data); + int context; + void *additional_data; +} xWatcher_reference; + +struct file { + // just the file name alone + char *name; + // used for adding (additional) context in the handler (if needed) + int context; + // in case you'd like to avoid global variables + void *additional_data; + + void (*callback_func)( + XWATCHER_FILE_EVENT event, + const char *path, + int context, + void *additional_data); +} file; + +struct directory { + // list of files + struct file *files; + + char *path; + // used for adding (additional) context in the handler (if needed) + int context; + // in case you'd like to avoid global variables + void *additional_data; + + void (*callback_func)( + XWATCHER_FILE_EVENT event, + const char *path, + int context, + void *additional_data); + + #if defined(__linux__) + // we need additional file descriptors (per directory basis) + int inotify_watch_fd; + #elif defined(__WIN32__) + HANDLE handle; + OVERLAPPED overlapped; + uint8_t *event_buffer; + #else + #error "Unsupported" + #endif +} directory; + +typedef struct x_watcher { + struct directory *directories; + pthread_t thread; + int thread_id; + bool alive; + + #if defined(__linux__) + int inotify_fd; // fd == file descriptor (a common UNIX thing) + #elif defined(__WIN32__) + // literal noop + #else + #error "Unsupported" + #endif +} x_watcher; + +// PRIVATE STUFF +#include +#include +#include + +#include "array.h" + +#ifdef __linux__ + #include + #include + #include + #include + #include // for POLLIN + #include // for O_NONBLOCK + + #define EVENT_SIZE (sizeof(struct inotify_event)) + #define BUF_LEN (1024 * (EVENT_SIZE + 16)) + #define DIRBRK '/' + + static inline void *__internal_xWatcherProcess(void *argument) { + x_watcher *watcher = (x_watcher*) argument; + char buffer[BUF_LEN]; + ssize_t lenght; + struct directory *directories = watcher->directories; + + while(watcher->alive) { + // poll for events + struct pollfd pfd = { watcher->inotify_fd, POLLIN, 0 }; + int ret = poll(&pfd, 1, 50); // timeout of 50ms + + if (ret < 0) { + // oops + fprintf(stderr, "poll failed: %s\n", strerror(errno)); + break; + } else if (ret == 0) { + // Timeout with no events, move on. + continue; + } + + // wait for the kernel to do it's thing + lenght = read(watcher->inotify_fd, buffer, BUF_LEN); + if(lenght < 0) { + // something messed up clearly + perror("read"); + return NULL; + } + + // pointer to the event structure + ssize_t i = 0; + while(i < lenght) { + // the event list itself + struct inotify_event *event = (struct inotify_event *) + &buffer[i]; + + // find directory for which this even matches via the descriptor + struct directory *directory = NULL; + for(size_t j = 0; j < arr_count(directories); j++) { + if(directories[j].inotify_watch_fd == event->wd) { + directory = &directories[j]; + } + } + if(directory == NULL) { + fprintf(stderr, + "MATCHING FILE DESCRIPTOR NOT FOUND! ERROR!\n"); + // BAIL???? + } + + // find matching file (if any) + struct file *file = NULL; + for(size_t j = 0; j < arr_count(directory->files); j++) { + if(strcmp(directory->files[j].name, event->name) == 0) { + file = &directory->files[j]; + } + } + + XWATCHER_FILE_EVENT send_event = XWATCHER_FILE_NONE; + + if(event->mask & IN_CREATE) + send_event = XWATCHER_FILE_CREATED; + if(event->mask & IN_MODIFY) + send_event = XWATCHER_FILE_MODIFIED; + if(event->mask & IN_DELETE) + send_event = XWATCHER_FILE_REMOVED; + if(event->mask & IN_CLOSE_WRITE || + event->mask & IN_CLOSE_NOWRITE) + send_event = XWATCHER_FILE_REMOVED; + if(event->mask & IN_ATTRIB) + send_event = XWATCHER_FILE_ATTRIBUTES_CHANGED; + if(event->mask & IN_OPEN) + send_event = XWATCHER_FILE_OPENED; + + // file found(?) + if(file != NULL) { + if(send_event != XWATCHER_FILE_NONE) { + // figure out the file path size + size_t filepath_size = strlen(directory->path); + filepath_size += strlen(file->name); + filepath_size += 2; + + // create file path string + char *filepath = (char*)malloc(filepath_size); + snprintf(filepath, filepath_size, "%s/%s", + directory->path, file->name); + + // callback + file->callback_func(send_event, + filepath, + file->context, + file->additional_data); + + // free that garbage + free(filepath); + } + } else { + // Cannot find file, lets try directory + if(directory->callback_func != NULL && + send_event != XWATCHER_FILE_NONE) { + directory->callback_func(send_event, + directory->path, + directory->context, + directory->additional_data); + } + } + + i += EVENT_SIZE + event->len; + } + } + + // cleanup time + for(size_t i = 0; i < arr_count(watcher->directories); i++) { + struct directory *directory = &watcher->directories[i]; + for(size_t j = 0; j < arr_count(directory->files); j++) { + struct file *file = &directory->files[j]; + free(file->name); + } + arr_free(directory->files); + free(directory->path); + inotify_rm_watch(watcher->inotify_fd, directory->inotify_watch_fd); + } + close(watcher->inotify_fd); + arr_free(watcher->directories); + // should we signify that the thread is dead? + return NULL; + } +#elif defined(__WIN32__) + #include + + #define BUF_LEN 1024 + #define DIRBRK '\\' + + static inline void *__internal_xWatcherProcess(void *argument) { + x_watcher *watcher = (x_watcher*) argument; + struct directory *directories = watcher->directories; + + // create an event list so we can still make use of the Windows API + HANDLE events[arr_count(directories)]; + for(int i = 0; i < arr_count(directories); i++) { + events[i] = directories[i].overlapped.hEvent; + } + + // obv first check if we need to stay alive + while(watcher->alive) { + // wait for any of the objects to respond + DWORD result = WaitForMultipleObjects(arr_count(directories), + events, FALSE, 50 /** timeout of 50ms **/); + + // test which object was it + int object_index = -1; + for(int i = 0; i < arr_count(directories); i++) { + if(result == (WAIT_OBJECT_0 + i)) { + object_index = i; + break; + } + } + + if(object_index == -1) { + if(result == WAIT_TIMEOUT) { + // it just timed out, let's continue + continue; + } else { + // RUNTIME ERROR! Let's bail + ExitProcess(GetLastError()); + } + } + + // shorhand for convenience + struct directory *dir = &directories[object_index]; + + // retrieve event data + DWORD bytes_transferred; + GetOverlappedResult(dir->handle, + &dir->overlapped, + &bytes_transferred, FALSE); + + // assign the data's pointer to a proper format for convenience + FILE_NOTIFY_INFORMATION *event = (FILE_NOTIFY_INFORMATION*) + dir->event_buffer; + + // loop through the data + for (;;) { + // figure out the wchar string size and allocate as needed + DWORD name_len = event->FileNameLength / sizeof(wchar_t); + char *name_char = malloc(sizeof(char)*(name_len+1)); + size_t converted_chars; + + // convert wchar* filename to char* + wcstombs_s(&converted_chars, name_char, + name_len+1, event->FileName, name_len); + + // convert to proper event type + XWATCHER_FILE_EVENT send_event = XWATCHER_FILE_NONE; + switch (event->Action) { + case FILE_ACTION_ADDED: + send_event = XWATCHER_FILE_CREATED; + break; + case FILE_ACTION_REMOVED: + send_event = XWATCHER_FILE_REMOVED; + break; + case FILE_ACTION_MODIFIED: + send_event = XWATCHER_FILE_MODIFIED; + break; + case FILE_ACTION_RENAMED_OLD_NAME: + case FILE_ACTION_RENAMED_NEW_NAME: + send_event = XWATCHER_FILE_RENAMED; + break; + default: + send_event = XWATCHER_FILE_UNSPECIFIED; + break; + } + + // find matching file (if any) + struct file *file = NULL; + for(size_t j = 0; j < arr_count(dir->files); j++) { + if(strcmp(dir->files[j].name, name_char) == 0) { + file = &dir->files[j]; + } + } + + // file found(?) + if(file != NULL) { + if(send_event != XWATCHER_FILE_NONE) { + // figure out the file path size + size_t filepath_size = strlen(dir->path); + filepath_size += strlen(file->name); + filepath_size += 2; + + // create file path string + char *filepath = (char*)malloc(filepath_size); + snprintf(filepath, filepath_size, "%s%c%s", + dir->path, DIRBRK, file->name); + + // callback + file->callback_func(send_event, + filepath, + file->context, + file->additional_data); + + // free that garbage + free(filepath); + } + } else { + // Cannot find file, lets try directory + if(dir->callback_func != NULL && + send_event != XWATCHER_FILE_NONE) { + dir->callback_func(send_event, + dir->path, + dir->context, + dir->additional_data); + } + } + + // free up the converted string + free(name_char); + + // Are there more events to handle? + if (event->NextEntryOffset) { + *((uint8_t**)&event) += event->NextEntryOffset; + } else { + break; + } + } + + DWORD dwNotifyFilter = + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_ATTRIBUTES | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE | + FILE_NOTIFY_CHANGE_LAST_ACCESS | + FILE_NOTIFY_CHANGE_CREATION | + FILE_NOTIFY_CHANGE_SECURITY; + BOOL recursive = FALSE; + + // Queue the next event + BOOL success = ReadDirectoryChangesW( + dir->handle, + dir->event_buffer, + BUF_LEN, + recursive, + dwNotifyFilter, + NULL, + &dir->overlapped, + NULL); + + if(!success) { + // get error code + DWORD error = GetLastError(); + char *message; + + // get error message + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL); + + fprintf(stderr, "ReadDirectoryChangesW failed: %s\n", + message); + + ExitProcess(error); + } + } + // cleanup time + for(size_t i = 0; i < arr_count(watcher->directories); i++) { + struct directory *directory = &watcher->directories[i]; + for(size_t j = 0; j < arr_count(directory->files); j++) { + struct file *file = &directory->files[j]; + free(file->name); + } + arr_free(directory->files); + free(directory->path); + free(directory->event_buffer); + CloseHandle(directory->handle); + } + arr_free(watcher->directories); + + return NULL; + } +#else + #error "Unsupported" +#endif + + +static inline x_watcher *xWatcher_create(void) { + x_watcher *watcher = (x_watcher*)malloc(sizeof(x_watcher)); + + arr_init(watcher->directories); + + #if defined(__WIN32__) + // literally noop + #elif defined(__linux__) + watcher->inotify_fd = inotify_init1(O_NONBLOCK); + if(watcher->inotify_fd < 0) { + perror("inotify_init"); + return NULL; + } + #else + #error "Unsupported" + #endif + + return watcher; +} + +static inline bool xWatcher_appendFile( + x_watcher *watcher, + xWatcher_reference *reference) { + char *path = strdup(reference->path); + + // the file MUST NOT contain slashed at the end + if(path[strlen(path)-1] == DIRBRK) + return false; + + char *filename = NULL; + + // we need to split the filename and path + for(size_t i = strlen(path)-1; i > 0; i--) { + if(path[i] == DIRBRK) { + path[i] = '\0'; // break the string, so it splits into two + filename = &path[i+1]; // set the rest of it as the filename + break; + } + } + + // If the directory is specifically local, treat it as such. + if(filename == NULL) { + filename = path; + } + + struct directory *dir = NULL; + + // check against the database of (pre-existing) directories + for(size_t i = 0; i < arr_count(watcher->directories); i++) { + // paths match + if(strcmp(watcher->directories[i].path, path) == 0) { + dir = &watcher->directories[i]; + } + } + + // directory exists, check if an callback has been already added + if(dir == NULL) { + struct directory new_dir; + + new_dir.callback_func = NULL; // DO NOT add callbacks if it's a file + new_dir.context = 0; // context should be invalid as well + new_dir.additional_data = NULL; // so should the data + new_dir.path = path; // add a path to the directory + #if defined(__linux__) + new_dir.inotify_watch_fd = -1; // invalidate inotify + #elif defined(__WIN32__) + new_dir.handle = NULL; + #else + #error "Unsupported" + #endif + + // initialize file arrays + arr_init(new_dir.files); + + // add the directory to the masses + arr_add(watcher->directories, new_dir); + + // move the pointer to the newly added element + dir = &watcher->directories[arr_count(watcher->directories)-1]; + } + + // search for the file + struct file *file = NULL; + for(size_t i = 0; i < arr_count(dir->files); i++) { + if(strcmp(dir->files[i].name, filename) == 0) { + file = &dir->files[i]; + } + } + + if(file != NULL) { + return false; // file already exists, that's an ERROR + } + + struct file new_file; + // avoid an invalid free because this shares the memory space + // of the full path string + new_file.name = strdup(filename); + new_file.context = reference->context; + new_file.additional_data = reference->additional_data; + new_file.callback_func = reference->callback_func; + + // the the element + arr_add(dir->files, new_file); + + // and move the pointer to the newly added element + file = &dir->files[arr_count(watcher->directories)-1]; + + // add the file watcher + #if defined(__linux__) + if(dir->inotify_watch_fd == -1) { + dir->inotify_watch_fd = inotify_add_watch( + watcher->inotify_fd, + path, + IN_ALL_EVENTS); + if(dir->inotify_watch_fd == -1) { + perror("inotify_watch_fd"); + return false; + } + } + #elif defined(__WIN32__) + // add directory path + dir->handle = CreateFile(dir->path, + FILE_LIST_DIRECTORY, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + if(dir->handle == INVALID_HANDLE_VALUE) { + // get error code + DWORD error = GetLastError(); + char *message; + + // get error message + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL); + + fprintf(stderr, "CreateFile failed: %s\n", + message); + + return false; + } + + // create event structure + dir->overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); + if(dir->overlapped.hEvent == NULL) { + // get error code + DWORD error = GetLastError(); + char *message; + + // get error message + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL); + + fprintf(stderr, "CreateEvent failed: %s\n", + message); + + return false; + } + + // allocate the event buffer + dir->event_buffer = malloc(BUF_LEN); + if(dir->event_buffer == NULL) { + fprintf(stderr, "malloc failed at __FILE__:__LINE__!\n"); + return false; + } + + // set reading params + BOOL success = ReadDirectoryChangesW( + dir->handle, dir->event_buffer, BUF_LEN, TRUE, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_LAST_WRITE, + NULL, &dir->overlapped, NULL); + if(!success) { + // get error code + DWORD error = GetLastError(); + char *message; + + // get error message + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL); + + fprintf(stderr, "ReadDirectoryChangesW failed: %s\n", + message); + + return false; + } + #else + #error "Unsupported" + #endif + return true; +} + +static inline bool xWatcher_appendDir( + x_watcher *watcher, + xWatcher_reference *reference) { + char *path = strdup(reference->path); + + // inotify only works with directories that do NOT have a front-slash + // at the end, so we have to make sure to cut that out + if(path[strlen(path)-1] == DIRBRK) + path[strlen(path)-1] = '\0'; + + struct directory *dir = NULL; + + // check against the database of (pre-existing) directories + for(size_t i=0; i < arr_count(watcher->directories); i++) { + // paths match + if(strcmp(watcher->directories[i].path, path) == 0) { + dir = &watcher->directories[i]; + } + } + + // directory exists, check if an callback has been already added + if(dir) { + // ERROR, CALLBACK EXISTS + if(dir->callback_func) { + return false; + } + + dir->callback_func = reference->callback_func; + dir->context = reference->context; + dir->additional_data = reference->additional_data; + } else { + // keep an eye for this one as it's on the stack + struct directory dir; + + dir.path = path; + dir.callback_func = reference->callback_func; + dir.context = reference->context; + dir.additional_data = reference->additional_data; + + // initialize file arrays + arr_init(dir.files); + + #if defined(__linux__) + dir.inotify_watch_fd = inotify_add_watch( + watcher->inotify_fd, + dir.path, + IN_ALL_EVENTS); + if(dir.inotify_watch_fd == -1) { + perror("inotify_watch_fd"); + return false; + } + #elif defined(__WIN32__) + // add directory path + dir.handle = CreateFile(dir.path, + FILE_LIST_DIRECTORY, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + if(dir.handle == INVALID_HANDLE_VALUE) { + // get error code + DWORD error = GetLastError(); + char *message; + + // get error message + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL); + + fprintf(stderr, "CreateFile failed: %s\n", + message); + + return false; + } + + // create event structure + dir.overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL); + if(dir.overlapped.hEvent == NULL) { + // get error code + DWORD error = GetLastError(); + char *message; + + // get error message + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL); + + fprintf(stderr, "CreateEvent failed: %s\n", + message); + + return false; + } + + // allocate the event buffer + dir.event_buffer = malloc(BUF_LEN); + if(dir.event_buffer == NULL) { + fprintf(stderr, "malloc failed at __FILE__:__LINE__!\n"); + return false; + } + + // set reading params + BOOL success = ReadDirectoryChangesW( + dir.handle, dir.event_buffer, BUF_LEN, TRUE, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_LAST_WRITE, + NULL, &dir.overlapped, NULL); + if(!success) { + // get error code + DWORD error = GetLastError(); + char *message; + + // get error message + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &message, + 0, NULL); + + fprintf(stderr, "ReadDirectoryChangesW failed: %s\n", + message); + + return false; + } + #else + #error "Unsupported" + #endif + + arr_add(watcher->directories, dir); + } + + return true; +} + +static inline bool xWatcher_start(x_watcher *watcher) { + watcher->alive = true; + + // create watcher thread + watcher->thread_id = pthread_create( + &watcher->thread, + NULL, + __internal_xWatcherProcess, + watcher); + + if(watcher->thread_id != 0) { + perror("pthread_create"); + watcher->alive = false; + return false; + } + + return true; +} + +static inline void xWatcher_destroy(x_watcher *watcher) { + void *ret; + watcher->alive = false; + pthread_join(watcher->thread, &ret); + free(watcher); +} + +#endif + diff --git a/townengine/audio.c b/townengine/audio/audio.c similarity index 98% rename from townengine/audio.c rename to townengine/audio/audio.c index a66a4d1..5d6e9d1 100644 --- a/townengine/audio.c +++ b/townengine/audio/audio.c @@ -1,8 +1,7 @@ -#include "audio/internal_api.h" -#include "audio.h" -#include "config.h" -#include "context.h" -#include "util.h" +#include "townengine/audio/internal_api.h" +#include "townengine/config.h" +#include "townengine/context.h" +#include "townengine/util.h" #include #include diff --git a/townengine/camera.c b/townengine/camera.c index adea359..1ac1c26 100644 --- a/townengine/camera.c +++ b/townengine/camera.c @@ -1,5 +1,5 @@ #include "camera.h" -#include "context.h" +#include "townengine/context.h" #include diff --git a/townengine/context.c b/townengine/context.c deleted file mode 100644 index 2c8a7bc..0000000 --- a/townengine/context.c +++ /dev/null @@ -1,3 +0,0 @@ -#include "context.h" - -t_ctx ctx = {0}; diff --git a/townengine/context.h b/townengine/context.h index cbc3193..3b078dd 100644 --- a/townengine/context.h +++ b/townengine/context.h @@ -60,6 +60,7 @@ typedef struct context { bool resync_flag; bool was_successful; bool window_size_has_changed; + bool initialization_needed; } t_ctx; extern t_ctx ctx; diff --git a/townengine/context/context.c b/townengine/context/context.c new file mode 100644 index 0000000..efbbab5 --- /dev/null +++ b/townengine/context/context.c @@ -0,0 +1,3 @@ +#include "townengine/context.h" + +t_ctx ctx = {0}; diff --git a/townengine/game_api.h b/townengine/game_api.h index ec1e30b..7ffeb9b 100644 --- a/townengine/game_api.h +++ b/townengine/game_api.h @@ -3,14 +3,20 @@ #define GAME_API_H -#include "context.h" -#include "rendering.h" -#include "audio.h" -#include "util.h" -#include "input.h" +#include "townengine/context.h" +#include "townengine/rendering.h" +#include "townengine/audio.h" +#include "townengine/util.h" +#include "townengine/input.h" -/* application provided */ + +#ifndef HOT_RELOAD_SUPPORT +/* sole game logic and display function. + all state must be used from and saved to supplied state pointer. */ extern void game_tick(void); + +/* called when application is closing. */ extern void game_end(void); +#endif #endif diff --git a/townengine/input.c b/townengine/input/input.c similarity index 98% rename from townengine/input.c rename to townengine/input/input.c index bf9109f..96d46cf 100644 --- a/townengine/input.c +++ b/townengine/input/input.c @@ -1,6 +1,6 @@ -#include "input.h" -#include "util.h" -#include "context.h" +#include "townengine/input/internal_api.h" +#include "townengine/context.h" +#include "townengine/util.h" #include #include @@ -311,3 +311,8 @@ void input_set_mouse_captured(struct input_state *input, bool enabled) { bool input_is_mouse_captured(struct input_state *input) { return SDL_GetRelativeMouseMode(); } + + +void input_reset_state(struct input_state *input) { + stbds_shfree(input->action_hash); +} diff --git a/townengine/input/internal_api.h b/townengine/input/internal_api.h new file mode 100644 index 0000000..b66e465 --- /dev/null +++ b/townengine/input/internal_api.h @@ -0,0 +1,8 @@ +#ifndef INPUT_INTERNAL_API_H +#define INPUT_INTERNAL_API_H + +#include "townengine/input.h" + +void input_reset_state(struct input_state *input); + +#endif diff --git a/townengine/main.c b/townengine/main.c index 292ae53..e8bfd89 100644 --- a/townengine/main.c +++ b/townengine/main.c @@ -1,11 +1,11 @@ -#include "context.h" -#include "rendering.h" -#include "input.h" -#include "util.h" -#include "game_api.h" -#include "audio/internal_api.h" -#include "textures/internal_api.h" -#include "rendering/internal_api.h" +#include "townengine/context.h" +#include "townengine/rendering.h" +#include "townengine/input/internal_api.h" +#include "townengine/util.h" +#include "townengine/game_api.h" +#include "townengine/audio/internal_api.h" +#include "townengine/textures/internal_api.h" +#include "townengine/rendering/internal_api.h" #include #include @@ -20,6 +20,30 @@ #include #include +#ifdef HOT_RELOAD_SUPPORT +#include +#include + +#ifdef __linux__ + #define GAME_OBJECT_PATH "../apps/testgame/libgame.so" +#else + #define GAME_OBJECT_PATH "game.dll" +#endif +#endif + + +#ifdef HOT_RELOAD_SUPPORT + +static void (*game_tick)(void); +static void (*game_end)(void); + +#endif + +/* is used to lock x-watcher driven hot reloading */ +static uint64_t game_object_last_time_modified; +static bool game_object_loaded_after_modification = true; +static SDL_mutex *game_object_mutex; + static void poll_events(void) { SDL_Event e; @@ -40,9 +64,15 @@ static void poll_events(void) { case SDL_WINDOWEVENT_SIZE_CHANGED: ctx.window_size_has_changed = true; break; + + default: + break; } break; + + default: + break; } } } @@ -161,10 +191,12 @@ static void main_loop(void) { } input_state_update(&ctx.input); + game_tick(); ctx.frame_accumulator -= ctx.desired_frametime; ctx.tick_count = (ctx.tick_count % ULLONG_MAX) + 1; + ctx.initialization_needed = false; } } @@ -341,6 +373,8 @@ static bool initialize(void) { } */ + game_object_mutex = SDL_CreateMutex(); + return true; fail: @@ -360,6 +394,8 @@ static void clean_up(void) { arrfree(ctx.render_queue_2d); textures_cache_deinit(&ctx.texture_cache); + SDL_DestroyMutex(game_object_mutex); + PHYSFS_deinit(); TTF_Quit(); IMG_Quit(); @@ -367,6 +403,108 @@ 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; + } +} + +#endif + + +static void load_game(void) { +#ifdef HOT_RELOAD_SUPPORT + x_watcher *watcher = xWatcher_create(); + + xWatcher_reference dir; + dir.path = GAME_OBJECT_PATH; + dir.callback_func = hot_reload_callback; + + xWatcher_appendFile(watcher, &dir); + xWatcher_start(watcher); + + // xWatcher_destroy(watcher); + + load_game_shared_object(); + +#endif +} + + +static void reset_state(void) { + input_reset_state(&ctx.input); +} + + int main(int argc, char **argv) { ctx.argc = argc; ctx.argv = argv; @@ -384,11 +522,27 @@ int main(int argc, char **argv) { } } + load_game(); + ctx.was_successful = true; - while (ctx.is_running) + ctx.initialization_needed = true; + + while (ctx.is_running) { + /* only load the modified library after some time, as compilers make a lot of modifications */ + SDL_LockMutex(game_object_mutex); + if (ctx.tick_count - game_object_last_time_modified > 10 && !game_object_loaded_after_modification) { + load_game_shared_object(); + game_object_loaded_after_modification = true; + ctx.initialization_needed = true; + reset_state(); + } + SDL_UnlockMutex(game_object_mutex); + main_loop(); + } game_end(); + clean_up(); return ctx.was_successful ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/townengine/null.c b/townengine/null.c new file mode 100644 index 0000000..465596d --- /dev/null +++ b/townengine/null.c @@ -0,0 +1,2 @@ +/* cmake nonsense */ +struct a { int b; }; diff --git a/townengine/rendering.c b/townengine/rendering.c index 470fd58..6b2c969 100644 --- a/townengine/rendering.c +++ b/townengine/rendering.c @@ -3,7 +3,7 @@ #include "rendering/triangles.h" #include "rendering/circles.h" #include "textures/internal_api.h" -#include "context.h" +#include "townengine/context.h" #include #include diff --git a/townengine/rendering/circles.h b/townengine/rendering/circles.h index 67ce710..ddbe48f 100644 --- a/townengine/rendering/circles.h +++ b/townengine/rendering/circles.h @@ -3,7 +3,7 @@ #define CIRCLES_H #include "../util.h" -#include "../context.h" +#include "townengine/context.h" #include "internal_api.h" #include diff --git a/townengine/rendering/sprites.h b/townengine/rendering/sprites.h index 1f6ae68..5569200 100644 --- a/townengine/rendering/sprites.h +++ b/townengine/rendering/sprites.h @@ -3,7 +3,7 @@ #define SPRITES_H #include "../rendering.h" -#include "../context.h" +#include "townengine/context.h" #include "../util.h" #include "../textures/internal_api.h" #include "quad_element_buffer.h" diff --git a/townengine/rendering/triangles.h b/townengine/rendering/triangles.h index 5ed2de6..9aa579c 100644 --- a/townengine/rendering/triangles.h +++ b/townengine/rendering/triangles.h @@ -2,7 +2,7 @@ #ifndef TRIANGLES_H #define TRIANGLES_H -#include "../context.h" +#include "townengine/context.h" #include "internal_api.h" #include "../textures/internal_api.h" diff --git a/townengine/scripting.c b/townengine/scripting.c index 9d425b3..069e07b 100644 --- a/townengine/scripting.c +++ b/townengine/scripting.c @@ -1,6 +1,6 @@ #include "scripting.h" #include "util.h" -#include "context.h" +#include "townengine/context.h" #include #include diff --git a/townengine/util.c b/townengine/util.c index 770a53a..a598679 100644 --- a/townengine/util.c +++ b/townengine/util.c @@ -1,5 +1,5 @@ #include "util.h" -#include "context.h" +#include "townengine/context.h" #include #include