From 222e9035c305713e0f9cba99e03eeeff0c0dd091 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Mon, 18 Nov 2024 12:31:12 -0600 Subject: [PATCH] faster file handling on windows --- meson.build | 6 +- scripts/actor.js | 20 +- scripts/components.js | 2 +- scripts/engine.js | 6 +- scripts/render.js | 18 +- source/jsffi.c | 217 +++++++--- source/qjs_tracy.c | 117 ++++-- source/render_trace.cpp | 4 +- source/tinydir.h | 848 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 1120 insertions(+), 118 deletions(-) create mode 100644 source/tinydir.h diff --git a/meson.build b/meson.build index c59f4cf0..ffe02ef2 100644 --- a/meson.build +++ b/meson.build @@ -32,6 +32,10 @@ endif if host_machine.system() == 'windows' deps += cc.find_library('d3d11') + # these are for tracy + deps += cc.find_library('ws2_32') + deps += cc.find_library('dbghelp') + #end link += '-static' # Required to pack in mingw dlls on cross compilation endif @@ -60,7 +64,7 @@ deps += dependency('qjs-layout',static:true) deps += dependency('qjs-nota',static:true) deps += dependency('qjs-miniz',static:true) deps += dependency('qjs-soloud',static:true) -deps += dependency('qjs-dmon',static:true) +#deps += dependency('qjs-dmon',static:true) deps += dependency('threads') if get_option('chipmunk') diff --git a/scripts/actor.js b/scripts/actor.js index 4dba3bf1..6103adb2 100644 --- a/scripts/actor.js +++ b/scripts/actor.js @@ -4,7 +4,8 @@ var actor_urs = {}; var actor_spawns = {}; -globalThis.class_use = function class_use(script, config, base, callback) { +function class_eval(script) +{ var file = Resources.find_script(script); if (!file) { @@ -30,7 +31,22 @@ globalThis.class_use = function class_use(script, config, base, callback) { script = `(function use_${file.name()}() { var self = this; var $ = this.__proto__; ${script}; })`; var fn = os.eval(file, script); - fn.call(padawan); + return { + usefn: fn, + file: file, + parent: actor_urs[file] + }; +}.hashify(); + +globalThis.class_use = function class_use(script, config, base, callback) { + var prog = class_eval(script); + var padawan = Object.create(prog.parent); + actor_spawns[file].push(padawan); + padawan._file = prog.file; + padawan._root = prog.file.dir(); + + if (callback) callback(padawan); + prog.fn.call(padawan); if (typeof config === "object") Object.merge(padawan, config); diff --git a/scripts/components.js b/scripts/components.js index e7bd5de9..be80f92e 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -24,7 +24,7 @@ function sprite_addbucket(sprite) { function sprite_rmbucket(sprite) { if (sprite._oldlayer && sprite._oldtex) sprite_buckets[sprite._oldlayer][sprite._oldtex].remove(sprite); - else for (var layer of Object.values(sprite_buckets)) for (var path of Object.values(layer)) path.remove(sprite); +// else for (var layer of Object.values(sprite_buckets)) for (var path of Object.values(layer)) path.remove(sprite); }; /* an anim is simply an array of images */ diff --git a/scripts/engine.js b/scripts/engine.js index f79885a8..e3061d50 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -324,6 +324,9 @@ globalThis.use = function use(file) { }; var allpaths = io.ls(); +//console.log(`found ${allpaths.length} files`); +//console.log(json.encode(allpaths)) + io.exists = function(path) { return allpaths.includes(path);// || core_db.exists(path); @@ -334,9 +337,8 @@ io.slurp = function slurp(path) { var findpath = Resources.replpath(path); var ret = tmpslurp(findpath, true); //|| core_db.slurp(findpath, true); - if (!ret) console.info(`could not slurp path ${path} as ${findpath}`) return ret; -} +}.hashify(); io.slurpbytes = function(path) { diff --git a/scripts/render.js b/scripts/render.js index 393109b3..918f937c 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -466,10 +466,9 @@ function make_shader(shader, pipe) { var file = shader; shader = io.slurp(file); - if (!shader) { - console.info(`not found! slurping ${file}`); + if (!shader) shader = io.slurp(`${file}`); - } + var writejson = `.prosperon/${file.name()}.shader.json`; breakme: if (io.exists(writejson)) { @@ -1450,15 +1449,6 @@ try{ render.end_pass(); render.commit(); endframe(); - var cycles = os.gc(); - if (cycles.length > 0) { - cycles.forEach(x => { - x.address = x.address.toString(16); - x.shape = x.shape.toString(16); - x.class = x.class.toString(16); - }); - tracy.message(`GC cycles freed: ${json.encode(cycles)}`); - } tracy.gpu_collect(); tracy.end_frame(); tracy.gpu_sync(); @@ -1472,7 +1462,7 @@ try{ } }; -dmon.watch('.'); +//if (dmon) dmon.watch('.'); function dmon_cb(e) { @@ -1489,7 +1479,7 @@ function dmon_cb(e) prosperon.process = function process() { layout.newframe(); // check for hot reloading - dmon.poll(dmon_cb); +// if (dmon) dmon.poll(dmon_cb); var dt = profile.secs(profile.now()) - frame_t; frame_t = profile.secs(profile.now()); diff --git a/source/jsffi.c b/source/jsffi.c index 8d709352..06d7e4eb 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -29,7 +29,7 @@ #include #include "timer.h" #include -#include +#include "tinydir.h" #include "cute_aseprite.h" JSValue js_getpropertyuint32(JSContext *js, JSValue v, unsigned int i) @@ -644,15 +644,15 @@ JSC_CCALL(render_camera_screen2world, JSC_CCALL(render_set_projection_ortho, lrtb extents = js2lrtb(js, argv[0]); - float near = js2number(js,argv[1]); - float far = js2number(js,argv[2]); + float nearme = js2number(js,argv[1]); + float farme = js2number(js,argv[2]); globalview.p = HMM_Orthographic_RH_ZO( extents.l, extents.r, extents.b, extents.t, - near, - far + nearme, + farme ); globalview.vp = HMM_MulM4(globalview.p, globalview.v); ) @@ -660,9 +660,9 @@ JSC_CCALL(render_set_projection_ortho, JSC_CCALL(render_set_projection_perspective, float fov = js2number(js,argv[0]); float aspect = js2number(js,argv[1]); - float near = js2number(js,argv[2]); - float far = js2number(js,argv[3]); - globalview.p = HMM_Perspective_RH_NO(fov, aspect, near, far); + float nearme = js2number(js,argv[2]); + float farme = js2number(js,argv[3]); + globalview.p = HMM_Perspective_RH_NO(fov, aspect, nearme, farme); globalview.vp = HMM_MulM4(globalview.p, globalview.v); ) @@ -1667,8 +1667,7 @@ static const JSCFunctionListEntry js_time_funcs[] = { }; JSC_SCALL(console_log, - printf("%s\n",str); - fflush(stdout); + printf("%s\n", str); ) static const JSCFunctionListEntry js_console_funcs[] = { @@ -1741,29 +1740,64 @@ static const JSCFunctionListEntry js_debug_funcs[] = { MIST_FUNC_DEF(debug, dump_obj, 1), }; +#ifdef __WIN32 +#include +#include + +void list_files(const char *path, JSContext *js, JSValue v, int *n) { + WIN32_FIND_DATA ffd; + HANDLE hFind = INVALID_HANDLE_VALUE; + char searchPath[MAX_PATH]; + + // Prepare the path for search + snprintf(searchPath, sizeof(searchPath), "%s/*", path); + + hFind = FindFirstFileEx(searchPath, FindExInfoStandard, &ffd, FindExSearchLimitToDirectories, NULL, 0); + if (hFind == INVALID_HANDLE_VALUE) { + return; + } + + do { + if (strcmp(ffd.cFileName, ".") != 0 && strcmp(ffd.cFileName, "..") != 0) { + char filePath[MAX_PATH]; + snprintf(filePath, sizeof(filePath), "%s/%s", path, ffd.cFileName); + + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + // If it's a directory, recurse into it +// printf("Directory: %s\n", filePath); + list_files(filePath, js, v, n); + } else { + JS_SetPropertyUint32(js, v, *n, JS_NewString(js, filePath+2)); + *n = *n+1; + // If it's a file, print it +// printf("File: %s\n", filePath); + } + } + } while (FindNextFile(hFind, &ffd) != 0); + + FindClose(hFind); +} +#else static void list_files(const char *path, JSContext *js, JSValue v, int *n) { - DIR *dir = opendir(path); - struct dirent *entry; - while ((entry = readdir(dir)) != NULL) { - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - - char full_path[1024]; - snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name); - struct stat statbuf; - if (stat(full_path, &statbuf) == 0) { - if (S_ISDIR(statbuf.st_mode)) { - list_files(full_path, js, v, n); - } else if (S_ISREG(statbuf.st_mode)) { - JS_SetPropertyUint32(js, v, *n, JS_NewString(js, full_path+2)); - *n = *n + 1; - } + tinydir_dir dir; + tinydir_open(&dir, path); + do { + tinydir_file file; + tinydir_readfile(&dir, &file); + if (file.is_dir) { + if (!strcmp(file.name, ".") || !strcmp(file.name, "..")) continue; + list_files(file.path, js, v, n); } - } - closedir(dir); + else { + JS_SetPropertyUint32(js, v, *n, JS_NewString(js, file.path+2)); + *n = *n+1; + } + } while (!tinydir_next(&dir)); + + tinydir_close(&dir); } - +#endif JSC_CCALL(io_ls, JSValue strarr = JS_NewArray(js); int i = 0; @@ -1775,31 +1809,103 @@ JSC_SCALL(io_chdir, ret = number2js(js,chdir(str))) JSC_SCALL(io_rm, ret = number2js(js,remove(str))) JSC_SCALL(io_mkdir, ret = number2js(js,mkdir(str,0777))) -JSValue js_io_slurp(JSContext *js, JSValue self, int argc, JSValue *argv) -{ - const char *file = JS_ToCString(js, argv[0]); - - FILE *f = fopen(file, "rb"); - JS_FreeCString(js,file); - if (!f) - return JS_UNDEFINED; +#include +#include +#include - fseek(f,0,SEEK_END); - size_t fsize = ftell(f); - rewind(f); - void *slurp = malloc(fsize); - fread(slurp,fsize,1,f); - fclose(f); +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif - JSValue ret; - if (JS_ToBool(js,argv[1])) - ret = JS_NewStringLen(js, slurp, fsize); - else - ret = JS_NewArrayBufferCopy(js, slurp, fsize); +JSValue js_io_slurp(JSContext *js, JSValue self, int argc, JSValue *argv) { + const char *file = JS_ToCString(js, argv[0]); + if (!file) + return JS_EXCEPTION; - free(slurp); - - return ret; + size_t fsize; + void *slurp = NULL; + +#ifdef _WIN32 + // Windows file mapping + HANDLE hFile = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + JS_FreeCString(js, file); + return JS_UNDEFINED; + } + + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(hFile, &fileSize)) { + CloseHandle(hFile); + JS_FreeCString(js, file); + return JS_UNDEFINED; + } + + fsize = (size_t)fileSize.QuadPart; + HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (hMapping == NULL) { + CloseHandle(hFile); + JS_FreeCString(js, file); + return JS_UNDEFINED; + } + + slurp = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); + CloseHandle(hMapping); + CloseHandle(hFile); + + if (slurp == NULL) { + JS_FreeCString(js, file); + return JS_UNDEFINED; + } + +#else + // POSIX mmap + int fd = open(file, O_RDONLY); + if (fd < 0) { + JS_FreeCString(js, file); + return JS_UNDEFINED; + } + + struct stat sb; + if (fstat(fd, &sb) == -1) { + close(fd); + JS_FreeCString(js, file); + return JS_UNDEFINED; + } + + fsize = sb.st_size; + slurp = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + + if (slurp == MAP_FAILED) { + JS_FreeCString(js, file); + return JS_UNDEFINED; + } +#endif + + JSValue ret; + if (JS_ToBool(js, argv[1])) { + // Return as string + ret = JS_NewStringLen(js, slurp, fsize); + } else { + // Return as ArrayBuffer + ret = JS_NewArrayBufferCopy(js, slurp, fsize); + } + +#ifdef _WIN32 + // Unmap on Windows + UnmapViewOfFile(slurp); +#else + // Unmap on POSIX + munmap(slurp, fsize); +#endif + + JS_FreeCString(js, file); + return ret; } JSValue js_io_slurpwrite(JSContext *js, JSValue self, int argc, JSValue *argv) @@ -2133,7 +2239,10 @@ JSValue js_os_cwd(JSContext *js, JSValue self, int argc, JSValue *argv) return JS_NewString(js,cwd); } -JSC_SCALL(os_env, ret = JS_NewString(js,getenv(str))) +JSC_SCALL(os_env, + char *env = getenv(str); + if (env) ret = JS_NewString(js,env); +) JSValue js_os_sys(JSContext *js, JSValue self, int argc, JSValue *argv) { @@ -2799,7 +2908,7 @@ JSValue js_layout_use(JSContext *js); JSValue js_miniz_use(JSContext *js); JSValue js_soloud_use(JSContext *js); JSValue js_chipmunk2d_use(JSContext *js); -JSValue js_dmon_use(JSContext *js); +//JSValue js_dmon_use(JSContext *js); #ifdef TRACY_ENABLE JSValue js_tracy_use(JSContext *js); @@ -2889,7 +2998,7 @@ void ffi_load(JSContext *js) { JS_SetPropertyStr(js, globalThis, "miniz", js_miniz_use(js)); JS_SetPropertyStr(js, globalThis, "soloud", js_soloud_use(js)); JS_SetPropertyStr(js, globalThis, "chipmunk2d", js_chipmunk2d_use(js)); - JS_SetPropertyStr(js, globalThis, "dmon", js_dmon_use(js)); +// JS_SetPropertyStr(js, globalThis, "dmon", js_dmon_use(js)); #ifdef TRACY_ENABLE JS_SetPropertyStr(js, globalThis, "tracy", js_tracy_use(js)); diff --git a/source/qjs_tracy.c b/source/qjs_tracy.c index c4a88c10..6c36a377 100644 --- a/source/qjs_tracy.c +++ b/source/qjs_tracy.c @@ -1,11 +1,12 @@ #include -#include "glad.h" #include #include #include +#include "render.h" static JSValue js_tracy_fiber_enter(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_UNDEFINED; #ifdef TRACY_ON_DEMAND if (!TracyCIsConnected) { JS_Call(js,argv[0], JS_UNDEFINED,0,NULL); @@ -24,6 +25,7 @@ static JSValue js_tracy_fiber_enter(JSContext *js, JSValue self, int argc, JSVal static JSValue js_tracy_fiber_leave(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_UNDEFINED; #ifdef TRACY_ON_DEMAND if (!TracyCIsConnected) return JS_UNDEFINED; @@ -105,7 +107,6 @@ static JSValue js_tracy_zone_begin(JSContext *js, JSValue self, int argc, JSValu } #endif -#ifndef DEEP_TRACE const char *fn_src = JS_AtomToCString(js, js_fn_filename(js, argv[0])); const char *js_fn_name = get_func_name(js, argv[0]); const char *fn_name; @@ -118,14 +119,36 @@ static JSValue js_tracy_zone_begin(JSContext *js, JSValue self, int argc, JSValu JS_Call(js, argv[0], JS_UNDEFINED, 0, NULL); TracyCZoneEnd(TCTX); -#endif } +#ifdef SOKOL_GLCORE +#include "glad.h" GLuint *ids; static GLsizei query_count = 64*1024; static int qhead = 0; static int qtail = 0; +static JSValue js_tracy_gpu_init(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + printf("GLAD LOAD %d\n", gladLoadGL()); + ids = malloc(sizeof(GLuint)*query_count); + glGenQueries(query_count, ids); // generate new query ids + int64_t tgpu; + glGetInteger64v(GL_TIMESTAMP, &tgpu); + + float period = 1.f; + struct ___tracy_gpu_new_context_data gpuctx = { + .gpuTime = tgpu, + .period = period, + .context = 0, + .flags = 0, + .type = 1 + }; + ___tracy_emit_gpu_new_context(gpuctx); + + return JS_UNDEFINED; +} + static JSValue js_tracy_gpu_zone_begin(JSContext *js, JSValue self, int argc, JSValue *argv) { #ifdef TRACY_ON_DEMAND @@ -170,44 +193,6 @@ static JSValue js_tracy_gpu_zone_begin(JSContext *js, JSValue self, int argc, JS return JS_UNDEFINED; } -static JSValue js_tracy_gpu_zone_end(JSContext *js, JSValue self, int argc, JSValue *argv) -{ -#ifdef TRACY_ON_DEMAND - if (!TracyCIsConnected) - return JS_UNDEFINED; -#endif - - glQueryCounter(ids[qhead], GL_TIMESTAMP); - struct ___tracy_gpu_zone_end_data data = { - .queryId = ids[qhead], - .context = 0 - }; - ___tracy_emit_gpu_zone_end(data); - qhead = (qhead+1)%query_count; - - return JS_UNDEFINED; -} - -static JSValue js_tracy_gpu_init(JSContext *js, JSValue self, int argc, JSValue *argv) -{ - ids = malloc(sizeof(GLuint)*query_count); - glGenQueries(query_count, ids); // generate new query ids - int64_t tgpu; - glGetInteger64v(GL_TIMESTAMP, &tgpu); - - float period = 1.f; - struct ___tracy_gpu_new_context_data gpuctx = { - .gpuTime = tgpu, - .period = period, - .context = 0, - .flags = 0, - .type = 1 - }; - ___tracy_emit_gpu_new_context(gpuctx); - - return JS_UNDEFINED; -} - static JSValue js_tracy_gpu_sync(JSContext *js, JSValue self, int argc, JSValue *argv) { #ifdef TRACY_ON_DEMAND @@ -246,12 +231,62 @@ static JSValue js_tracy_gpu_collect(JSContext *js, JSValue self, int argc, JSVal return JS_UNDEFINED; } +#elifdef SOKOL_D3D11 +static int max_queries = 64*1024; + +static JSValue js_tracy_gpu_init(JSContext *js, JSValue self, int argc, JSValue *argv) +{ +// D3D11_QUERY_DESC desc= {}; + return JS_UNDEFINED; +} + +static JSValue js_tracy_gpu_zone_begin(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + return JS_Call(js,argv[0], JS_UNDEFINED, 0, NULL); + return JS_UNDEFINED; +} + +static JSValue js_tracy_gpu_sync(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + return JS_UNDEFINED; +} + +static JSValue js_tracy_gpu_collect(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + return JS_UNDEFINED; +} + +#else + +static JSValue js_tracy_gpu_init(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + return JS_UNDEFINED; +} + +static JSValue js_tracy_gpu_zone_begin(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + return JS_Call(js,argv[0], JS_UNDEFINED, 0, NULL); +} + +static JSValue js_tracy_gpu_sync(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + return JS_UNDEFINED; +} + +static JSValue js_tracy_gpu_collect(JSContext *js, JSValue self, int argc, JSValue *argv) +{ + return JS_UNDEFINED; +} + +#endif + static const JSCFunctionListEntry js_tracy_funcs[] = { JS_CFUNC_DEF("fiber", 1, js_tracy_fiber_enter), JS_CFUNC_DEF("fiber_leave", 1, js_tracy_fiber_leave), JS_CFUNC_DEF("gpu_zone", 1, js_tracy_gpu_zone_begin), JS_CFUNC_DEF("gpu_collect", 0, js_tracy_gpu_collect), JS_CFUNC_DEF("gpu_init", 0, js_tracy_gpu_init), + JS_CFUNC_DEF("gpu_sync", 0, js_tracy_gpu_sync), JS_CFUNC_DEF("end_frame", 0, js_tracy_frame_mark), JS_CFUNC_DEF("zone", 1, js_tracy_zone_begin), JS_CFUNC_DEF("message", 1, js_tracy_message), diff --git a/source/render_trace.cpp b/source/render_trace.cpp index 0671e8d4..4cfcca47 100644 --- a/source/render_trace.cpp +++ b/source/render_trace.cpp @@ -3,7 +3,6 @@ #include #include - void trace_apply_uniforms(sg_shader_stage stage, int ub_index, const sg_range *data, void *user_data) { } @@ -111,6 +110,7 @@ static sg_trace_hooks hooks; extern "C"{ void render_trace_init() { + return; hooks.apply_pipeline = trace_apply_pipeline; hooks.begin_pass = trace_begin_pass; SG_HOOK_SET(buffer); @@ -124,8 +124,6 @@ void render_trace_init() hooks.draw = trace_draw; sg_trace_hooks hh = sg_install_trace_hooks(&hooks); - - printf("GLAD LOAD %d\n", gladLoadGL()); TracyGpuContext; } diff --git a/source/tinydir.h b/source/tinydir.h new file mode 100644 index 00000000..d7f46689 --- /dev/null +++ b/source/tinydir.h @@ -0,0 +1,848 @@ +/* +Copyright (c) 2013-2021, tinydir authors: +- Cong Xu +- Lautis Sun +- Baudouin Feildel +- Andargor +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef TINYDIR_H +#define TINYDIR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if ((defined _UNICODE) && !(defined UNICODE)) +#define UNICODE +#endif + +#if ((defined UNICODE) && !(defined _UNICODE)) +#define _UNICODE +#endif + +#include +#include +#include +#ifdef _MSC_VER +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include +# pragma warning(push) +# pragma warning (disable : 4996) +#else +# include +# include +# include +# include +#endif +#ifdef __MINGW32__ +# include +#endif + + +/* types */ + +/* Windows UNICODE wide character support */ +#if defined _MSC_VER || defined __MINGW32__ +# define _tinydir_char_t TCHAR +# define TINYDIR_STRING(s) _TEXT(s) +# define _tinydir_strlen _tcslen +# define _tinydir_strcpy _tcscpy +# define _tinydir_strcat _tcscat +# define _tinydir_strcmp _tcscmp +# define _tinydir_strrchr _tcsrchr +# define _tinydir_strncmp _tcsncmp +#else +# define _tinydir_char_t char +# define TINYDIR_STRING(s) s +# define _tinydir_strlen strlen +# define _tinydir_strcpy strcpy +# define _tinydir_strcat strcat +# define _tinydir_strcmp strcmp +# define _tinydir_strrchr strrchr +# define _tinydir_strncmp strncmp +#endif + +#if (defined _MSC_VER || defined __MINGW32__) +# include +# define _TINYDIR_PATH_MAX MAX_PATH +#elif defined __linux__ +# include +# ifdef PATH_MAX +# define _TINYDIR_PATH_MAX PATH_MAX +# endif +#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +# include +# if defined(BSD) +# include +# ifdef PATH_MAX +# define _TINYDIR_PATH_MAX PATH_MAX +# endif +# endif +#endif + +#ifndef _TINYDIR_PATH_MAX +#define _TINYDIR_PATH_MAX 4096 +#endif + +#ifdef _MSC_VER +/* extra chars for the "\\*" mask */ +# define _TINYDIR_PATH_EXTRA 2 +#else +# define _TINYDIR_PATH_EXTRA 0 +#endif + +#define _TINYDIR_FILENAME_MAX 256 + +#if (defined _MSC_VER || defined __MINGW32__) +#define _TINYDIR_DRIVE_MAX 3 +#endif + +#ifdef _MSC_VER +# define _TINYDIR_FUNC static __inline +#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L +# define _TINYDIR_FUNC static __inline__ +#elif defined(__cplusplus) +# define _TINYDIR_FUNC static inline +#elif defined(__GNUC__) +/* Suppress unused function warning */ +# define _TINYDIR_FUNC __attribute__((unused)) static +#else +# define _TINYDIR_FUNC static +#endif + +#if defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) +#ifdef _MSC_VER +# define _TINYDIR_CDECL __cdecl +#else +# define _TINYDIR_CDECL __attribute__((cdecl)) +#endif +#else +# define _TINYDIR_CDECL +#endif + +/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */ +#ifdef TINYDIR_USE_READDIR_R + +/* readdir_r is a POSIX-only function, and may not be available under various + * environments/settings, e.g. MinGW. Use readdir fallback */ +#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\ + _POSIX_SOURCE +# define _TINYDIR_HAS_READDIR_R +#endif +#if _POSIX_C_SOURCE >= 200112L +# define _TINYDIR_HAS_FPATHCONF +# include +#endif +#if _BSD_SOURCE || _SVID_SOURCE || \ + (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) +# define _TINYDIR_HAS_DIRFD +# include +#endif +#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\ + defined _PC_NAME_MAX +# define _TINYDIR_USE_FPATHCONF +#endif +#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\ + !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX) +# define _TINYDIR_USE_READDIR +#endif + +/* Use readdir by default */ +#else +# define _TINYDIR_USE_READDIR +#endif + +/* MINGW32 has two versions of dirent, ASCII and UNICODE*/ +#ifndef _MSC_VER +#if (defined __MINGW32__) && (defined _UNICODE) +#define _TINYDIR_DIR _WDIR +#define _tinydir_dirent _wdirent +#define _tinydir_opendir _wopendir +#define _tinydir_readdir _wreaddir +#define _tinydir_closedir _wclosedir +#else +#define _TINYDIR_DIR DIR +#define _tinydir_dirent dirent +#define _tinydir_opendir opendir +#define _tinydir_readdir readdir +#define _tinydir_closedir closedir +#endif +#endif + +/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */ +#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE) +#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE) +#else +#error "Either define both alloc and free or none of them!" +#endif + +#if !defined(_TINYDIR_MALLOC) + #define _TINYDIR_MALLOC(_size) malloc(_size) + #define _TINYDIR_FREE(_ptr) free(_ptr) +#endif /* !defined(_TINYDIR_MALLOC) */ + +typedef struct tinydir_file +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + _tinydir_char_t name[_TINYDIR_FILENAME_MAX]; + _tinydir_char_t *extension; + int is_dir; + int is_reg; + +#ifndef _MSC_VER +#ifdef __MINGW32__ + struct _stat _s; +#else + struct stat _s; +#endif +#endif +} tinydir_file; + +typedef struct tinydir_dir +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + int has_next; + size_t n_files; + + tinydir_file *_files; +#ifdef _MSC_VER + HANDLE _h; + WIN32_FIND_DATA _f; +#else + _TINYDIR_DIR *_d; + struct _tinydir_dirent *_e; +#ifndef _TINYDIR_USE_READDIR + struct _tinydir_dirent *_ep; +#endif +#endif +} tinydir_dir; + + +/* declarations */ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path); +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path); +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir); + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir); +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); + +_TINYDIR_FUNC +int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path); +_TINYDIR_FUNC +void _tinydir_get_ext(tinydir_file *file); +_TINYDIR_FUNC +int _TINYDIR_CDECL _tinydir_file_cmp(const void *a, const void *b); +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR +_TINYDIR_FUNC +size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp); +#endif +#endif + + +/* definitions*/ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path) +{ +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR + int error; + int size; /* using int size */ +#endif +#else + _tinydir_char_t path_buf[_TINYDIR_PATH_MAX]; +#endif + _tinydir_char_t *pathp; + + if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* initialise dir */ + dir->_files = NULL; +#ifdef _MSC_VER + dir->_h = INVALID_HANDLE_VALUE; +#else + dir->_d = NULL; +#ifndef _TINYDIR_USE_READDIR + dir->_ep = NULL; +#endif +#endif + tinydir_close(dir); + + _tinydir_strcpy(dir->path, path); + /* Remove trailing slashes */ + pathp = &dir->path[_tinydir_strlen(dir->path) - 1]; + while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/'))) + { + *pathp = TINYDIR_STRING('\0'); + pathp++; + } +#ifdef _MSC_VER + _tinydir_strcpy(path_buf, dir->path); + _tinydir_strcat(path_buf, TINYDIR_STRING("\\*")); +#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) + dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0); +#else + dir->_h = FindFirstFile(path_buf, &dir->_f); +#endif + if (dir->_h == INVALID_HANDLE_VALUE) + { + errno = ENOENT; +#else + dir->_d = _tinydir_opendir(path); + if (dir->_d == NULL) + { +#endif + goto bail; + } + + /* read first file */ + dir->has_next = 1; +#ifndef _MSC_VER +#ifdef _TINYDIR_USE_READDIR + dir->_e = _tinydir_readdir(dir->_d); +#else + /* allocate dirent buffer for readdir_r */ + size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */ + if (size == -1) return -1; + dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size); + if (dir->_ep == NULL) return -1; + + error = readdir_r(dir->_d, dir->_ep, &dir->_e); + if (error != 0) return -1; +#endif + if (dir->_e == NULL) + { + dir->has_next = 0; + } +#endif + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path) +{ + /* Count the number of files first, to pre-allocate the files array */ + size_t n_files = 0; + if (tinydir_open(dir, path) == -1) + { + return -1; + } + while (dir->has_next) + { + n_files++; + if (tinydir_next(dir) == -1) + { + goto bail; + } + } + tinydir_close(dir); + + if (n_files == 0 || tinydir_open(dir, path) == -1) + { + return -1; + } + + dir->n_files = 0; + dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files); + if (dir->_files == NULL) + { + goto bail; + } + while (dir->has_next) + { + tinydir_file *p_file; + dir->n_files++; + + p_file = &dir->_files[dir->n_files - 1]; + if (tinydir_readfile(dir, p_file) == -1) + { + goto bail; + } + + if (tinydir_next(dir) == -1) + { + goto bail; + } + + /* Just in case the number of files has changed between the first and + second reads, terminate without writing into unallocated memory */ + if (dir->n_files == n_files) + { + break; + } + } + + qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir) +{ + if (dir == NULL) + { + return; + } + + memset(dir->path, 0, sizeof(dir->path)); + dir->has_next = 0; + dir->n_files = 0; + _TINYDIR_FREE(dir->_files); + dir->_files = NULL; +#ifdef _MSC_VER + if (dir->_h != INVALID_HANDLE_VALUE) + { + FindClose(dir->_h); + } + dir->_h = INVALID_HANDLE_VALUE; +#else + if (dir->_d) + { + _tinydir_closedir(dir->_d); + } + dir->_d = NULL; + dir->_e = NULL; +#ifndef _TINYDIR_USE_READDIR + _TINYDIR_FREE(dir->_ep); + dir->_ep = NULL; +#endif +#endif +} + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir) +{ + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (!dir->has_next) + { + errno = ENOENT; + return -1; + } + +#ifdef _MSC_VER + if (FindNextFile(dir->_h, &dir->_f) == 0) +#else +#ifdef _TINYDIR_USE_READDIR + dir->_e = _tinydir_readdir(dir->_d); +#else + if (dir->_ep == NULL) + { + return -1; + } + if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0) + { + return -1; + } +#endif + if (dir->_e == NULL) +#endif + { + dir->has_next = 0; +#ifdef _MSC_VER + if (GetLastError() != ERROR_SUCCESS && + GetLastError() != ERROR_NO_MORE_FILES) + { + tinydir_close(dir); + errno = EIO; + return -1; + } +#endif + } + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) +{ + const _tinydir_char_t *filename; + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } +#ifdef _MSC_VER + if (dir->_h == INVALID_HANDLE_VALUE) +#else + if (dir->_e == NULL) +#endif + { + errno = ENOENT; + return -1; + } + filename = +#ifdef _MSC_VER + dir->_f.cFileName; +#else + dir->_e->d_name; +#endif + if (_tinydir_strlen(dir->path) + + _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >= + _TINYDIR_PATH_MAX) + { + /* the path for the file will be too long */ + errno = ENAMETOOLONG; + return -1; + } + if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + _tinydir_strcpy(file->path, dir->path); + if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0) + _tinydir_strcat(file->path, TINYDIR_STRING("/")); + _tinydir_strcpy(file->name, filename); + _tinydir_strcat(file->path, filename); +#ifndef _MSC_VER +#ifdef __MINGW32__ + if (_tstat( +#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \ + || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \ + || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) \ + || ((defined __APPLE__) && (defined __MACH__)) \ + || (defined BSD) + if (lstat( +#else + if (stat( +#endif + file->path, &file->_s) == -1) + { + return -1; + } +#endif + _tinydir_get_ext(file); + + file->is_dir = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); +#else + S_ISDIR(file->_s.st_mode); +#endif + file->is_reg = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || + ( + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && +#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && +#endif +#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && +#endif + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); +#else + S_ISREG(file->_s.st_mode); +#endif + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) +{ + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files) + { + errno = ENOENT; + return -1; + } + + memcpy(file, &dir->_files[i], sizeof(tinydir_file)); + _tinydir_get_ext(file); + + return 0; +} + +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files || !dir->_files[i].is_dir) + { + errno = ENOENT; + return -1; + } + + _tinydir_strcpy(path, dir->_files[i].path); + tinydir_close(dir); + if (tinydir_open_sorted(dir, path) == -1) + { + return -1; + } + + return 0; +} + +/* Open a single file given its path */ +_TINYDIR_FUNC +int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) +{ + tinydir_dir dir; + int result = 0; + int found = 0; + _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t file_name_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t *dir_name; + _tinydir_char_t *base_name; +#if (defined _MSC_VER || defined __MINGW32__) + _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; +#endif + + if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* Get the parent path */ +#if (defined _MSC_VER || defined __MINGW32__) +#if ((defined _MSC_VER) && (_MSC_VER >= 1400)) + errno = _tsplitpath_s( + path, + drive_buf, _TINYDIR_DRIVE_MAX, + dir_name_buf, _TINYDIR_FILENAME_MAX, + file_name_buf, _TINYDIR_FILENAME_MAX, + ext_buf, _TINYDIR_FILENAME_MAX); +#else + _tsplitpath( + path, + drive_buf, + dir_name_buf, + file_name_buf, + ext_buf); +#endif + + if (errno) + { + return -1; + } + +/* _splitpath_s not work fine with only filename and widechar support */ +#ifdef _UNICODE + if (drive_buf[0] == L'\xFEFE') + drive_buf[0] = '\0'; + if (dir_name_buf[0] == L'\xFEFE') + dir_name_buf[0] = '\0'; +#endif + + /* Emulate the behavior of dirname by returning "." for dir name if it's + empty */ + if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') + { + _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); + } + /* Concatenate the drive letter and dir name to form full dir name */ + _tinydir_strcat(drive_buf, dir_name_buf); + dir_name = drive_buf; + /* Concatenate the file name and extension to form base name */ + _tinydir_strcat(file_name_buf, ext_buf); + base_name = file_name_buf; +#else + _tinydir_strcpy(dir_name_buf, path); + dir_name = dirname(dir_name_buf); + _tinydir_strcpy(file_name_buf, path); + base_name = basename(file_name_buf); +#endif + + /* Special case: if the path is a root dir, open the parent dir as the file */ +#if (defined _MSC_VER || defined __MINGW32__) + if (_tinydir_strlen(base_name) == 0) +#else + if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) +#endif + { + memset(file, 0, sizeof * file); + file->is_dir = 1; + file->is_reg = 0; + _tinydir_strcpy(file->path, dir_name); + file->extension = file->path + _tinydir_strlen(file->path); + return 0; + } + + /* Open the parent directory */ + if (tinydir_open(&dir, dir_name) == -1) + { + return -1; + } + + /* Read through the parent directory and look for the file */ + while (dir.has_next) + { + if (tinydir_readfile(&dir, file) == -1) + { + result = -1; + goto bail; + } + if (_tinydir_strcmp(file->name, base_name) == 0) + { + /* File found */ + found = 1; + break; + } + tinydir_next(&dir); + } + if (!found) + { + result = -1; + errno = ENOENT; + } + +bail: + tinydir_close(&dir); + return result; +} + +_TINYDIR_FUNC +void _tinydir_get_ext(tinydir_file *file) +{ + _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.')); + if (period == NULL) + { + file->extension = &(file->name[_tinydir_strlen(file->name)]); + } + else + { + file->extension = period + 1; + } +} + +_TINYDIR_FUNC +int _TINYDIR_CDECL _tinydir_file_cmp(const void *a, const void *b) +{ + const tinydir_file *fa = (const tinydir_file *)a; + const tinydir_file *fb = (const tinydir_file *)b; + if (fa->is_dir != fb->is_dir) + { + return -(fa->is_dir - fb->is_dir); + } + return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); +} + +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR +/* +The following authored by Ben Hutchings +from https://womble.decadent.org.uk/readdir_r-advisory.html +*/ +/* Calculate the required buffer size (in bytes) for directory * +* entries read from the given directory handle. Return -1 if this * +* this cannot be done. * +* * +* This code does not trust values of NAME_MAX that are less than * +* 255, since some systems (including at least HP-UX) incorrectly * +* define it to be a smaller value. */ +_TINYDIR_FUNC +size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp) +{ + long name_max; + size_t name_end; + /* parameter may be unused */ + (void)dirp; + +#if defined _TINYDIR_USE_FPATHCONF + name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); + if (name_max == -1) +#if defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else + return (size_t)(-1); +#endif +#elif defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else +#error "buffer size for readdir_r cannot be determined" +#endif + name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1; + return (name_end > sizeof(struct _tinydir_dirent) ? + name_end : sizeof(struct _tinydir_dirent)); +} +#endif +#endif + +#ifdef __cplusplus +} +#endif + +# if defined (_MSC_VER) +# pragma warning(pop) +# endif + +#endif