├── .gitignore ├── README.md ├── example ├── CMakeLists.txt ├── scripts │ └── math │ │ └── trig.c └── source.c └── scriptosaurus.h /.gitignore: -------------------------------------------------------------------------------- 1 | scriptosaurus/ 2 | example/scripts/.bin/ 3 | example/build/ 4 | cmake-build-*/ 5 | .idea 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scriptosaurus - Single-Header C Live Scripting 2 | 3 | ### Introduction 4 | Scriptosaurus is a single-header C hot-reloading library. It's an end-to-end solution that runs a daemon on the specified user directory, recompiles the code on changes and hot-swaps the shared library replacing registered function pointers. 5 | Scripts are individual `*.c` source files that get compiled to a single shared library and can export multiple functions. If a script is located in `foo/bar.c` it is referenced to as `foo$bar` (path separators are replaced by `$`) from the application side. 6 | If hot-reloading is not enabled (by **not** defining `SSR_LIVE`) the library will run in **Release** mode, compiling all the scripts into a single shared library with zero-overhead at runtime. 7 | 8 | 9 | ## Example 10 | ``` 11 | / 12 | main.c 13 | scripts/ 14 | foo/ 15 | bar.c 16 | ``` 17 | 18 | `main.c` 19 | 20 | ```c 21 | #define SSR_LIVE // Live editing 22 | #define SSR_IMPLEMENTATION // Platform 23 | #include "scriptosaurus.h" // Single header 24 | 25 | typedef int(*fun_t)(int); 26 | 27 | void main() 28 | { 29 | fun_t my_fun; 30 | ssr_t ssr; 31 | ssr_init(&ssr, "scripts", NULL); 32 | 33 | // Always call run before any other ssr_add 34 | ssr_run(&ssr); 35 | 36 | // Links 37 | ssr_add(&ssr, "foo$bar", "my_script_fun", &my_fun); 38 | 39 | // ... 40 | // If the content of scripts/foo/bar.c, it will be recompiled 41 | // and my_fun updated accordingly 42 | } 43 | ``` 44 | 45 | `scripts/foo/bar.c` 46 | ``` 47 | #define SSR_SCRIPT 48 | #include "scriptosaurus.h" 49 | 50 | ssr_fun(int, my_script_fun)(int v) 51 | { 52 | return i * 2; 53 | } 54 | ``` 55 | 56 | ## API 57 | 58 | **`ssr_t`** Library object, each instance works on a single directory. Multiple are supported. 59 | ```c 60 | typedef struct _ssr_t { } ssr_t; 61 | ``` 62 | 63 | **`ssr_init()`** Initializes the library and launches the daemon (if `SSR_LIVE` is defined) 64 | ```c 65 | bool ssr_init(struct ssr_t* ssr, const char* root, struct ssr_config_t* config) 66 | ``` 67 | ``` 68 | Arguments: 69 | - ssr: Library object 70 | - root: Relative of absolute path of the directory to be searched for scripts 71 | - config: Compiler options, see ssr_config_t for more information 72 | Returns: 73 | True if initialization was successful, false otherwise 74 | ``` 75 | 76 | **`ssr_add`** Registers a function for hot-reloading. 77 | ```c 78 | bool ssr_add(struct ssr_t* srr, const char* script_id, const char* fun_name, ssr_func_t* user_routine) 79 | ``` 80 | ``` 81 | Arguments: 82 | - ssr: Library object 83 | - script_id: Unique script identifier relative to root directory as specified in ssr_init(). Path separators are replaced by '$'. 84 | - fun_name: Name of the function exported by the script 85 | - user_routine: Pointer to function pointer which is to be registered for hot-reloading. Remember to cast it to the correct function type before loading 86 | 87 | Returns: 88 | True if registering was successful and the function pointer will be updated, false otherwise. 89 | ``` 90 | 91 | **`ssr_remove`** Unregisters a function previously registered with `ssr_add` 92 | ```c 93 | void ssr_remove(struct ssr_t* ssr, const char* script_id, const char* fun_name, ssr_func_t* user_routine) 94 | ``` 95 | ``` 96 | Arguments: 97 | - ssr: Library object 98 | - script_id: Unique script identifier relative to root directory as specified in ssr_init(). Path separators are replaced by '$'. 99 | - fun_name: Name of the function user_routine refers to. 100 | - user_routine: Address of function pointer previously registered for listening. 101 | ``` 102 | 103 | Compiler and related options can be controlled by `ssr_config_t`. By default the client's configuration is used. 104 | 105 | ```c 106 | typedef struct ssr_config_t 107 | { 108 | int compiler; // SSR_COMPILER 109 | int msvc_ver; // SSR_MSVC_VER 110 | int target_arch; // SSR_ARCH 111 | int flags; // SSR_FLAGS 112 | char* compile_args_beg; // added before any other flag 113 | char* compile_args_end; // added before input files 114 | char* link_args_beg; // added before any other flag 115 | char* link_args_end; // added before input files 116 | char** include_directories; // compiler include directories 117 | char** link_libraries; // libraries to link to - absolute paths 118 | char** defines; // name=value strings 119 | size_t num_include_directories; 120 | size_t num_link_libraries; 121 | size_t num_defines; 122 | }ssr_config_t; 123 | ``` 124 | 125 | **Note**: For more info please browse `scriptosaurus.h` 126 | 127 | ## Supported platforms 128 | 129 | **Backends**: Win32, Posix 130 | **Compilers**: gcc, clang, msvc 131 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2 FATAL_ERROR) 2 | project(Scriptosaurus_example VERSION 1.0 LANGUAGES C) 3 | add_executable(Scriptosaurus_example ../scriptosaurus.h source.c) 4 | 5 | set(compile_definitions "") 6 | set(link_libraries "") 7 | 8 | if (WIN32) 9 | list(APPEND compile_definitions _CRT_SECURE_NO_WARNINGS) 10 | set_target_properties(Scriptosaurus_example PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) 11 | endif (WIN32) 12 | 13 | if (UNIX) 14 | list(APPEND link_libraries pthread dl) 15 | endif (UNIX) 16 | 17 | target_compile_definitions(Scriptosaurus_example PUBLIC ${compile_definitions}) 18 | target_link_libraries(Scriptosaurus_example PUBLIC ${link_libraries}) 19 | -------------------------------------------------------------------------------- /example/scripts/math/trig.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define SSR_SCRIPT 5 | #include "../../../scriptosaurus.h" 6 | 7 | ssr_func(float, my_sin)(float v) 8 | { 9 | printf("my_sin(%f)=", v); 10 | return sinf(v); 11 | } 12 | 13 | ssr_func(float, my_cos)(float v) 14 | { 15 | printf("my_cos(%f)=", v); 16 | return cosf(v); 17 | } 18 | 19 | ssr_func(float, my_tan)(float v) 20 | { 21 | printf("my_tan(%f)=", v); 22 | return tanf(v); 23 | } 24 | 25 | ssr_func(float, my_tan2)(float v) 26 | { 27 | return v; 28 | } -------------------------------------------------------------------------------- /example/source.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SSR_LIVE 4 | #define SSR_IMPLEMENTATION 5 | #include "../scriptosaurus.h" 6 | 7 | // !!! remember that this is called asynchronously 8 | void msg_callback(int type, const char* msg) 9 | { 10 | if (type == SSR_CB_ERR || type == SSR_CB_WARN) 11 | fprintf(stderr, "ssr: %s\n", msg); 12 | else 13 | fprintf(stdout, "ssr: %s\n", msg); 14 | } 15 | 16 | struct Entry 17 | { 18 | _ssr_hash_t hash; 19 | _ssr_str_t id; 20 | int x; 21 | }; 22 | 23 | _ssr_map_t map; 24 | 25 | typedef float(*func_t)(float); 26 | int main() 27 | { 28 | ssr_t ssr; 29 | 30 | // !!! SET YOUR PATH HERE 31 | // Doesn't necessarily have to be global, just make sure it's relative to your working directory 32 | ssr_init(&ssr, "scripts", NULL); 33 | 34 | ssr_cb(&ssr, SSR_CB_ERR | SSR_CB_WARN | SSR_CB_INFO, msg_callback); 35 | 36 | // Always call run before any other ssr_add 37 | ssr_run(&ssr); 38 | 39 | ssr_func_t func = NULL; 40 | while (true) 41 | { 42 | char script_name[255]; 43 | char func_name[255]; 44 | float arg; 45 | printf(">"); 46 | scanf("%s %s %f", script_name, func_name, &arg); 47 | 48 | if (strcmp("quit", script_name) == 0) 49 | break; 50 | 51 | ssr_add(&ssr, script_name, func_name, &func); 52 | while (func == NULL); // Waiting for script to be compiled 53 | float ret = (*(func_t)func)(arg); 54 | printf("%f\n", ret); 55 | ssr_remove(&ssr, script_name, func_name, &func); 56 | func = NULL; 57 | } 58 | 59 | ssr_destroy(&ssr); 60 | return EXIT_SUCCESS; 61 | } 62 | -------------------------------------------------------------------------------- /scriptosaurus.h: -------------------------------------------------------------------------------- 1 | #ifndef _SSR_H_GUARD_ 2 | #define _SSR_H_GUARD_ 3 | 4 | #ifdef _WIN32 5 | #define SSR_WIN 6 | #ifdef _WIN64 7 | #define SSR_64 8 | #else 9 | #define SSR_32 10 | #endif 11 | #elif defined(__linux__) 12 | #define SSR_LINUX 13 | #ifdef __x86_64__ 14 | #define SSR_64 15 | #else 16 | #define SSR_32 17 | #endif 18 | #else 19 | #error 20 | #endif 21 | 22 | #if defined(SSR_SCRIPT) 23 | #if __cplusplus__ 24 | #define SSR_EXTERN extern "C" 25 | #else 26 | #define SSR_EXTERN 27 | #endif 28 | 29 | #if defined(SSR_WIN) 30 | #define SSR_EXPORT __declspec(dllexport) 31 | #elif defined(SSR_LINUX) 32 | #define SSR_EXPORT __attribute__((visibility("default"))) 33 | #endif 34 | 35 | #if defined(SSR_SCRIPTID) 36 | #define _ssr_func_paste(a, b) a##$##b 37 | #define _ssr_func_eval(a, b) _ssr_func_paste(a, b) 38 | #define ssr_func(ret, name) SSR_EXTERN __declspec(dllexport) ret _ssr_func_eval(SSR_SCRIPTID, name) 39 | #else 40 | #define ssr_func(ret, name) SSR_EXTERN SSR_EXPORT ret name 41 | #endif 42 | #else 43 | 44 | // libc includes 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | 54 | /*----------------------------------------------------------------------------- 55 | Scriptosaurus API - abbreviation is ssr 56 | 57 | Usage: 58 | For usage see example/ 59 | Remember to call run() before any add() otherwise scripts won't be loaded 60 | when not in live. 61 | 62 | Macros: 63 | SSR_LIVE - If defined scripts will dynamically recompiled and updated by the 64 | daemon. Otherwise all the files will be compiled on the run(). 65 | */ 66 | 67 | #ifndef SSR_MAX_SCRIPTS 68 | #define SSR_MAX_SCRIPTS 128 // TODO: not really needed atm 69 | #endif 70 | 71 | #ifndef SSR_BIN_DIR 72 | #define SSR_BIN_DIR ".bin" 73 | #endif 74 | 75 | #ifndef SSR_SL_LEN 76 | #define SSR_SL_LEN 10 77 | #endif 78 | 79 | #ifndef SSR_SINGLE_SL_NAME 80 | #define SSR_SINGLE_SL_NAME "ssr" 81 | #endif 82 | 83 | #ifndef SSR_FILE_EXTS 84 | #define SSR_FILE_EXTS ".c", "" // end w/ empty string 85 | #endif 86 | 87 | #ifndef SSR_LOG_BUF 88 | #define SSR_LOG_BUF 1024 * 4 89 | #endif 90 | 91 | #ifndef SSR_COMPILER_BUF 92 | #define SSR_COMPILER_BUF 1024 * 4 93 | #endif 94 | 95 | #ifndef SSR_SLEEP_MS 96 | #define SSR_SLEEP_MS 16 97 | #endif 98 | 99 | #ifdef SSR_STATIC 100 | #define SSR_DEF static 101 | #else 102 | #define SSR_DEF extern 103 | #endif 104 | 105 | enum SSR_COMPILER { 106 | SSR_COMPILER_MSVC, // no need for vcvars/cl/link to be in PATH 107 | SSR_COMPILER_CLANG, // assumes clang is a valid command 108 | SSR_COMPILER_GCC, // assumes gcc is a valid command 109 | }; 110 | 111 | enum SSR_MSVC_VER { 112 | SSR_MSVC_VER_14_1, // Visual Studio 2017 113 | SSR_MSVC_VER_14_0, // Visual Studio 2015 114 | // .. Older MSVC versions should work out of the box 115 | }; 116 | 117 | enum SSR_ARCH { 118 | SSR_ARCH_X86, 119 | SSR_ARCH_X64, 120 | SSR_ARCH_ARM, 121 | }; 122 | 123 | enum SSR_FLAGS { 124 | SSR_FLAGS_GEN_DEBUG = 1, 125 | 126 | SSR_FLAGS_GEN_OPT1 = 1 << 1, 127 | SSR_FLAGS_GEN_OPT2 = 1 << 2, 128 | }; 129 | 130 | #ifdef __cplusplus 131 | extern "C" { 132 | #endif 133 | 134 | typedef enum _SSR_CB_TYPE { 135 | SSR_CB_INFO = 1, 136 | SSR_CB_WARN = 1 << 1, 137 | SSR_CB_ERR = 1 << 2, 138 | SSR_CB_ALL = SSR_CB_INFO | SSR_CB_WARN | SSR_CB_ERR, 139 | } SSR_CB_TYPE; 140 | 141 | struct ssr_t; 142 | 143 | typedef struct ssr_config_t { 144 | // If not specified they are filled in matching the client compiler 145 | int compiler; // SSR_COMPILER 146 | int msvc_ver; // SSR_MSVC_VER 147 | const char* msvc141_path; 148 | int target_arch; // SSR_ARCH 149 | int flags; // SSR_FLAGS 150 | char* compile_args_beg; // added before any other flag 151 | char* compile_args_end; // added before input files 152 | char* link_args_beg; // added before any other flag 153 | char* link_args_end; // added before input files 154 | char** include_directories; // compiler include directories 155 | char** link_libraries; // libraries to link to - absolute paths 156 | char** defines; // name=value strings 157 | size_t num_include_directories; 158 | size_t num_link_libraries; 159 | size_t num_defines; 160 | } ssr_config_t; 161 | 162 | 163 | typedef void* ssr_func_t; 164 | typedef void (*ssr_cb_t)(int, const char*); // flags is SSR_CB_*** | [SSR_CB_ERROR] 165 | typedef void* ssr_routine_t; 166 | #define SSR_SEP \ 167 | '$' // Not the best looking separator indeed, but it makes life slightly easier as it's also 168 | // valid as a function character (TODO) 169 | 170 | SSR_DEF bool ssr_init(struct ssr_t*, 171 | const char* root, 172 | struct ssr_config_t* 173 | config); // if config==NULL a default one is generated based on the host arch/compiler 174 | SSR_DEF void ssr_destroy(struct ssr_t*); 175 | SSR_DEF bool ssr_run(struct ssr_t*); 176 | SSR_DEF bool ssr_add(struct ssr_t*, const char*, const char*, ssr_func_t*); 177 | SSR_DEF void ssr_remove(struct ssr_t*, const char*, const char*, ssr_func_t*); 178 | SSR_DEF void ssr_cb(struct ssr_t*, int mask, ssr_cb_t); 179 | 180 | #endif 181 | 182 | #ifdef __cplusplus 183 | } 184 | #endif 185 | 186 | /*----------------------------------------------------------------------------- 187 | Internal API 188 | _ssr_str 189 | _ssr_vec 190 | _ssr_map 191 | */ 192 | #ifdef SSR_IMPLEMENTATION 193 | 194 | static void _ssr_log(int flags, const char* fmt, ...); 195 | 196 | typedef struct __ssr_str_t { 197 | char* b; 198 | } _ssr_str_t; 199 | 200 | static char* _ssr_alloc(size_t len); 201 | static void _ssr_free(char* ptr); 202 | 203 | static _ssr_str_t _ssr_str_e(void); 204 | static _ssr_str_t _ssr_str(const char* src); 205 | static _ssr_str_t _ssr_str_f(const char* fmt, ...); 206 | static _ssr_str_t _ssr_str_rnd(size_t len); 207 | static void _ssr_str_destroy(_ssr_str_t); 208 | static char* _ssr_strncpy(char* a, const char* b, size_t n); 209 | 210 | typedef struct __ssr_vec_t { 211 | uint8_t* beg; 212 | uint8_t* cur; 213 | uint8_t* end; 214 | size_t size; 215 | } _ssr_vec_t; 216 | 217 | static void _ssr_vec(_ssr_vec_t* vec, size_t stride, size_t len); 218 | static void _ssr_vec_destroy(_ssr_vec_t* vec); 219 | static size_t _ssr_vec_capacity(const _ssr_vec_t* vec); 220 | static size_t _ssr_vec_len(_ssr_vec_t* vec); 221 | static void* _ssr_vec_at(_ssr_vec_t* vec, size_t idx); 222 | static void _ssr_vec_push(_ssr_vec_t* vec, const void* el); 223 | static void _ssr_vec_remove(_ssr_vec_t* vec, const void* el); 224 | 225 | #define _SSR_MAP_CAPACITY SSR_MAX_SCRIPTS * 4 226 | #define _SSR_MAP_HASH_MASK (_ssr_hash_t) 1 << 63 227 | typedef struct __ssr_map_t { 228 | uint8_t* buf; 229 | size_t size; 230 | size_t mask; 231 | size_t key_off; 232 | } _ssr_map_t; 233 | typedef uint64_t _ssr_hash_t; 234 | typedef void (*_ssr_map_iter_cb_t)(void* el, void* args); 235 | 236 | static void _ssr_map(_ssr_map_t* map, size_t entry_size, size_t key_off); 237 | static void _ssr_map_destroy(_ssr_map_t* map); 238 | static void* _ssr_map_find(_ssr_map_t* map, _ssr_hash_t hash); 239 | static void* _ssr_map_find_str(_ssr_map_t* map, const char* key); 240 | static void _ssr_map_add(_ssr_map_t* map, const void* _obj, _ssr_hash_t hash); 241 | static void _ssr_map_add_str(_ssr_map_t* map, const void* _obj); 242 | static void _ssr_map_iter(_ssr_map_t* map, _ssr_map_iter_cb_t cb, void* args); 243 | 244 | 245 | /*----------------------------------------------------------------------------- 246 | Internal System API 247 | _ssr_lock 248 | _srr_thread 249 | _ssr_lib 250 | string & file helpers 251 | */ 252 | struct _ssr_lock_t; 253 | struct _ssr_thread_t; 254 | struct _ssr_lib_t; 255 | typedef uint64_t _ssr_timestamp_t; 256 | typedef void (*_ssr_iter_dir_cb_t)(void* args, const char*, const char*); 257 | 258 | static void _ssr_lock(struct _ssr_lock_t* lock); 259 | static void _ssr_lock_destroy(struct _ssr_lock_t* lock); 260 | static void _ssr_lock_acq(struct _ssr_lock_t* lock); 261 | static void _ssr_lock_rel(struct _ssr_lock_t* lock); 262 | 263 | static bool _ssr_thread(struct _ssr_thread_t* thread, void* fun, void* args); 264 | static void _ssr_thread_join(struct _ssr_thread_t* thread); 265 | 266 | static const char* _ssr_lib_ext(void); 267 | static bool _ssr_lib(struct _ssr_lib_t* lib, const char* path); 268 | static void _ssr_lib_destroy(struct _ssr_lib_t* lib); 269 | static void* _ssr_lib_func_addr(struct _ssr_lib_t* lib, const char* fname); 270 | 271 | static _ssr_timestamp_t _ssr_file_timestamp(const char* path); 272 | static bool _ssr_file_exists(const char* path); 273 | static _ssr_str_t _ssr_fullpath(const char* rel); 274 | static _ssr_str_t _ssr_remove_ext(const char* str, long long len); 275 | static const char* _ssr_extract_rel(const char* base, const char* path); 276 | static const char* _ssr_extract_ext(const char* path); 277 | static _ssr_str_t _ssr_replace_seps(const char* str, char new_sep); 278 | static void _ssr_iter_dir(const char* root, _ssr_iter_dir_cb_t cv, void* args); 279 | static void _ssr_new_dir(const char* dir); 280 | static void _ssr_sleep(unsigned int ms); 281 | static int _ssr_run(char* cmd, _ssr_str_t* out, _ssr_str_t* err); 282 | 283 | /*----------------------------------------------------------------------------- 284 | Implementation 285 | */ 286 | static char* _ssr_alloc(size_t len) { return (char*) malloc(sizeof(char) * len); } 287 | 288 | static void _ssr_free(char* str) { free(str); } 289 | 290 | static _ssr_str_t _ssr_str_e(void) { 291 | _ssr_str_t ret; 292 | ret.b = NULL; 293 | return ret; 294 | } 295 | 296 | static _ssr_str_t _ssr_str(const char* src) { 297 | size_t len = strlen(src); 298 | _ssr_str_t ret; 299 | ret.b = _ssr_alloc(len + 1); 300 | _ssr_strncpy(ret.b, src, len + 1); 301 | return ret; 302 | } 303 | 304 | static _ssr_str_t _ssr_str_f(const char* fmt, ...) { 305 | // The code runs without va_copy on gcc and msvc 306 | va_list args, args_cp; 307 | va_start(args, fmt); 308 | va_copy(args_cp, args); 309 | size_t len = vsnprintf(NULL, 0, fmt, args); 310 | _ssr_str_t ret; 311 | ret.b = _ssr_alloc(len + 1); 312 | vsnprintf(ret.b, len + 1, fmt, args_cp); 313 | va_end(args); 314 | return ret; 315 | } 316 | 317 | static _ssr_str_t _ssr_str_rnd(size_t len) { 318 | if (len == 0) return _ssr_str_e(); 319 | _ssr_str_t ret; 320 | ret.b = _ssr_alloc(len + 1); 321 | size_t i; 322 | for (i = 0; i < len; ++i) 323 | ret.b[i] = (char) (97 + (rand() % 25)); 324 | ret.b[i] = '\0'; 325 | return ret; 326 | } 327 | 328 | static void _ssr_str_destroy(_ssr_str_t str) { _ssr_free(str.b); } 329 | 330 | static char* _ssr_strncpy(char* a, const char* b, size_t n) { 331 | char* p = a; 332 | while (n > 0 && *b != '\0') { 333 | *p++ = *b++; 334 | --n; 335 | } 336 | 337 | while (n > 0) { 338 | *p++ = '\0'; 339 | --n; 340 | } 341 | return a; 342 | } 343 | 344 | static void _ssr_vec(_ssr_vec_t* vec, size_t size, size_t len) { 345 | if (vec == NULL) return; 346 | vec->beg = (uint8_t*) malloc(size * len); 347 | vec->cur = vec->beg; 348 | vec->end = vec->beg + size * len; 349 | vec->size = size; 350 | } 351 | 352 | static void _ssr_vec_destroy(_ssr_vec_t* vec) { 353 | if (vec != NULL) { 354 | free(vec->beg); 355 | vec->beg = NULL; 356 | } 357 | } 358 | 359 | static size_t _ssr_vec_len(_ssr_vec_t* vec) { 360 | if (vec == NULL || vec->beg == NULL) return 0; 361 | return (vec->cur - vec->beg) / vec->size; 362 | } 363 | 364 | static size_t _ssr_vec_capacity(const _ssr_vec_t* vec) { return (vec->end - vec->beg) / vec->size; } 365 | 366 | static void* _ssr_vec_at(_ssr_vec_t* vec, size_t idx) { return vec->beg + idx * vec->size; } 367 | 368 | static void _ssr_vec_push(_ssr_vec_t* vec, const void* el) { 369 | size_t capacity = _ssr_vec_capacity(vec); 370 | if (_ssr_vec_len(vec) >= capacity) { 371 | size_t new_capacity = capacity * 2 > 32 ? capacity * 2 : 32; 372 | vec->beg = (uint8_t*) realloc(vec->beg, vec->size * new_capacity); 373 | vec->cur = vec->beg; 374 | vec->end = vec->beg + new_capacity * vec->size; 375 | } 376 | 377 | memcpy(vec->cur, el, vec->size); 378 | vec->cur += vec->size; 379 | } 380 | 381 | // swaps with last 382 | static void _ssr_vec_remove(_ssr_vec_t* vec, const void* obj) { 383 | uint8_t* cur = vec->beg; 384 | while (cur < vec->cur) { 385 | if (memcmp(cur, obj, vec->size) == 0) { 386 | memcpy(cur, (vec->cur - vec->size), vec->size); 387 | vec->cur -= vec->size; 388 | return; 389 | } 390 | 391 | cur += vec->size; 392 | } 393 | } 394 | 395 | static void _ssr_map(_ssr_map_t* map, size_t entry_size, size_t key_off) { 396 | map->buf = (uint8_t*) malloc(entry_size * _SSR_MAP_CAPACITY); 397 | memset(map->buf, 0, entry_size * _SSR_MAP_CAPACITY); 398 | map->size = entry_size; 399 | map->mask = _SSR_MAP_CAPACITY - 1; 400 | map->key_off = key_off; 401 | } 402 | 403 | static void _ssr_map_destroy(_ssr_map_t* map) { 404 | if (map != NULL) { 405 | free(map->buf); 406 | map->buf = 0; 407 | } 408 | } 409 | 410 | static void* _ssr_map_find(_ssr_map_t* map, _ssr_hash_t hash) { 411 | size_t base = (size_t) hash & map->mask; 412 | for (size_t i = 0; i <= map->mask; ++i) { 413 | uint8_t* cur = map->buf + (((base + i) & map->mask) * map->size); 414 | uint8_t* cur_key = cur + map->key_off; 415 | _ssr_hash_t* cur_hash = (_ssr_hash_t*) cur_key; 416 | 417 | if ((uint32_t) *cur_hash == (uint32_t) hash) return cur; 418 | 419 | if ((*cur_hash & _SSR_MAP_HASH_MASK) == 0) return NULL; 420 | } 421 | 422 | return NULL; 423 | } 424 | 425 | static uint32_t _fnv_32_str(const char* str, uint32_t hval) { 426 | unsigned char* s = (unsigned char*) str; 427 | while (*s) { 428 | hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); 429 | hval ^= (uint32_t) *s++; 430 | } 431 | return hval; 432 | } 433 | 434 | static void* _ssr_map_find_str(_ssr_map_t* map, const char* key) { 435 | uint32_t hash = _fnv_32_str(key, 0); 436 | return _ssr_map_find(map, hash); 437 | } 438 | 439 | static void _ssr_map_add(_ssr_map_t* map, const void* _obj, _ssr_hash_t hash) { 440 | size_t base = (size_t) hash & map->mask; 441 | for (size_t i = 0; i <= map->mask; ++i) { 442 | uint8_t* cur = map->buf + (((base + i) & map->mask) * map->size); 443 | uint8_t* cur_key = cur + map->key_off; 444 | _ssr_hash_t* cur_hash = (_ssr_hash_t*) cur_key; 445 | 446 | if ((*cur_hash & _SSR_MAP_HASH_MASK) == 0) { 447 | memcpy(cur, _obj, map->size); 448 | *cur_hash = (uint64_t) hash | (uint64_t) 1 << 63; 449 | return; 450 | } 451 | } 452 | } 453 | 454 | static void _ssr_map_add_str(_ssr_map_t* map, const void* _obj) { 455 | _ssr_str_t* str = (_ssr_str_t*) ((const char*) _obj + map->key_off + sizeof(_ssr_hash_t)); 456 | uint32_t hash = _fnv_32_str(str->b, 0); 457 | _ssr_map_add(map, _obj, hash); 458 | } 459 | 460 | static void _ssr_map_iter(_ssr_map_t* map, _ssr_map_iter_cb_t cb, void* args) { 461 | for (size_t i = 0; i <= map->mask; ++i) { 462 | uint8_t* cur = map->buf + (i & map->mask) * map->size; 463 | uint8_t* cur_key = cur + map->key_off; 464 | _ssr_hash_t* cur_hash = (_ssr_hash_t*) cur_key; 465 | 466 | if ((*cur_hash & _SSR_MAP_HASH_MASK) != 0x0) cb(cur, args); 467 | } 468 | } 469 | 470 | // Windows 471 | #ifdef SSR_WIN 472 | #define WIN32_LEAN_AND_MEAN 473 | #include 474 | #include 475 | #include 476 | #pragma comment(lib, "Shlwapi.lib") // TODO 477 | 478 | typedef struct _ssr_lock_t { 479 | // Initially I had just CRITICAL_SECTION, but it gets memcpyd around and the application 480 | // verifier triggered an "Uninitialized critical section error" 481 | CRITICAL_SECTION* h; 482 | } _ssr_lock_t; 483 | 484 | static void _ssr_lock(struct _ssr_lock_t* lock) { 485 | lock->h = (CRITICAL_SECTION*) malloc(sizeof(CRITICAL_SECTION)); 486 | InitializeCriticalSection(lock->h); 487 | } 488 | 489 | static void _ssr_lock_destroy(struct _ssr_lock_t* lock) { 490 | if (lock != NULL) { 491 | DeleteCriticalSection(lock->h); 492 | free(lock->h); 493 | } 494 | } 495 | 496 | static void _ssr_lock_acq(struct _ssr_lock_t* lock) { EnterCriticalSection(lock->h); } 497 | 498 | static void _ssr_lock_rel(struct _ssr_lock_t* lock) { LeaveCriticalSection(lock->h); } 499 | 500 | typedef struct _ssr_thread_t { 501 | HANDLE handle; 502 | unsigned int id; 503 | } _ssr_thread_t; 504 | 505 | static bool _ssr_thread(struct _ssr_thread_t* thread, void* fun, void* args) { 506 | thread->handle = 507 | (HANDLE) _beginthreadex(NULL, 0, (_beginthreadex_proc_type) fun, args, 0, &thread->id); 508 | return thread->handle != NULL; 509 | } 510 | 511 | static void _ssr_thread_join(struct _ssr_thread_t* thread) { 512 | if (thread == NULL || thread->handle == NULL) return; 513 | 514 | WaitForSingleObject(thread->handle, INFINITE); 515 | CloseHandle(thread->handle); 516 | } 517 | 518 | typedef struct _ssr_lib_t { 519 | HMODULE h; 520 | } _ssr_lib_t; 521 | 522 | static const char* _ssr_lib_ext(void) { return "dll"; } 523 | 524 | static bool _ssr_lib(struct _ssr_lib_t* lib, const char* path) { 525 | lib->h = LoadLibraryA(path); 526 | if (lib->h == NULL) { 527 | _ssr_log(SSR_CB_ERR, "Error loading shared library: %s", path); 528 | return false; 529 | } 530 | 531 | return true; 532 | } 533 | 534 | static void _ssr_lib_destroy(struct _ssr_lib_t* lib) { FreeLibrary(lib->h); } 535 | 536 | static void* _ssr_lib_func_addr(struct _ssr_lib_t* lib, const char* fname) { 537 | if (lib == NULL) return NULL; 538 | return (void*) GetProcAddress(lib->h, fname); 539 | } 540 | 541 | static _ssr_timestamp_t _ssr_file_timestamp(const char* path) { 542 | if (path == NULL) return (uint64_t) -1; 543 | HANDLE hfile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); 544 | if (hfile == NULL) return (uint64_t) -1; 545 | 546 | FILETIME wt; 547 | GetFileTime(hfile, NULL, NULL, &wt); 548 | 549 | CloseHandle(hfile); 550 | 551 | ULARGE_INTEGER wt_i; 552 | wt_i.u.LowPart = wt.dwLowDateTime; 553 | wt_i.u.HighPart = wt.dwHighDateTime; 554 | return (_ssr_timestamp_t) wt_i.QuadPart; 555 | } 556 | 557 | static bool _ssr_file_exists(const char* path) { return PathFileExistsA(path); } 558 | 559 | static _ssr_str_t _ssr_fullpath(const char* rel) { 560 | _ssr_str_t ret; 561 | ret.b = _fullpath(NULL, rel, 0); 562 | return ret; 563 | } 564 | 565 | 566 | static void _ssr_iter_dir(const char* root, _ssr_iter_dir_cb_t cb, void* args) { 567 | char path[2048]; 568 | sprintf(path, "%s\\*.*", root); 569 | 570 | WIN32_FIND_DATAA fd; 571 | HANDLE hfind = FindFirstFileA(path, &fd); 572 | if (hfind != INVALID_HANDLE_VALUE) { 573 | do { 574 | if (strcmp(fd.cFileName, ".") == 0 || strcmp(fd.cFileName, "..") == 0 || 575 | strcmp(fd.cFileName, ".bin") == 0) 576 | continue; 577 | 578 | sprintf(path, "%s\\%s", root, fd.cFileName); 579 | 580 | if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { 581 | _ssr_iter_dir(path, cb, args); 582 | continue; 583 | } 584 | 585 | FILE* fp = fopen(path, "r"); 586 | if (fp != NULL) { 587 | cb(args, root, fd.cFileName); 588 | fclose(fp); 589 | } 590 | } while (FindNextFileA(hfind, &fd)); 591 | FindClose(hfind); 592 | } 593 | } 594 | 595 | static void _ssr_new_dir(const char* dir) { 596 | if (dir == NULL || dir[0] == '\0') { 597 | _ssr_log(SSR_CB_ERR, "Failed to create new directory"); 598 | return; 599 | } 600 | 601 | _ssr_str_t rm = _ssr_str_f("cmd.exe /C \"rd /s /q \"%s\"\"", dir); 602 | _ssr_run(rm.b, NULL, NULL); 603 | CreateDirectoryA(dir, NULL); 604 | _ssr_str_destroy(rm); 605 | } 606 | 607 | static void _ssr_sleep(unsigned int ms) { Sleep(ms); } 608 | 609 | static int _ssr_run(char* cmd, _ssr_str_t* out, _ssr_str_t* err) { 610 | HANDLE stdout_r, stdout_w; 611 | HANDLE stderr_r, stderr_w; 612 | SECURITY_ATTRIBUTES sa; 613 | sa.nLength = sizeof(SECURITY_ATTRIBUTES); 614 | sa.bInheritHandle = true; 615 | sa.lpSecurityDescriptor = NULL; 616 | 617 | bool ret = CreatePipe(&stdout_r, &stdout_w, &sa, 0); 618 | ret = SetHandleInformation(stdout_r, HANDLE_FLAG_INHERIT, 0); 619 | ret = CreatePipe(&stderr_r, &stderr_w, &sa, 0); 620 | ret = SetHandleInformation(stderr_r, HANDLE_FLAG_INHERIT, 0); 621 | 622 | STARTUPINFOA si; 623 | ZeroMemory(&si, sizeof(STARTUPINFOA)); 624 | si.cb = sizeof(STARTUPINFOA); 625 | si.hStdOutput = stdout_w; 626 | si.hStdError = stderr_w; 627 | si.dwFlags = STARTF_USESTDHANDLES; 628 | ; 629 | 630 | PROCESS_INFORMATION pi; 631 | if (!CreateProcessA(NULL, cmd, NULL, NULL, true, 0, NULL, NULL, &si, &pi)) return -1; 632 | 633 | WaitForSingleObject(pi.hProcess, INFINITE); 634 | 635 | DWORD out_size; 636 | PeekNamedPipe(stdout_r, NULL, 0, NULL, &out_size, NULL); 637 | DWORD err_size; 638 | PeekNamedPipe(stderr_r, NULL, 0, NULL, &err_size, NULL); 639 | 640 | if (out != NULL) { 641 | if (out_size == 0) { 642 | *out = _ssr_str_e(); 643 | } else { 644 | out->b = _ssr_alloc(out_size); 645 | if (out_size) ReadFile(stdout_r, out->b, out_size, &out_size, NULL); 646 | out->b[out_size - 2] = '\0'; // overwrite trailing \r\n 647 | } 648 | } 649 | 650 | if (err != NULL) { 651 | if (err_size == 0) { 652 | *err = _ssr_str_e(); 653 | } else { 654 | err->b = _ssr_alloc(err_size); 655 | if (err_size) ReadFile(stderr_r, err->b, err_size, &err_size, NULL); 656 | err->b[err_size - 2] = '\0'; // overwrite trailing \r\n 657 | } 658 | } 659 | 660 | CloseHandle(stdout_w); 661 | CloseHandle(stderr_w); 662 | CloseHandle(stdout_r); 663 | CloseHandle(stderr_r); 664 | 665 | DWORD ret_val; 666 | GetExitCodeProcess(pi.hProcess, &ret_val); 667 | CloseHandle(pi.hThread); 668 | CloseHandle(pi.hProcess); 669 | return (int) ret_val; 670 | } 671 | 672 | #elif defined(SSR_LINUX) 673 | #include 674 | #include 675 | #include 676 | #include 677 | #include 678 | #include 679 | #include 680 | 681 | typedef struct _ssr_lock_t { 682 | pthread_mutex_t h; 683 | } _ssr_lock_t; 684 | 685 | static void _ssr_lock(struct _ssr_lock_t* lock) { 686 | if (lock == NULL) return; 687 | pthread_mutexattr_t attr; 688 | pthread_mutexattr_init(&attr); 689 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 690 | pthread_mutex_init(&lock->h, &attr); 691 | } 692 | 693 | static void _ssr_lock_destroy(struct _ssr_lock_t* lock) { 694 | if (lock == NULL) return; 695 | pthread_mutex_destroy(&lock->h); 696 | } 697 | 698 | static void _ssr_lock_acq(struct _ssr_lock_t* lock) { 699 | if (lock == NULL) return; 700 | pthread_mutex_lock(&lock->h); 701 | } 702 | 703 | static void _ssr_lock_rel(struct _ssr_lock_t* lock) { 704 | if (lock == NULL) return; 705 | pthread_mutex_unlock(&lock->h); 706 | } 707 | 708 | typedef struct _ssr_thread_t { 709 | pthread_t handle; 710 | } _ssr_thread_t; 711 | 712 | static bool _ssr_thread(struct _ssr_thread_t* thread, void* fun, void* args) { 713 | typedef void* fun_t(void*); 714 | if (thread == NULL || fun == NULL) return false; 715 | if (pthread_create(&thread->handle, NULL, (fun_t*) fun, (void*) args)) return true; 716 | return false; 717 | } 718 | 719 | static void _ssr_thread_join(struct _ssr_thread_t* thread) { 720 | if (thread == NULL) return; 721 | pthread_join(thread->handle, NULL); 722 | } 723 | 724 | typedef struct _ssr_lib_t { 725 | void* h; 726 | } _ssr_lib_t; 727 | 728 | static const char* _ssr_lib_ext(void) { return "so"; } 729 | 730 | static bool _ssr_lib(struct _ssr_lib_t* lib, const char* path) { 731 | if (lib == NULL || path == NULL) return false; 732 | lib->h = dlopen(path, RTLD_NOW); 733 | if (lib->h == NULL) { 734 | const char* err = dlerror(); 735 | _ssr_log(SSR_CB_WARN, "%s", err); 736 | return false; 737 | } 738 | return true; 739 | } 740 | 741 | static void _ssr_lib_destroy(struct _ssr_lib_t* lib) { 742 | if (lib == NULL || lib->h == NULL) return; 743 | dlclose(lib->h); 744 | } 745 | 746 | static void* _ssr_lib_func_addr(struct _ssr_lib_t* lib, const char* fname) { 747 | if (lib == NULL) return NULL; 748 | return dlsym(lib->h, fname); 749 | } 750 | 751 | static _ssr_timestamp_t _ssr_file_timestamp(const char* path) { 752 | struct stat s; 753 | if (stat(path, &s) == -1) return 0; 754 | 755 | return s.st_mtime; 756 | } 757 | 758 | static bool _ssr_file_exists(const char* path) { 759 | if (path == NULL) return false; 760 | return access(path, R_OK) != -1; 761 | } 762 | 763 | static _ssr_str_t _ssr_fullpath(const char* rel) { 764 | char fullpath[PATH_MAX + 1]; 765 | if (realpath(rel, fullpath) == NULL) return _ssr_str_e(); 766 | return _ssr_str(fullpath); 767 | } 768 | 769 | static void _ssr_iter_dir(const char* root, _ssr_iter_dir_cb_t cb, void* args) { 770 | DIR* dir; 771 | dir = opendir(root); 772 | 773 | struct dirent* de; 774 | while ((de = readdir(dir)) != NULL) { 775 | if (de->d_type == DT_DIR) { 776 | if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; 777 | char path[PATH_MAX + 1]; 778 | snprintf(path, PATH_MAX + 1, "%s/%s", root, de->d_name); 779 | _ssr_iter_dir(path, cb, args); 780 | } else 781 | cb(args, root, de->d_name); 782 | } 783 | closedir(dir); 784 | } 785 | 786 | static void _ssr_new_dir(const char* dir) { 787 | if (dir == NULL) return; 788 | if (mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) return; 789 | } 790 | 791 | static void _ssr_sleep(unsigned int ms) { usleep(ms * 1000); } 792 | 793 | static int _ssr_run(char* cmd, _ssr_str_t* out, _ssr_str_t* err) { 794 | char buf[SSR_COMPILER_BUF + 3]; 795 | memset(buf, 0, SSR_COMPILER_BUF + 3); 796 | 797 | FILE* pipe_out = popen(cmd, "r"); 798 | if (pipe_out == NULL) { 799 | // TODO: Error 800 | return 1; 801 | } 802 | 803 | while (fgets(buf, SSR_COMPILER_BUF, pipe_out) != NULL) 804 | ; 805 | int ret_code = pclose(pipe_out); 806 | 807 | if (WIFEXITED(ret_code)) 808 | ret_code = 0; 809 | else 810 | ret_code = WEXITSTATUS(ret_code); 811 | 812 | // Gcc output sometimes contains '\0\0' on newlines, as the buffer is guaranteed 813 | // to have '\0\0\0' at the end this is safe. Just replacing '\0\0' to make code printable 814 | char* cur = buf; 815 | while (*cur != '\0' || *(cur + 1) != '\0' || (*(cur + 2) != '\0')) { 816 | if (*cur == '\0') *cur = ' '; 817 | ++cur; 818 | } 819 | 820 | if (buf[0] != '\0') *err = _ssr_str(buf); 821 | return ret_code; 822 | } 823 | 824 | #else 825 | #error 826 | #endif 827 | 828 | static _ssr_str_t _ssr_remove_ext(const char* str, long long len) { 829 | if (len == -1) len = strlen(str); 830 | const char* cur = str + len - 1; 831 | while (cur > str && *cur != '.') 832 | --cur; 833 | --cur; 834 | _ssr_str_t ret; 835 | size_t buf_len = cur - str + 1; 836 | ret.b = (char*) malloc(buf_len + 1); 837 | memcpy(ret.b, str, buf_len); 838 | ret.b[buf_len] = '\0'; 839 | return ret; 840 | } 841 | 842 | // Returns a pointer in the same buffer that represents the relative path based on base. 843 | // base and path need to be full paths w/ driver letters in the same notation. 844 | // path separators are not required to be the same 845 | static const char* _ssr_extract_rel(const char* base, const char* path) { 846 | // let's be safe on the first comparisons 847 | if (base == NULL || path == NULL || *base == '\0' || *path == '\0' || *(base + 1) == '\0' || 848 | *(base + 2) == '\0') 849 | return NULL; 850 | 851 | const char* b = base; 852 | const char* p = path; 853 | 854 | if (*(b + 1) == ':') { 855 | if (*(p + 1) == ':') { 856 | if (*b != *p) return NULL; 857 | } else 858 | return NULL; 859 | 860 | b += 2; 861 | p += 2; 862 | } 863 | 864 | while (true) { 865 | const char* sb = b; 866 | const char* sp = p; 867 | 868 | // Stopping at first path separator 869 | while (*b != '\0' && *b != '/' && *b != '\\') 870 | ++b; 871 | while (*p != '\0' && *p != '/' && *p != '\\') 872 | ++p; 873 | 874 | // Comparing directory 875 | if (b - sb != p - sp) return p; 876 | if (memcmp(sb, sp, p - sp) != 0) return p; 877 | 878 | // Skipping separators 879 | while (*b != '\0' && *b == '/' || *b == '\\') 880 | ++b; 881 | while (*p != '\0' && *p == '/' || *p == '\\') 882 | ++p; 883 | 884 | if (*b == '\0') return p; 885 | if (*p == '\0') return NULL; // the other way around 886 | } 887 | return NULL; 888 | } 889 | 890 | static const char* _ssr_extract_ext(const char* path) { 891 | const char* cur = path + strlen(path); 892 | while (*(--cur) != '.' && cur >= path) 893 | ; 894 | return cur; 895 | } 896 | 897 | static _ssr_str_t _ssr_replace_seps(const char* str, char new_sep) { 898 | size_t rel_path_len = strlen(str); // TODO: Can we avoid? 899 | char* buffer = _ssr_alloc(rel_path_len + 1); 900 | const char* read = str; 901 | char* write = buffer; 902 | while (*read != '\0') { 903 | if (*read == '\\' || *read == '/') { 904 | if (write > buffer && *(buffer - 1) != new_sep) // Avoiding double separator 905 | { 906 | *(write++) = new_sep; 907 | ++read; 908 | continue; 909 | } 910 | } 911 | *(write++) = *(read++); 912 | } 913 | *write = '\0'; 914 | _ssr_str_t no_ext_ret = _ssr_remove_ext(buffer, -1); 915 | _ssr_free(buffer); 916 | return no_ext_ret; 917 | } 918 | 919 | // Needed for live/not live 920 | enum _SSR_COMPILE_STAGES { 921 | _SSR_COMPILE = 1, 922 | _SSR_LINK = 1 << 1, 923 | _SSR_COMPILE_N_LINK = _SSR_COMPILE | _SSR_LINK 924 | }; 925 | 926 | static void 927 | _ssr_merge_in(char* buf, size_t buf_len, const char* flag, char** args, size_t num_args) { 928 | buf[0] = '\0'; 929 | // TODO: Could be done in a bette way ? 930 | size_t len = 0; 931 | for (size_t i = 0; i < num_args; ++i) { 932 | size_t new_len = snprintf(NULL, 0, "%s%s%s", buf, flag, args[i]); 933 | if (new_len > buf_len) break; // TODO: warn user that skipping define 934 | len += snprintf(buf, buf_len, "%s%s%s", buf, flag, args[i]); 935 | } 936 | } 937 | 938 | // In the todolist there is eventually a string pool 939 | #define _SSR_ARGS_BUF_LEN 1 << 13 940 | // runs `vcvars && cl` and `vcvars && link` 941 | //@todo: in the future you can call vcvars, save env and feed env to `cl` and `link`, maybe there is 942 | // even better way? default compiler arguments: absolute paths assumed satges is 943 | // SSR_COMPILE_STAGES 944 | // if stages == SSR_LINK then inputs should be the object files 945 | // input is single string with filesnames separated by spaces 946 | static bool 947 | _ssr_compile_msvc(const char* input, ssr_config_t* config, const char* _out, int stages) { 948 | bool ret = false; 949 | 950 | // Generates vcvars string as is both used in cl & link 951 | char* vcvars = NULL; 952 | { 953 | // Atleast from 15->17 installations paths are different 954 | 955 | const char* bases[] = { 956 | config->msvc141_path, "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC"}; 957 | 958 | static const char* archs[] = {"x86", "x86_amd64", "x86_arm"}; 959 | 960 | const char* base = bases[config->msvc_ver]; 961 | const char* arch = archs[config->target_arch]; 962 | 963 | char* fmt = "\"%svcvarsall.bat\" %s"; 964 | size_t vcvars_len = snprintf(NULL, 0, fmt, base, arch); 965 | vcvars = (char*) malloc(vcvars_len + 1); 966 | snprintf(vcvars, vcvars_len + 1, fmt, base, arch); 967 | } 968 | 969 | // compile step 970 | _ssr_str_t compile_out = _ssr_str_e(); 971 | 972 | if (stages & _SSR_COMPILE) { 973 | // defines 974 | char defines[_SSR_ARGS_BUF_LEN]; 975 | _ssr_merge_in(defines, _SSR_ARGS_BUF_LEN, "/D", config->defines, config->num_defines); 976 | 977 | // include directories 978 | char include_directories[_SSR_ARGS_BUF_LEN]; 979 | _ssr_merge_in(include_directories, 980 | _SSR_ARGS_BUF_LEN, 981 | "/I", 982 | config->include_directories, 983 | config->num_include_directories); 984 | 985 | // default flags 986 | const char* default_args = "/c /EHsc /nologo"; 987 | 988 | // flags 989 | const char* gen_dbg = config->flags & SSR_FLAGS_GEN_DEBUG ? "/Zi" : ""; 990 | const char* mt_lib = ""; // config->flags & SSR_FLAGS_GEN_DEBUG ? "/Mtd" : "/Mt"; 991 | 992 | // custom args 993 | const char* beg_args = config->compile_args_beg == NULL ? "" : config->compile_args_beg; 994 | const char* end_args = config->compile_args_end == NULL ? "" : config->compile_args_end; 995 | 996 | // output just appending .obj at end of full path 997 | if (stages & _SSR_LINK) compile_out = _ssr_str_f("%s.obj", _out); 998 | 999 | // vcvars - base - beg_args - default_flags - gen_dbg - mt_lib - compile_out - end_args - 1000 | // input files 1001 | char* fmt = "%s > nul && cl.exe %s %s %s %s %s /Fo\"%s\" %s \"%s\""; 1002 | 1003 | _ssr_str_t compile = _ssr_str_f(fmt, 1004 | vcvars, 1005 | beg_args, 1006 | default_args, 1007 | gen_dbg, 1008 | mt_lib, 1009 | defines, 1010 | stages & _SSR_LINK ? compile_out.b : _out, 1011 | end_args, 1012 | input); 1013 | _ssr_log(SSR_CB_INFO, "Compiling %s ...", input); 1014 | 1015 | _ssr_str_t err; 1016 | int ret = _ssr_run(compile.b, NULL, &err) == 0; 1017 | _ssr_str_destroy(compile); 1018 | if (err.b != NULL) { 1019 | if (ret) 1020 | _ssr_log(SSR_CB_WARN, err.b); 1021 | else 1022 | _ssr_log(SSR_CB_ERR, err.b); 1023 | } 1024 | _ssr_str_destroy(err); 1025 | 1026 | if (!ret) goto end; 1027 | } 1028 | 1029 | if (stages & _SSR_LINK) { 1030 | // link libraries 1031 | char link_directories[_SSR_ARGS_BUF_LEN]; 1032 | _ssr_merge_in(link_directories, 1033 | _SSR_ARGS_BUF_LEN, 1034 | "", 1035 | config->link_libraries, 1036 | config->num_link_libraries); 1037 | 1038 | // default flags 1039 | const char* default_flags = "/DLL /NOLOGO"; 1040 | 1041 | // flags 1042 | const char* gen_dbg = config->flags & SSR_FLAGS_GEN_DEBUG ? "/DEBUG" : ""; 1043 | 1044 | // custom args 1045 | const char* beg_args = config->link_args_beg == NULL ? "" : config->link_args_beg; 1046 | const char* end_args = config->link_args_end == NULL ? "" : config->link_args_end; 1047 | 1048 | // target 1049 | const char* target = 1050 | #if defined SSR_64 1051 | "/MACHINE:AMD64" 1052 | #else 1053 | "/MACHINE:X86" 1054 | #endif 1055 | ; 1056 | 1057 | // vcvars - base - beg_args - default_flags - gen_dbg - target - _out - end_args - input 1058 | char* fmt = "%s > nul && link.exe %s %s %s %s /OUT:\"%s\" %s \"%s\" %s"; 1059 | 1060 | _ssr_str_t link = _ssr_str_f(fmt, 1061 | vcvars, 1062 | beg_args, 1063 | default_flags, 1064 | gen_dbg, 1065 | target, 1066 | _out, 1067 | end_args, 1068 | stages & _SSR_COMPILE ? compile_out.b : input, 1069 | link_directories); 1070 | _ssr_log(SSR_CB_INFO, "Linking %s ...", input); 1071 | 1072 | _ssr_str_t err; 1073 | int ret = _ssr_run(link.b, NULL, &err) == 0; 1074 | _ssr_str_destroy(link); 1075 | if (err.b != NULL) { 1076 | if (ret) 1077 | _ssr_log(SSR_CB_WARN, err.b); 1078 | else 1079 | _ssr_log(SSR_CB_ERR, err.b); 1080 | } 1081 | 1082 | if (!ret) goto end; 1083 | } 1084 | 1085 | ret = true; 1086 | end: 1087 | free(vcvars); 1088 | _ssr_str_destroy(compile_out); 1089 | return ret; 1090 | } 1091 | 1092 | #if !defined SSR_CLANG_EXEC 1093 | #define SSR_CLANG_EXEC "clang" 1094 | #endif 1095 | static bool 1096 | _ssr_compile_clang(const char* input, ssr_config_t* config, const char* _out, int stages) { 1097 | bool ret = false; 1098 | _ssr_str_t compile_out = _ssr_str_e(); 1099 | if (stages & _SSR_COMPILE) { 1100 | compile_out = _ssr_str_f("%s.obj", _out); 1101 | 1102 | // defines 1103 | char defines[_SSR_ARGS_BUF_LEN]; 1104 | _ssr_merge_in(defines, _SSR_ARGS_BUF_LEN, "-D", config->defines, config->num_defines); 1105 | 1106 | // include directories 1107 | char include_directories[_SSR_ARGS_BUF_LEN]; 1108 | _ssr_merge_in(include_directories, 1109 | _SSR_ARGS_BUF_LEN, 1110 | "-I", 1111 | config->include_directories, 1112 | config->num_include_directories); 1113 | 1114 | const char* gen_dbg = config->flags & SSR_FLAGS_GEN_DEBUG ? "-g " : ""; 1115 | const char* opt_lvl = config->flags & SSR_FLAGS_GEN_OPT2 ? 1116 | "-O2" : 1117 | (config->flags & SSR_FLAGS_GEN_OPT1 ? "-O1" : ""); // Are they actually the same as gcc? 1118 | 1119 | #if defined(SSR_LINUX) 1120 | char* fmt = "%s -c -fPIC -o %s %s %s %s -o%s %s -lm 2>&1"; 1121 | #else 1122 | char* fmt = "%s -c -fPIC -o %s %s %s %s -o%s %s"; 1123 | #endif 1124 | 1125 | _ssr_str_t compile = _ssr_str_f(fmt, 1126 | SSR_CLANG_EXEC, 1127 | gen_dbg, 1128 | opt_lvl, 1129 | defines, 1130 | include_directories, 1131 | stages & _SSR_LINK ? compile_out.b : _out, 1132 | input); 1133 | _ssr_log(SSR_CB_INFO, "Compiling %s ...", input); 1134 | 1135 | _ssr_str_t err; 1136 | int ret = _ssr_run(compile.b, NULL, &err) == 0; 1137 | _ssr_str_destroy(compile); 1138 | if (err.b != NULL) { 1139 | if (ret) 1140 | _ssr_log(SSR_CB_WARN, err.b); 1141 | else 1142 | _ssr_log(SSR_CB_ERR, err.b); 1143 | } 1144 | _ssr_str_destroy(err); 1145 | 1146 | if (!ret) goto end; 1147 | } 1148 | 1149 | if (stages & _SSR_LINK) { 1150 | // link libraries 1151 | char link_directories[_SSR_ARGS_BUF_LEN]; 1152 | _ssr_merge_in(link_directories, 1153 | _SSR_ARGS_BUF_LEN, 1154 | "/DEFAULTLIB", 1155 | config->link_libraries, 1156 | config->num_link_libraries); 1157 | 1158 | #if defined(SSR_LINUX) 1159 | char* fmt = "%s -shared -lm -o %s %s -lm 2>&1"; 1160 | #else 1161 | char* fmt = "%s -shared -lm -o %s %s"; 1162 | #endif 1163 | 1164 | _ssr_str_t link = 1165 | _ssr_str_f(fmt, SSR_CLANG_EXEC, _out, stages & _SSR_COMPILE ? compile_out.b : input); 1166 | _ssr_log(SSR_CB_INFO, "Linking %s ...", input); 1167 | 1168 | _ssr_str_t err; 1169 | int ret = _ssr_run(link.b, NULL, &err) == 0; 1170 | _ssr_str_destroy(link); 1171 | if (err.b != NULL) { 1172 | if (ret) 1173 | _ssr_log(SSR_CB_WARN, err.b); 1174 | else 1175 | _ssr_log(SSR_CB_ERR, err.b); 1176 | } 1177 | _ssr_str_destroy(err); 1178 | 1179 | if (!ret) goto end; 1180 | } 1181 | 1182 | ret = true; 1183 | end: 1184 | _ssr_str_destroy(compile_out); 1185 | return ret; 1186 | } 1187 | 1188 | #if !defined SSR_GCC_EXEC 1189 | #define SSR_GCC_EXEC "gcc" 1190 | #endif 1191 | 1192 | static bool 1193 | _ssr_compile_gcc(const char* input, ssr_config_t* config, const char* _out, int stages) { 1194 | bool ret = false; 1195 | _ssr_str_t compile_out = _ssr_str_e(); 1196 | if (stages & _SSR_COMPILE) { 1197 | compile_out = _ssr_str_f("%s.obj", _out); 1198 | 1199 | // defines 1200 | char defines[_SSR_ARGS_BUF_LEN]; 1201 | _ssr_merge_in(defines, _SSR_ARGS_BUF_LEN, "-D", config->defines, config->num_defines); 1202 | 1203 | // include directories 1204 | char include_directories[_SSR_ARGS_BUF_LEN]; 1205 | _ssr_merge_in(include_directories, 1206 | _SSR_ARGS_BUF_LEN, 1207 | "-I", 1208 | config->include_directories, 1209 | config->num_include_directories); 1210 | 1211 | const char* gen_dbg = config->flags & SSR_FLAGS_GEN_DEBUG ? "-g " : ""; 1212 | const char* opt_lvl = config->flags & SSR_FLAGS_GEN_OPT2 ? 1213 | "-O2" : 1214 | (config->flags & SSR_FLAGS_GEN_OPT1 ? "-O1" : ""); 1215 | 1216 | #if defined(SSR_LINUX) 1217 | char* fmt = "%s -c -o %s %s %s %s -o %s %s -lm 2>&1"; 1218 | #else 1219 | char* fmt = "%s -c -o %s %s %s %s -o %s %s"; 1220 | #endif 1221 | 1222 | _ssr_str_t compile = _ssr_str_f(fmt, 1223 | SSR_GCC_EXEC, 1224 | gen_dbg, 1225 | opt_lvl, 1226 | defines, 1227 | include_directories, 1228 | stages & _SSR_LINK ? compile_out.b : _out, 1229 | input); 1230 | _ssr_log(SSR_CB_INFO, "Compiling %s ...", input); 1231 | 1232 | _ssr_str_t err; 1233 | int ret = _ssr_run(compile.b, NULL, &err) == 0; 1234 | _ssr_str_destroy(compile); 1235 | if (err.b != NULL) { 1236 | if (ret) 1237 | _ssr_log(SSR_CB_WARN, err.b); 1238 | else 1239 | _ssr_log(SSR_CB_ERR, err.b); 1240 | } 1241 | _ssr_str_destroy(err); 1242 | 1243 | if (!ret) goto end; 1244 | } 1245 | 1246 | if (stages & _SSR_LINK) { 1247 | // link libraries 1248 | char link_directories[_SSR_ARGS_BUF_LEN]; 1249 | _ssr_merge_in(link_directories, 1250 | _SSR_ARGS_BUF_LEN, 1251 | "/DEFAULTLIB", 1252 | config->link_libraries, 1253 | config->num_link_libraries); 1254 | 1255 | #if defined(SSR_LINUX) 1256 | char* fmt = "%s -shared -o %s %s -lm 2>&1"; 1257 | #else 1258 | char* fmt = "%s -shared -o %s %s"; 1259 | #endif 1260 | 1261 | _ssr_str_t link = 1262 | _ssr_str_f(fmt, SSR_GCC_EXEC, _out, stages & _SSR_COMPILE ? compile_out.b : input); 1263 | _ssr_log(SSR_CB_INFO, "Linking %s ...", input); 1264 | 1265 | _ssr_str_t err; 1266 | int ret = _ssr_run(link.b, NULL, &err) == 0; 1267 | _ssr_str_destroy(link); 1268 | if (err.b != NULL) { 1269 | if (ret) 1270 | _ssr_log(SSR_CB_WARN, err.b); 1271 | else 1272 | _ssr_log(SSR_CB_ERR, err.b); 1273 | } 1274 | _ssr_str_destroy(err); 1275 | 1276 | if (!ret) goto end; 1277 | } 1278 | 1279 | ret = true; 1280 | end: 1281 | _ssr_str_destroy(compile_out); 1282 | return ret; 1283 | } 1284 | 1285 | static bool _ssr_compile(const char* path, ssr_config_t* config, const char* out, int stages) { 1286 | switch (config->compiler) { 1287 | case SSR_COMPILER_MSVC: 1288 | return _ssr_compile_msvc(path, config, out, stages); 1289 | case SSR_COMPILER_CLANG: 1290 | return _ssr_compile_clang(path, config, out, stages); 1291 | case SSR_COMPILER_GCC: 1292 | return _ssr_compile_gcc(path, config, out, stages); 1293 | } 1294 | return true; 1295 | } 1296 | 1297 | // Internal 1298 | typedef struct __ssr_routine_t { 1299 | _ssr_str_t name; // name of the function 1300 | void* addr; // current pointer to function address 1301 | _ssr_vec_t moos; // listeners 1302 | _ssr_lock_t moos_lock; 1303 | } _ssr_routine_t; 1304 | 1305 | // Element in dameon script map, indexed by script id (relative path with SSR_SEP) 1306 | typedef struct __ssr_script_t { 1307 | _ssr_hash_t hash; 1308 | _ssr_str_t id; 1309 | _ssr_vec_t routines; // list of routines, could use a map, but not worth atm 1310 | clock_t last_seen; // removal after X time, data stays in memory for a bit (Ctrl+Z friendly) 1311 | _ssr_timestamp_t last_written; // for updating 1312 | _ssr_str_t rnd_id; // current id of the dll (also part of filename 1313 | _ssr_lib_t lib; // current lib lodaded in memory 1314 | } _ssr_script_t; 1315 | 1316 | typedef struct ssr_t { 1317 | ssr_config_t* config; // global config 1318 | char* root; // base directory (your scripts/ directory) 1319 | _ssr_thread_t thread; // daemon thread 1320 | volatile uint32_t 1321 | state; // for joining & keep track of wheter we generated a config or the user supplied one 1322 | 1323 | ssr_cb_t cb; 1324 | int cb_mask; 1325 | 1326 | #ifdef SSR_LIVE 1327 | _ssr_map_t scripts; // id -> _sso_script_t 1328 | const char* bin; 1329 | #else 1330 | _ssr_lib_t lib; // single library when running 'release' 1331 | #endif 1332 | } ssr_t; 1333 | 1334 | SSR_DEF bool ssr_init(struct ssr_t* ssr, const char* root, struct ssr_config_t* config) { 1335 | memset(ssr, 0, sizeof(ssr_t)); 1336 | ssr->root = _ssr_fullpath(root).b; 1337 | ssr->state = 0x0; 1338 | 1339 | #ifdef SSR_LIVE 1340 | _ssr_map(&ssr->scripts, sizeof(_ssr_script_t), 0); 1341 | #endif 1342 | 1343 | if (config == NULL) { 1344 | ssr->config = (ssr_config_t*) malloc(sizeof(ssr_config_t)); 1345 | #if defined(__clang__) 1346 | ssr->config->compiler = SSR_COMPILER_CLANG; 1347 | #elif defined(__GNUC__) || defined(__GNUG__) 1348 | ssr->config->compiler = SSR_COMPILER_GCC; 1349 | #elif defined(_MSC_VER) 1350 | ssr->config->compiler = SSR_COMPILER_MSVC; 1351 | #if _MSC_VER >= 1910 1352 | ssr->config->msvc_ver = SSR_MSVC_VER_14_1; 1353 | ssr->config->msvc141_path = NULL; 1354 | #elif _MSC_VER >= 1900 1355 | ssr->config->msvc_ver = SSR_MSVC_VER_14_0; 1356 | #else 1357 | #error Previous MSVC versions have not been tested, but should probably work with the right paths 1358 | #endif 1359 | #else 1360 | #error Unsupported compiler 1361 | #endif 1362 | 1363 | #if defined(SSR_32) 1364 | ssr->config->target_arch = SSR_ARCH_X86; 1365 | #elif defined(SSR_64) 1366 | ssr->config->target_arch = SSR_ARCH_X64; 1367 | #else 1368 | #error Unrecognized platform 1369 | #endif 1370 | ssr->config->flags = SSR_FLAGS_GEN_DEBUG; 1371 | ssr->config->include_directories = NULL; 1372 | ssr->config->num_include_directories = 0; 1373 | ssr->config->link_libraries = NULL; 1374 | ssr->config->num_link_libraries = 0; 1375 | ssr->config->defines = NULL; 1376 | ssr->config->num_defines = 0; 1377 | ssr->config->compile_args_beg = NULL; 1378 | ssr->config->compile_args_end = NULL; 1379 | ssr->config->link_args_beg = NULL; 1380 | ssr->config->link_args_end = NULL; 1381 | ssr->state |= 0x2; 1382 | } else { 1383 | *ssr->config = *config; 1384 | } 1385 | 1386 | if (ssr->config->compiler == SSR_COMPILER_MSVC && ssr->config->msvc_ver == SSR_MSVC_VER_14_1) { 1387 | const char* vswhere_cmd = 1388 | "C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe -property installationPath"; 1389 | _ssr_str_t output; 1390 | _ssr_run(vswhere_cmd, &output, NULL); 1391 | ssr->config->msvc141_path = _ssr_str_f("%s/VC/Auxiliary/Build/", output).b; 1392 | } 1393 | 1394 | /* Syntax: 1395 | :[L] 1396 | : 1397 | .. 1398 | EOF 1399 | can be either a single file (relative from root) or a directory, no support for 1400 | globbing here e.g. math/incr math/incr/ L specifies if the commands are valid only when in 1401 | live mode : represents a compile option in cmake style (w/o the target_): 1402 | include_directories [,] 1403 | link_directories [,] 1404 | link_libraries [,] 1405 | add_definitions = [,=]* 1406 | [clang|msvc|gcc]: 1407 | */ 1408 | return true; 1409 | } 1410 | 1411 | static void _ssr_script_destroy(void* el, void* args) { 1412 | _ssr_script_t* script = (_ssr_script_t*) el; 1413 | 1414 | size_t routines_len = _ssr_vec_len(&script->routines); 1415 | for (size_t i = 0; i < routines_len; ++i) { 1416 | _ssr_routine_t* routine = (_ssr_routine_t*) _ssr_vec_at(&script->routines, i); 1417 | _ssr_str_destroy(routine->name); 1418 | _ssr_vec_destroy(&routine->moos); 1419 | _ssr_lock_destroy(&routine->moos_lock); 1420 | } 1421 | 1422 | _ssr_vec_destroy(&script->routines); 1423 | _ssr_str_destroy(script->id); 1424 | _ssr_str_destroy(script->rnd_id); 1425 | _ssr_lib_destroy(&script->lib); 1426 | } 1427 | 1428 | SSR_DEF void ssr_destroy(struct ssr_t* ssr) { 1429 | #ifdef SSR_LIVE 1430 | ssr->state |= 0x1; 1431 | _ssr_thread_join(&ssr->thread); 1432 | #endif 1433 | 1434 | free(ssr->root); 1435 | if (ssr->state & 0x2) free(ssr->config); 1436 | 1437 | #ifdef SSR_LIVE 1438 | _ssr_map_iter(&ssr->scripts, _ssr_script_destroy, NULL); 1439 | _ssr_map_destroy(&ssr->scripts); 1440 | #else 1441 | _ssr_lib_destroy(&ssr->lib); 1442 | #endif 1443 | } 1444 | 1445 | #ifdef SSR_LIVE 1446 | static void _ssr_on_file(void* args, const char* base, const char* filename) { 1447 | const char* exts[] = {SSR_FILE_EXTS}; 1448 | ssr_t* ssr = (ssr_t*) args; 1449 | 1450 | // Extracting full path 1451 | _ssr_str_t full_path = _ssr_str_f("%s/%s", base, filename); 1452 | const char* ext = _ssr_extract_ext(filename); 1453 | 1454 | // Checking if file is of a valid extension 1455 | size_t exti = 0; 1456 | while (exts[exti][0] != '\0') { 1457 | if (strcmp(exts[exti], ext) == 0) break; 1458 | ++exti; 1459 | } 1460 | 1461 | // In case skipping file 1462 | if (exts[exti][0] == '\0') { 1463 | _ssr_str_destroy(full_path); 1464 | return; 1465 | } 1466 | 1467 | // Computing script id 1468 | // |root|rel_path|filename 1469 | // | base |filename 1470 | // ptr is in base 1471 | const char* rel_path = _ssr_extract_rel(ssr->root, full_path.b); 1472 | _ssr_str_t id = _ssr_replace_seps(rel_path, SSR_SEP); 1473 | 1474 | _ssr_script_t* script = (_ssr_script_t*) _ssr_map_find_str(&ssr->scripts, id.b); 1475 | 1476 | // new script, ssr has no permission to add scripts or routines, if script == NULL means 1477 | // that no one is registered to listen to this file, no need to go further 1478 | if (script == NULL || _ssr_vec_len(&script->routines) == 0) goto end; 1479 | 1480 | _ssr_timestamp_t ts = _ssr_file_timestamp(full_path.b); 1481 | if (script->last_written < ts) { 1482 | // Generating name for the share library 1483 | _ssr_str_t _shared_lib_out = _ssr_str_rnd(SSR_SL_LEN); 1484 | while (_ssr_file_exists(_shared_lib_out.b)) { 1485 | _ssr_str_destroy(_shared_lib_out); 1486 | _shared_lib_out = _ssr_str_rnd(SSR_SL_LEN); 1487 | } 1488 | 1489 | // Combining into full path 1490 | _ssr_str_t shared_lib_out = 1491 | _ssr_str_f("%s/%s.%s", ssr->bin, _shared_lib_out.b, _ssr_lib_ext()); 1492 | 1493 | // Time to compile and link into dll 1494 | bool compile_ret = 1495 | _ssr_compile(full_path.b, ssr->config, shared_lib_out.b, _SSR_COMPILE_N_LINK); 1496 | if (!compile_ret) { 1497 | _ssr_log(SSR_CB_ERR, "something went wrong"); // todo 1498 | } 1499 | 1500 | // Loading library 1501 | _ssr_lib_t shared_lib; 1502 | if (!_ssr_lib(&shared_lib, shared_lib_out.b)) { 1503 | _ssr_str_destroy(_shared_lib_out); 1504 | _ssr_str_destroy(shared_lib_out); 1505 | goto end; 1506 | } 1507 | 1508 | // Updating map and all listeneres 1509 | _ssr_script_t* script = (_ssr_script_t*) _ssr_map_find_str(&ssr->scripts, id.b); 1510 | size_t routines_len = _ssr_vec_len(&script->routines); 1511 | for (size_t i = 0; i < routines_len; ++i) { 1512 | _ssr_routine_t* routine = (_ssr_routine_t*) _ssr_vec_at(&script->routines, i); 1513 | 1514 | // Finding new function pointer 1515 | void* new_fptr = _ssr_lib_func_addr(&shared_lib, routine->name.b); 1516 | 1517 | _ssr_lock_acq(&routine->moos_lock); 1518 | size_t moos_len = _ssr_vec_len(&routine->moos); 1519 | for (size_t j = 0; j < moos_len; ++j) { 1520 | ssr_routine_t* user_routine = *(ssr_routine_t**) _ssr_vec_at(&routine->moos, j); 1521 | // Updating function pointer 1522 | *user_routine = new_fptr; 1523 | } 1524 | _ssr_lock_rel(&routine->moos_lock); 1525 | } 1526 | 1527 | // Can safely free library 1528 | _ssr_lib_destroy(&script->lib); 1529 | script->lib = shared_lib; 1530 | 1531 | // rnd_id is saved for cleaning 1532 | _ssr_str_destroy(script->rnd_id); 1533 | script->rnd_id = _shared_lib_out; 1534 | 1535 | script->last_written = ts; 1536 | _ssr_str_destroy(shared_lib_out); 1537 | } 1538 | script->last_seen = clock(); 1539 | 1540 | // The reason this is needed is because the compilation is triggered by a client-side call to 1541 | // ssr_add() which inserts a ssr_script and an ssr_routine inside the map. _ssr_on_file compiles 1542 | // the script and updates the listener. Any subsequent call to ssr_add inserts a new routine in 1543 | // the map, but the client-side **cannot** operate on ssr->lib as there is no lock. 1544 | size_t routines_len = _ssr_vec_len(&script->routines); 1545 | for (size_t i = 0; i < routines_len; ++i) { 1546 | _ssr_routine_t* routine = (_ssr_routine_t*) _ssr_vec_at(&script->routines, i); 1547 | void* addr = routine->addr; 1548 | routine->addr = _ssr_lib_func_addr(&script->lib, routine->name.b); 1549 | if (addr != routine->addr) { 1550 | void* new_fptr = _ssr_lib_func_addr(&script->lib, routine->name.b); 1551 | 1552 | _ssr_lock_acq(&routine->moos_lock); 1553 | size_t moos_len = _ssr_vec_len(&routine->moos); 1554 | for (size_t j = 0; j < moos_len; ++j) { 1555 | ssr_routine_t* user_routine = *(ssr_routine_t**) _ssr_vec_at(&routine->moos, j); 1556 | // Updating function pointer 1557 | *user_routine = new_fptr; 1558 | } 1559 | _ssr_lock_rel(&routine->moos_lock); 1560 | } 1561 | } 1562 | 1563 | end: 1564 | _ssr_str_destroy(full_path); 1565 | _ssr_str_destroy(id); 1566 | } 1567 | 1568 | 1569 | //@main 1570 | #ifdef _WIN32 // && _MSC_VER 1571 | static DWORD WINAPI 1572 | #else 1573 | static void* 1574 | #endif 1575 | _ssr_main(void* args) { 1576 | ssr_t* ssr = (ssr_t*) args; 1577 | 1578 | _ssr_str_t bin_dir = _ssr_str_f("%s/%s", ssr->root, SSR_BIN_DIR); 1579 | ssr->bin = bin_dir.b; 1580 | 1581 | // Setting up logging for current instance 1582 | _ssr_log(0, NULL, ssr); 1583 | //_ssr_log(SSR_CB_INFO, "Watcher running on %s", ssr->root); 1584 | 1585 | // Removing current bin directory 1586 | _ssr_new_dir(bin_dir.b); 1587 | 1588 | while ((ssr->state & 0x1) == 0) { 1589 | _ssr_iter_dir(ssr->root, _ssr_on_file, ssr); 1590 | _ssr_sleep(SSR_SLEEP_MS); 1591 | } 1592 | 1593 | _ssr_str_destroy(bin_dir); 1594 | return EXIT_SUCCESS; 1595 | } 1596 | #else 1597 | static void _ssr_add_file_cb(void* args, const char* base, const char* filename) { 1598 | _ssr_vec_t* files = (_ssr_vec_t*) args; 1599 | _ssr_str_t full_path = _ssr_str_f("%s/%s", base, filename); 1600 | _ssr_vec_push(files, &full_path); 1601 | } 1602 | #endif 1603 | 1604 | SSR_DEF bool ssr_run(struct ssr_t* ssr) { 1605 | #ifdef SSR_LIVE 1606 | if (_ssr_thread(&ssr->thread, (void*) _ssr_main, ssr)) { 1607 | // ERROR: Failed to launch thread 1608 | return false; 1609 | } 1610 | return true; 1611 | #else 1612 | bool ret = false; 1613 | _ssr_vec_t files; 1614 | _ssr_vec(&files, sizeof(_ssr_str_t), 12); 1615 | _ssr_iter_dir(ssr->root, _ssr_add_file_cb, &files); 1616 | 1617 | _ssr_str_t bin = _ssr_str_f("%s/%s", ssr->root, SSR_BIN_DIR); 1618 | _ssr_new_dir(bin.b); 1619 | 1620 | size_t files_len = _ssr_vec_len(&files); 1621 | if (files_len == 0) goto end_no_files; 1622 | 1623 | char** defines = (char**) malloc(sizeof(char*) * (ssr->config->num_defines + 1)); 1624 | for (size_t i = 0; i < ssr->config->num_defines; ++i) 1625 | defines[i] = ssr->config->defines[i]; 1626 | ++ssr->config->num_defines; 1627 | ssr->config->defines = defines; 1628 | 1629 | size_t linker_input_len = 0; 1630 | size_t linker_input_cap = 1024; 1631 | char* linker_input = (char*) malloc(linker_input_cap); 1632 | linker_input[0] = '\0'; 1633 | for (size_t i = 0; i < files_len; ++i) { 1634 | _ssr_str_t* path = (_ssr_str_t*) _ssr_vec_at(&files, i); 1635 | _ssr_str_t out = _ssr_str_f("%s/%d.obj", bin.b, i); 1636 | 1637 | size_t out_len = strlen(out.b); 1638 | if (out_len + linker_input_len > linker_input_cap) { 1639 | linker_input = (char*) realloc(linker_input, linker_input_cap * 2); // TODO: max.. 1640 | linker_input_cap *= 2; 1641 | } 1642 | 1643 | if (i == 0) 1644 | snprintf(linker_input, linker_input_cap, "%s", out.b); 1645 | else 1646 | snprintf(linker_input, linker_input_cap, "%s %s", linker_input, out.b); 1647 | linker_input_len += out_len; 1648 | 1649 | // In order to avoid name clashes _ 1650 | const char* script_rel = 1651 | _ssr_extract_rel(ssr->root, ((_ssr_str_t*) _ssr_vec_at(&files, i))->b); 1652 | _ssr_str_t script_id = _ssr_replace_seps(script_rel, SSR_SEP); 1653 | char buf[1024]; 1654 | size_t buf_len = snprintf(buf, 1024, "\"SSR_SCRIPTID=%s\"", script_id.b); 1655 | _ssr_str_destroy(script_id); 1656 | defines[ssr->config->num_defines - 1] = (char*) malloc(buf_len + 1); 1657 | _ssr_strncpy(defines[ssr->config->num_defines - 1], buf, buf_len + 1); 1658 | 1659 | bool ret = _ssr_compile(path->b, ssr->config, out.b, _SSR_COMPILE); 1660 | _ssr_str_destroy(out); 1661 | free(defines[ssr->config->num_defines - 1]); 1662 | if (!ret) goto end_no_lib; 1663 | } 1664 | 1665 | _ssr_str_t out = _ssr_str_f("%s/%s.%s", bin.b, "ssr", _ssr_lib_ext()); 1666 | bool link_ret = _ssr_compile(linker_input, ssr->config, out.b, _SSR_LINK); 1667 | if (!link_ret) goto end; 1668 | 1669 | // loading library & hooking up functions 1670 | _ssr_lib(&ssr->lib, out.b); // todo check valid 1671 | 1672 | ret = true; 1673 | 1674 | end: 1675 | _ssr_str_destroy(out); 1676 | end_no_lib: 1677 | free(linker_input); 1678 | free(defines); 1679 | end_no_files: 1680 | _ssr_str_destroy(bin); 1681 | 1682 | for (size_t i = 0; i < files_len; ++i) 1683 | _ssr_str_destroy(*(_ssr_str_t*) _ssr_vec_at(&files, i)); 1684 | _ssr_vec_destroy(&files); 1685 | return ret; 1686 | #endif 1687 | } 1688 | 1689 | SSR_DEF bool 1690 | ssr_add(struct ssr_t* ssr, const char* script_id, const char* fname, ssr_func_t* user_routine) { 1691 | #ifdef SSR_LIVE 1692 | _ssr_script_t* script = (_ssr_script_t*) _ssr_map_find_str(&ssr->scripts, script_id); 1693 | 1694 | // First time request 1695 | if (script == NULL) { 1696 | _ssr_script_t new_script; 1697 | _ssr_vec(&new_script.routines, sizeof(_ssr_routine_t), 32); 1698 | new_script.last_seen = 0; 1699 | new_script.last_written = 0; 1700 | new_script.rnd_id.b = NULL; 1701 | new_script.id = _ssr_str(script_id); 1702 | new_script.lib.h = NULL; 1703 | _ssr_map_add_str(&ssr->scripts, &new_script); 1704 | } 1705 | script = (_ssr_script_t*) _ssr_map_find_str(&ssr->scripts, script_id); // TODO can be avoided 1706 | 1707 | _ssr_routine_t* routine = NULL; 1708 | size_t routines_len = _ssr_vec_len(&script->routines); 1709 | for (size_t i = 0; i < routines_len; ++i) { 1710 | _ssr_routine_t* _routine = (_ssr_routine_t*) _ssr_vec_at(&script->routines, i); 1711 | if (strcmp(fname, _routine->name.b) == 0) { 1712 | routine = _routine; 1713 | break; 1714 | } 1715 | } 1716 | 1717 | if (routine == NULL) { 1718 | _ssr_routine_t new_routine; 1719 | new_routine.name = _ssr_str(fname); 1720 | new_routine.addr = NULL; 1721 | _ssr_vec(&new_routine.moos, sizeof(void*), 32); 1722 | _ssr_lock(&new_routine.moos_lock); 1723 | 1724 | _ssr_lock_acq(&new_routine.moos_lock); 1725 | _ssr_vec_push(&new_routine.moos, &user_routine); 1726 | _ssr_lock_rel(&new_routine.moos_lock); 1727 | 1728 | _ssr_vec_push(&script->routines, &new_routine); 1729 | routine = 1730 | (_ssr_routine_t*) _ssr_vec_at(&script->routines, _ssr_vec_len(&script->routines) - 1); 1731 | } 1732 | 1733 | // Registering as listener 1734 | void* routine_addr = routine->addr; 1735 | if (routine_addr != NULL) 1736 | *user_routine = routine_addr; // Might be already up to date but not a problem 1737 | 1738 | #else 1739 | _ssr_str_t func = _ssr_str_f("%s$%s", script_id, fname); 1740 | void* fptr = _ssr_lib_func_addr(&ssr->lib, func.b); 1741 | if (fptr == NULL) { 1742 | // todo log 1743 | return false; 1744 | } 1745 | (*user_routine) = fptr; 1746 | #endif 1747 | return true; 1748 | } 1749 | 1750 | SSR_DEF void 1751 | ssr_remove(struct ssr_t* ssr, const char* script_id, const char* fname, ssr_func_t* user_routine) { 1752 | #ifdef SSR_LIVE 1753 | _ssr_script_t* script = (_ssr_script_t*) _ssr_map_find_str(&ssr->scripts, script_id); 1754 | if (script == NULL) return; 1755 | 1756 | size_t num_routines = _ssr_vec_len(&script->routines); 1757 | for (size_t i = 0; i < num_routines; ++i) { 1758 | _ssr_routine_t* routine = (_ssr_routine_t*) _ssr_vec_at(&script->routines, i); 1759 | if (strcmp(routine->name.b, fname) == 0) { 1760 | _ssr_vec_remove(&routine->moos, &user_routine); 1761 | 1762 | if (_ssr_vec_len(&routine->moos) == 0) { 1763 | } // TODO: Remove subnode 1764 | return; 1765 | } 1766 | } 1767 | #else 1768 | (void) ssr; 1769 | (void) script_id; 1770 | (void) fname; 1771 | (void) user_routine; 1772 | #endif 1773 | } 1774 | 1775 | SSR_DEF void ssr_cb(struct ssr_t* ssr, int mask, ssr_cb_t cb) { 1776 | ssr->cb_mask = mask; 1777 | ssr->cb = cb; 1778 | } 1779 | 1780 | static void _ssr_log(int type, const char* fmt, ...) { 1781 | static ssr_t* ssr = NULL; 1782 | 1783 | if (fmt == NULL) { 1784 | va_list args; 1785 | va_start(args, fmt); 1786 | ssr = va_arg(args, ssr_t*); 1787 | va_end(args); 1788 | return; 1789 | } 1790 | 1791 | if (ssr == NULL || ssr->cb == NULL || (type & ssr->cb_mask) == 0x0) return; 1792 | 1793 | char log_buf[SSR_LOG_BUF]; 1794 | va_list args; 1795 | va_start(args, fmt); 1796 | vsnprintf(log_buf, SSR_LOG_BUF, fmt, args); 1797 | va_end(args); 1798 | ssr->cb(type, log_buf); 1799 | } 1800 | #endif // SSR_SCRIPT 1801 | #endif // SSR_H_GUARD 1802 | --------------------------------------------------------------------------------