hot reloading and friends
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -5,6 +5,8 @@ | ||||
| !Makefile | ||||
|  | ||||
| **/*.exe | ||||
| **/*.so | ||||
| **/*.dll | ||||
|  | ||||
| .vs/ | ||||
| .vscode/ | ||||
|   | ||||
| @@ -18,6 +18,8 @@ endif () | ||||
| set(TOWNENGINE_TARGET townengine CACHE INTERNAL "") | ||||
| set(TOWNENGINE_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "") | ||||
|  | ||||
| add_compile_options($<$<BOOL:${TOWNENGINE_HOT_RELOAD}>:-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}" | ||||
|                                              $<$<BOOL:${TOWNENGINE_HOT_RELOAD}>: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 $<TARGET_FILE_DIR:${target}> | ||||
|                                                     $<TARGET_RUNTIME_DLLS:${target}> | ||||
|                 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) | ||||
|   | ||||
| @@ -4,11 +4,13 @@ | ||||
| #include <malloc.h> | ||||
|  | ||||
|  | ||||
| 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 */ | ||||
|   | ||||
| @@ -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}) | ||||
|   | ||||
| @@ -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 <SDL_scancode.h> | ||||
| #include <stdio.h> | ||||
| #include <malloc.h> | ||||
| @@ -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); | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| #include "scene.h" | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
| #include "townengine/tabela.h" | ||||
|  | ||||
| #define STB_PERLIN_IMPLEMENTATION | ||||
| #include <stb_perlin.h> | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
| #include "ingame.h" | ||||
| #include "../world.h" | ||||
| #include "../player.h" | ||||
|  | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| #include "world.h" | ||||
| #include "townengine/game_api.h" | ||||
|  | ||||
| #include "world.h" | ||||
|  | ||||
| #include <stdbool.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdint.h> | ||||
|   | ||||
							
								
								
									
										77
									
								
								third-party/x-watcher/array.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								third-party/x-watcher/array.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| #ifndef ARRAY_H | ||||
| #define ARRAY_H | ||||
|  | ||||
| #include <string.h> | ||||
| #include <stdlib.h> | ||||
|  | ||||
| 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 | ||||
|  | ||||
|  | ||||
							
								
								
									
										838
									
								
								third-party/x-watcher/x-watcher.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										838
									
								
								third-party/x-watcher/x-watcher.h
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,838 @@ | ||||
| #ifndef __X_WATCHER_H | ||||
| #define __X_WATCHER_H | ||||
|  | ||||
| // necessary includes for the public stuff | ||||
| #include <pthread.h> | ||||
| #include <stdbool.h> | ||||
| #if defined(__linux__) | ||||
| 	// noop | ||||
| #elif defined(__WIN32__) | ||||
| 	#include <windows.h> | ||||
| 	#include <stdint.h> | ||||
| #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 <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
|  | ||||
| #include "array.h" | ||||
|  | ||||
| #ifdef __linux__ | ||||
| 	#include <sys/types.h> | ||||
| 	#include <sys/inotify.h> | ||||
| 	#include <errno.h> | ||||
| 	#include <unistd.h> | ||||
| 	#include <poll.h>  // for POLLIN | ||||
| 	#include <fcntl.h> // 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 <tchar.h> | ||||
|  | ||||
| 	#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 | ||||
|  | ||||
| @@ -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 <SDL2/SDL.h> | ||||
| #include <stb_ds.h> | ||||
| @@ -1,5 +1,5 @@ | ||||
| #include "camera.h" | ||||
| #include "context.h" | ||||
| #include "townengine/context.h" | ||||
|  | ||||
| #include <math.h> | ||||
|  | ||||
|   | ||||
| @@ -1,3 +0,0 @@ | ||||
| #include "context.h" | ||||
|  | ||||
| t_ctx ctx = {0}; | ||||
| @@ -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; | ||||
|   | ||||
							
								
								
									
										3
									
								
								townengine/context/context.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								townengine/context/context.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| #include "townengine/context.h" | ||||
|  | ||||
| t_ctx ctx = {0}; | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 <SDL2/SDL.h> | ||||
| #include <stb_ds.h> | ||||
| @@ -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); | ||||
| } | ||||
							
								
								
									
										8
									
								
								townengine/input/internal_api.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								townengine/input/internal_api.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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 <SDL2/SDL.h> | ||||
| #include <SDL2/SDL_ttf.h> | ||||
| @@ -20,6 +20,30 @@ | ||||
| #include <tgmath.h> | ||||
| #include <limits.h> | ||||
|  | ||||
| #ifdef HOT_RELOAD_SUPPORT | ||||
| #include <x-watcher.h> | ||||
| #include <dlfcn.h> | ||||
|  | ||||
| #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; | ||||
|   | ||||
							
								
								
									
										2
									
								
								townengine/null.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								townengine/null.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| /* cmake nonsense */ | ||||
| struct a { int b; }; | ||||
| @@ -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 <SDL2/SDL.h> | ||||
| #include <glad/glad.h> | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| #define CIRCLES_H | ||||
|  | ||||
| #include "../util.h" | ||||
| #include "../context.h" | ||||
| #include "townengine/context.h" | ||||
| #include "internal_api.h" | ||||
|  | ||||
| #include <SDL2/SDL.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" | ||||
|   | ||||
| @@ -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" | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| #include "scripting.h" | ||||
| #include "util.h" | ||||
| #include "context.h" | ||||
| #include "townengine/context.h" | ||||
|  | ||||
| #include <SDL2/SDL.h> | ||||
| #include <umka_api.h> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #include "util.h" | ||||
| #include "context.h" | ||||
| #include "townengine/context.h" | ||||
|  | ||||
| #include <SDL2/SDL.h> | ||||
| #include <physfsrwops.h> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user